Работа с данными с помощью MLUtils.jl

Flux повторно экспортирует тип DataLoader и вспомогательные функции для работы с данными из MLUtils.

DataLoader

DataLoader можно использовать для создания мини-пакетов данных в формате, принимаемом train!.

На веб-сайте Flux есть специальное руководство по DataLoader, в котором можно найти дополнительные сведения.

# MLUtils.DataLoaderType

DataLoader(data; [batchsize, buffer, collate, parallel, partial, rng, shuffle])

Объект, который выполняет итерацию по мини-пакетам data, каждый из которых содержит batchsize наблюдений (кроме, возможно, последнего).

Принимает в качестве входных данных один массив данных, кортеж (или именованный кортеж) массивов либо в общем случае любой объект data, реализующий методы numobs и getobs .

Последним измерением в каждом массиве является измерение наблюдений, разделенное на мини-пакеты.

Исходные данные сохраняются в поле data объекта DataLoader.

Аргументы

  • data: итерируемые данные. Тип данных должен поддерживаться методами numobs и getobs.

  • batchsize: если значение меньше 0, итерация производится по отдельным наблюдениям. В противном случае при каждой итерации (кроме, возможно, последней) выдается мини-пакет, содержащий batchsize наблюдений. Значение по умолчанию — 1.

  • buffer: если buffer=true и данная возможность поддерживается типом объекта data, для повышения эффективности в памяти размещается повторно используемый буфер. В buffer можно также передать предварительно размещенный в памяти объект. Значение по умолчанию — false.

  • collate: поведение при пакетировании. При значении nothing (по умолчанию) пакет формируется посредством getobs(data, indices). При значении false каждый пакет формируется посредством [getobs(data, i) for i in indices]. При значении true функция batch применяется к вектору наблюдений в пакете, рекурсивно сопоставляя массивы по последним измерениям. Дополнительные сведения и примеры см. в описании функции batch.

  • parallel: следует ли загружать данные параллельно, используя рабочие потоки. Существенно ускоряет загрузку данных — в n раз, где n — число доступных потоков. Требует запуска Julia с несколькими потоками. Используйте Threads.nthreads() для проверки числа доступных потоков. При передаче parallel = true упорядоченность не гарантируется. Значение по умолчанию — false.

  • partial: этот аргумент используется только при batchsize > 0. Если partial=false и количество наблюдений не кратно batchsize, последний мини-пакет отбрасывается. Значение по умолчанию — true.

  • rng: генератор случайных чисел. Значение по умолчанию — Random.GLOBAL_RNG.

  • shuffle: следует ли перемешивать наблюдения перед итерированием. В отличие от обертывания контейнера данных с помощью shuffleobs(data), shuffle=true обеспечивает перемешивание наблюдений заново каждый раз, когда начинается итерация по eachobs. Значение по умолчанию — false.

Примеры

julia> Xtrain = rand(10, 100);

julia> array_loader = DataLoader(Xtrain, batchsize=2);

julia> for x in array_loader
         @assert size(x) == (10, 2)
         # выполняем какие-либо действия с x 50 раз
       end

julia> array_loader.data === Xtrain
true

julia> tuple_loader = DataLoader((Xtrain,), batchsize=2);  # то же самое, но выдаются одноэлементные кортежи

julia> for x in tuple_loader
         @assert x isa Tuple{Matrix}
         @assert size(x[1]) == (10, 2)
       end

julia> Ytrain = rand('a':'z', 100);  # теперь DataLoader будет выдавать 2-элементные именованные кортежи

julia> train_loader = DataLoader((data=Xtrain, label=Ytrain), batchsize=5, shuffle=true);

julia> for epoch in 1:100
         for (x, y) in train_loader  # доступ путем деструктуризации кортежа
           @assert size(x) == (10, 5)
           @assert size(y) == (5,)
           # потери += f(x, y) # и т. д., выполняется 100 * 20 раз
         end
       end

julia> first(train_loader).label isa Vector{Char}  # доступ по имени свойства
true

julia> first(train_loader).label == Ytrain[1:5]  # так как shuffle=true
false

julia> foreach(println∘summary, DataLoader(rand(Int8, 10, 64), batchsize=30))  # из-за partial=false последний мини-пакет пропускается
10×30 Matrix{Int8}
10×30 Matrix{Int8}
10×4 Matrix{Int8}

Вспомогательные функции

Вспомогательные функции предназначены для использования при работе с данными. Они помогают создавать входные данные для моделей или разделять набор данных на пакеты.

# MLUtils.batchFunction

batch(xs)

Объединяет массивы xs в один массив c дополнительным измерением.

Если элементами xs являются кортежи, именованные кортежи или словари, результат будет того же типа.

См. также описание unbatch.

Примеры

julia> batch([[1,2,3],
              [4,5,6]])
3×2 Matrix{Int64}:
 1  4
 2  5
 3  6

julia> batch([(a=[1,2], b=[3,4])
               (a=[5,6], b=[7,8])])
(a = [1 5; 2 6], b = [3 7; 4 8])

# MLUtils.batchsizeFunction

batchsize(data::BatchView) -> Int

Возвращает фиксированный размер каждого пакета в data.

Примеры

using MLUtils
X, Y = MLUtils.load_iris()

A = BatchView(X, batchsize=30)
@assert batchsize(A) == 30

# MLUtils.batchseqFunction

batchseq(seqs, val = 0)

Принимает список из N последовательностей и преобразует их в одну последовательность, каждый элемент которой представляет собой пакет N. Короткие последовательности дополняются значением val.

Примеры

julia> batchseq([[1, 2, 3], [4, 5]], 0)
3-element Vector{Vector{Int64}}:
 [1, 4]
 [2, 5]
 [3, 0]

# MLUtils.BatchViewType

BatchView(data, batchsize; partial=true, collate=nothing)
BatchView(data; batchsize=1, partial=true, collate=nothing)

Создает представление объекта data в виде вектора пакетов. Каждый пакет содержит одинаковое количество наблюдений. Размер пакета можно задать с помощью параметра batchsize. Если размер набора данных не кратен указанному значению batchsize, оставшиеся наблюдения игнорируются при partial=false. Если же partial=true, размер последнего пакета может быть немного меньше.

Обратите внимание, что доступ к данным не производится, пока не будет вызвана функция getindex.

При использовании в качестве итератора объект выполнит итерацию по набору данных один раз, что соответствует одной эпохе.

Для применения BatchView к какой-либо структуре данных тип переменной data должен реализовывать интерфейс контейнера данных. Дополнительные сведения см. в описании ObsView.

Аргументы

  • data: объект, описывающий набор данных. Может иметь любой тип, реализующий методы getobs и numobs (дополнительные сведения см. в разделе «Подробные сведения»).

  • batchsize: размер каждого пакета. Это количество наблюдений, которое должно содержаться в каждом пакете (кроме, возможно, последнего).

  • partial: если partial=false и количество наблюдений не кратно размеру пакета, последний мини-пакет отбрасывается.

  • collate: поведение при пакетировании. При значении nothing (по умолчанию) пакет формируется посредством getobs(data, indices). При значении false каждый пакет формируется посредством [getobs(data, i) for i in indices]. При значении true функция batch применяется к вектору наблюдений в пакете, рекурсивно сопоставляя массивы по последним измерениям. Дополнительные сведения и примеры см. в описании функции batch .

Примеры

using MLUtils
X, Y = MLUtils.load_iris()

A = BatchView(X, batchsize=30)
@assert typeof(A) <: BatchView <: AbstractVector
@assert eltype(A) <: SubArray{Float64,2}
@assert length(A) == 5 # Набор Iris содержит 150 наблюдений
@assert size(A[1]) == (4,30) # В наборе Iris 4 признака

# 5 пакетов по 30 наблюдений
for x in BatchView(X, batchsize=30)
    @assert typeof(x) <: SubArray{Float64,2}
    @assert numobs(x) === 30
end

# 7 пакетов по 20 наблюдений
# Обратите внимание, что в наборе данных Iris 150 наблюдений.
# Это означает, что при размере пакета, равном 20, последние
# 10 наблюдений будут игнорироваться
for (x, y) in BatchView((X, Y), batchsize=20, partial=false)
    @assert typeof(x) <: SubArray{Float64,2}
    @assert typeof(y) <: SubArray{String,1}
    @assert numobs(x) == numobs(y) == 20
end

# сопоставляем кортежи наблюдений
for (x, y) in BatchView((rand(10, 3), ["a", "b", "c"]), batchsize=2, collate=true, partial=false)
    @assert size(x) == (10, 2)
    @assert size(y) == (2,)
end


# случайным образом назначаем наблюдения одному и только одному пакету.
for (x, y) in BatchView(shuffleobs((X, Y)), batchsize=20)
    @assert typeof(x) <: SubArray{Float64,2}
    @assert typeof(y) <: SubArray{String,1}
end

# MLUtils.chunkFunction

chunk(x, n; [dims])
chunk(x; [size, dims])

Разделяет x на n частей либо, если size — целое число, на равные фрагменты размера size. Части содержат одинаковое количество элементов, кроме, возможно, последней, которая может быть меньше.

Если же size — это коллекция целочисленных значений, элементы x разделяются на фрагменты указанных размеров.

Если x — это массив, с помощью аргумента dims можно указать измерение, по которому должно производиться разделение (по умолчанию последнее измерение).

Примеры

julia> chunk(1:10, 3)
3-element Vector{UnitRange{Int64}}:
 1:4
 5:8
 9:10

julia> chunk(1:10; size = 2)
5-element Vector{UnitRange{Int64}}:
 1:2
 3:4
 5:6
 7:8
 9:10

julia> x = reshape(collect(1:20), (5, 4))
5×4 Matrix{Int64}:
 1   6  11  16
 2   7  12  17
 3   8  13  18
 4   9  14  19
 5  10  15  20

julia> xs = chunk(x, 2, dims=1)
2-element Vector{SubArray{Int64, 2, Matrix{Int64}, Tuple{UnitRange{Int64}, Base.Slice{Base.OneTo{Int64}}}, false}}:
 [1 6 11 16; 2 7 12 17; 3 8 13 18]
 [4 9 14 19; 5 10 15 20]

julia> xs[1]
3×4 view(::Matrix{Int64}, 1:3, :) with eltype Int64:
 1  6  11  16
 2  7  12  17
 3  8  13  18

julia> xes = chunk(x; size = 2, dims = 2)
2-element Vector{SubArray{Int64, 2, Matrix{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}, UnitRange{Int64}}, true}}:
 [1 6; 2 7; … ; 4 9; 5 10]
 [11 16; 12 17; … ; 14 19; 15 20]

julia> xes[2]
5×2 view(::Matrix{Int64}, :, 3:4) with eltype Int64:
 11  16
 12  17
 13  18
 14  19
 15  20

julia> chunk(1:6; size = [2, 4])
2-element Vector{UnitRange{Int64}}:
 1:2
 3:6
chunk(x, partition_idxs; [npartitions, dims])

Разделяет массив x по измерению dims в соответствии с индексами в partition_idxs.

Объект partition_idxs должен быть отсортирован и содержать только положительные целые числа от 1 до количества разделов.

Если количество разделов npartitions не указано, оно выводится из partition_idxs.

Если аргумент dims не задан, по умолчанию берется последнее измерение.

См. также описание unbatch.

Примеры

julia> x = reshape([1:10;], 2, 5)
2×5 Matrix{Int64}:
 1  3  5  7   9
 2  4  6  8  10

julia> chunk(x, [1, 2, 2, 3, 3])
3-element Vector{SubArray{Int64, 2, Matrix{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}, UnitRange{Int64}}, true}}:
 [1; 2;;]
 [3 5; 4 6]
 [7 9; 8 10]

# MLUtils.eachobsFunction

eachobs(data; kws...)

Возвращает итератор по data.

Поддерживает те же аргументы, что и DataLoader. Значение batchsize по умолчанию в данном случае равно -1, в то время как для DataLoader оно равно 1.

Примеры

X = rand(4,100)

for x in eachobs(X)
    # вход в цикл 100 раз
    @assert typeof(x) <: Vector{Float64}
    @assert size(x) == (4,)
end

# итерации по мини-пакетам
for x in eachobs(X, batchsize=10)
    # вход в цикл 10 раз
    @assert typeof(x) <: Matrix{Float64}
    @assert size(x) == (4,10)
end

# поддержка кортежей, именованных кортежей, словарей
for (x, y) in eachobs((X, Y))
    # ...
end

# MLUtils.fill_likeFunction

fill_like(x, val, [element_type=eltype(x)], [dims=size(x)]))

Создает массив с заданным типом элементов и размером на основе исходного массива x. Всем элементам нового массива присваивается значение val. Третий и четвертый аргументы являются необязательными, и их значениями по умолчанию являются тип элементов и размер исходного массива. Измерения могут быть указаны как целое число либо как кортеж.

См. также описание zeros_like и ones_like.

Примеры

julia> x = rand(Float32, 2)
2-element Vector{Float32}:
 0.16087806
 0.89916044

julia> fill_like(x, 1.7, (3, 3))
3×3 Matrix{Float32}:
 1.7  1.7  1.7
 1.7  1.7  1.7
 1.7  1.7  1.7

julia> using CUDA

julia> x = CUDA.rand(2, 2)
2×2 CuArray{Float32, 2, CUDA.Mem.DeviceBuffer}:
 0.803167  0.476101
 0.303041  0.317581

julia> fill_like(x, 1.7, Float64)
2×2 CuArray{Float64, 2, CUDA.Mem.DeviceBuffer}:
 1.7  1.7
 1.7  1.7

# MLUtils.filterobsFunction

filterobs(f, data)

Возвращает подмножество данных из контейнера data, включая все индексы i, для которых f(getobs(data, i)) === true.

data = 1:10
numobs(data) == 10
fdata = filterobs(>(5), data)
numobs(fdata) == 5

# MLUtils.flattenFunction

flatten(x::AbstractArray)

Преобразует входные данные произвольной формы в матрицу с сохранением размера последнего измерения.

См. также описание unsqueeze.

Примеры

julia> rand(3,4,5) |> flatten |> size
(12, 5)

# MLUtils.getobsFunction

getobs(data, [idx])

Возвращает наблюдения, соответствующие индексу наблюдения idx. Обратите внимание, что idx может быть любого типа, для которого в data определен метод getobs. Если аргумент idx не задан, материализуются все наблюдения в data.

Если для data не определен метод getobs, то в случае Tables.table(data) == true возвращаются строки в позиции idx. В противном случае возвращается data[idx].

Разработчики пользовательских контейнеров данных должны реализовывать Base.getindex для своего типа вместо getobs. Метод getobs следует реализовывать для типов только в том случае, если между getobs и Base.getindex есть различия (как в случае с многомерными массивами).

Возвращаемые наблюдения должны иметь форму, подходящую для передачи в какой-либо алгоритм обучения без дополнительных преобразований. Строгих требований к тому, как должны выглядеть эти «фактические данные», нет. Разработчик пользовательского контейнера данных принимает решение сам. Результат должен соответствовать типу idx (скаляр или вектор).

getobs по умолчанию поддерживает вложение массивов, кортежей, именованных кортежей и словарей в различных сочетаниях.

См. также описание getobs! и numobs.

Примеры

# именованные кортежи
x = (a = [1, 2, 3], b = rand(6, 3))

getobs(x, 2) == (a = 2, b = x.b[:, 2])
getobs(x, [1, 3]) == (a = [1, 3], b = x.b[:, [1, 3]])


# словари
x = Dict(:a => [1, 2, 3], :b => rand(6, 3))

getobs(x, 2) == Dict(:a => 2, :b => x[:b][:, 2])
getobs(x, [1, 3]) == Dict(:a => [1, 3], :b => x[:b][:, [1, 3]])

# MLUtils.getobs!Function

getobs!(buffer, data, idx)

Версия getobs(data, idx), выполняемая на месте. Если этот метод определен для типа объекта data, то результат следует сохранять в buffer вместо размещения специального объекта в памяти.

Реализовывать эту функцию необязательно. Если такой метод отсутствует для типа объекта data, аргумент buffer будет игнорироваться и будет возвращен результат getobs. Причиной может быть то, что тип объекта data противоречит принципам работы copy!. Таким образом, поддержка пользовательского метода getobs! является необязательной.

См. также описание getobs и numobs.

# MLUtils.joinobsFunction

joinobs(datas...)

Выполняет конкатенацию контейнеров данных datas.

data1, data2 = 1:10, 11:20
jdata = joinumobs(data1, data2)
getobs(jdata, 15) == 15

# MLUtils.group_countsFunction

group_counts(x)

Подсчитывает, сколько раз встречается каждый элемент x.

См. также описание group_indices.

Примеры

julia> group_counts(['a', 'b', 'b'])
Dict{Char, Int64} with 2 entries:
  'a' => 1
  'b' => 2

# MLUtils.group_indicesFunction

group_indices(x) -> Dict

Вычисляет индексы элементов вектора x для каждого содержащегося в нем уникального значения. Эта информация полезна для стратегий повторной выборки, таких как послойная выборка.

См. также описание group_counts.

Примеры

julia> x = [:yes, :no, :maybe, :yes];

julia> group_indices(x)
Dict{Symbol, Vector{Int64}} with 3 entries:
  :yes   => [1, 4]
  :maybe => [3]
  :no    => [2]

# MLUtils.groupobsFunction

groupobs(f, data)

Разделяет контейнер данных data на отдельные контейнеры данных, группируя наблюдения по f(obs).

data = -10:10
datas = groupobs(>(0), data)
length(datas) == 2

# MLUtils.kfoldsFunction

kfolds(n::Integer, k = 5) -> Tuple

Вычисляет назначения обучения и валидации для k перераспределений по n наблюдений и возвращает их в форме двух векторов. Первый вектор содержит векторы индексов для обучающих подмножеств, а второй — соответственно векторы индексов для валидационных подмножеств. На практике, как правило, используется значение k = 5 или k = 10. Следующий фрагмент кода генерирует назначения индексов для k = 5:

julia> train_idx, val_idx = kfolds(10, 5);

Каждое наблюдение назначается валидационному подмножеству один (и только один) раз. Таким образом, объединение всех векторов индексов валидационных данных соответствует полному диапазону 1:n. Обратите внимание, что наблюдения не присваиваются подмножествам случайным образом. Это означает, что смежные наблюдения, скорее всего, будут относиться к одному валидационному подмножеству.

julia> train_idx
5-element Array{Array{Int64,1},1}:
 [3,4,5,6,7,8,9,10]
 [1,2,5,6,7,8,9,10]
 [1,2,3,4,7,8,9,10]
 [1,2,3,4,5,6,9,10]
 [1,2,3,4,5,6,7,8]

julia> val_idx
5-element Array{UnitRange{Int64},1}:
 1:2
 3:4
 5:6
 7:8
 9:10
kfolds(data, [k = 5])

Повторно разделяет контейнер data k раз с использованием k-блочной стратегии и возвращает последовательность сверток в виде отложенного итератора. Создаются только подмножества данных, то есть фактические данные не копируются, пока не будет вызвана функция getobs.

По сути, стратегия k-блочного повторного разделения разделяет данный объект data на k частей примерно одинакового размера. Каждая часть один раз используется как валидационное множество, а остальные части применяются для обучения. В результате получается k разных разделов данных data.

Если размер набора данных не кратен указанному значению k, оставшиеся наблюдения равномерно распределяются между частями.

for (x_train, x_val) in kfolds(X, k=10)
    # код вызывается 10 раз
    # значение nobs(x_val) может различаться на ±1 или менее между итерациями
end

Поддерживается несколько переменных (например, для маркированных данных):

for ((x_train, y_train), val) in kfolds((X, Y), k=10)
    # ...
end

По умолчанию блоки создаются путем статических разделений. Используйте shuffleobs для назначения наблюдений блокам случайным образом.

for (x_train, x_val) in kfolds(shuffleobs(X), k = 10)
    # ...
end

Связанную функцию см. в описании leavepout.

# MLUtils.leavepoutFunction

leavepout(n::Integer, [size = 1]) -> Tuple

Вычисляет назначения обучения и валидации для k ≈ n/size перераспределений по n наблюдений и возвращает их в форме двух векторов. Первый вектор содержит векторы индексов для обучающих подмножеств, а второй — соответственно векторы индексов для валидационных подмножеств. Каждому валидационному подмножеству будет назначено либо size, либо size+1 наблюдений. Следующий фрагмент кода генерирует векторы индексов для size = 2.

julia> train_idx, val_idx = leavepout(10, 2);

Каждое наблюдение назначается валидационному подмножеству один (и только один) раз. Таким образом, объединение всех векторов индексов валидационных данных соответствует полному диапазону 1:n. Обратите внимание, что наблюдения не присваиваются подмножествам случайным образом. Это означает, что смежные наблюдения, скорее всего, будут относиться к одному валидационному подмножеству.

julia> train_idx
5-element Array{Array{Int64,1},1}:
 [3,4,5,6,7,8,9,10]
 [1,2,5,6,7,8,9,10]
 [1,2,3,4,7,8,9,10]
 [1,2,3,4,5,6,9,10]
 [1,2,3,4,5,6,7,8]

julia> val_idx
5-element Array{UnitRange{Int64},1}:
 1:2
 3:4
 5:6
 7:8
 9:10
leavepout(data, p = 1)

Повторно разделяет контейнер data с использованием k-блочной стратегии, где k выбирается так, чтобы каждое валидационное подмножество получившихся блоков содержало примерно p наблюдений. По умолчанию p = 1, что также известно как разделение с одним в остатке.

Полученная последовательность блоков возвращается в виде отложенного итератора. Создаются только подмножества данных. Это означает, что фактические данные не копируются, пока не будет вызвана функция getobs.

for (train, val) in leavepout(X, p=2)
    # если nobs(X) кратно 2,
    # результат numobs(val) будет равен 2 для каждой итерации,
    # в противном случае он может быть равен 3 для первых двух итераций.
end

Связанную функцию см. в описании kfolds.

# MLUtils.mapobsFunction

mapobs(f, data; batched=:auto)

Применяет функцию f к наблюдениям в контейнере данных data в отложенном режиме. Возвращает новый контейнер данных mdata, который может быть проиндексирован и имеет длину. Индексирование активирует преобразование f.

Именованный аргумент batched управляет поведением mdata[idx] и mdata[idxs], где idx — целое число, а idxs — вектор целых чисел:

  • batched=:auto (по умолчанию). Позволяет f обрабатывать оба варианта. Вызывает f(getobs(data, idx)) и f(getobs(data, idxs)).

  • batched=:never. Функция f всегда вызывается для отдельного наблюдения. Вызывает f(getobs(data, idx)) и [f(getobs(data, idx)) for idx in idxs].

  • batched=:always. Функция f всегда вызывается для пакета наблюдений. Вызывает getobs(f(getobs(data, [idx])), 1) и f(getobs(data, idxs)).

Примеры

julia> data = (a=[1,2,3], b=[1,2,3]);

julia> mdata = mapobs(data) do x
         (c = x.a .+ x.b,  d = x.a .- x.b)
       end
mapobs(#25, (a = [1, 2, 3], b = [1, 2, 3]); batched=:auto))

julia> mdata[1]
(c = 2, d = 0)

julia> mdata[1:2]
(c = [2, 4], d = [0, 0])
mapobs(fs, data)

Применяет каждую функцию в кортеже fs к наблюдениям в контейнере данных data в отложенном режиме. Возвращает кортеж преобразованных контейнеров данных.

mapobs(namedfs::NamedTuple, data)

Применяет NamedTuple функций к контейнеру data, преобразовывая его в контейнер данных, состоящий из NamedTuple. Выбрать столбец полученного контейнера данных можно с помощью синтаксиса обращения к полю.

data = 1:10
nameddata = mapobs((x = sqrt, y = log), data)
getobs(nameddata, 10) == (x = sqrt(10), y = log(10))
getobs(nameddata.x, 10) == sqrt(10)

# MLUtils.numobsFunction

numobs(data)

Возвращает общее количество наблюдений, содержащихся в data.

Если для data не определен метод numobs, то в случае Tables.table(data) == true возвращается количество строк, в противном случае возвращается length(data).

Разработчики пользовательских контейнеров данных должны реализовывать Base.length для своего типа вместо numobs. Метод numobs следует реализовывать для типов только в том случае, если между numobs и Base.length есть различия (как в случае с многомерными массивами).

getobs по умолчанию поддерживает вложение массивов, кортежей, именованных кортежей и словарей в различных сочетаниях.

См. также описание getobs.

Примеры

# именованные кортежи
x = (a = [1, 2, 3], b = rand(6, 3))
numobs(x) == 3

# словари
x = Dict(:a => [1, 2, 3], :b => rand(6, 3))
numobs(x) == 3

Во всех внутренних контейнерах должно быть одинаковое количество наблюдений:

julia> x = (a = [1, 2, 3, 4], b = rand(6, 3));

julia> numobs(x)
ERROR: DimensionMismatch: All data containers must have the same number of observations.
Stacktrace:
 [1] _check_numobs_error()
   @ MLUtils ~/.julia/dev/MLUtils/src/observation.jl:163
 [2] _check_numobs
   @ ~/.julia/dev/MLUtils/src/observation.jl:130 [inlined]
 [3] numobs(data::NamedTuple{(:a, :b), Tuple{Vector{Int64}, Matrix{Float64}}})
   @ MLUtils ~/.julia/dev/MLUtils/src/observation.jl:177
 [4] top-level scope
   @ REPL[35]:1

# MLUtils.normaliseFunction

normalise(x; dims=ndims(x), ϵ=1e-5)

Нормализует массив x к среднему значению 0 и среднеквадратичному отклонению 1 по измерениям, указанным в dims. По умолчанию dims — это последнее измерение.

ϵ — это небольшой дополнительный фактор, который добавляется в знаменатель для численной устойчивости.

# MLUtils.obsviewFunction

obsview(data, [indices])

Возвращает отложенное представление наблюдений в data, соответствующих индексам indices. Никакие данные, кроме индексов, не копируются. Эта функция действует аналогично созданию ObsView, но возвращает SubArray, если типом data является Array или SubArray. Кроме того, эту функцию можно расширить для пользовательских типов data, которые также должны предоставлять собственный тип подмножества.

Если data — кортеж, конструктор будет сопоставляться по его элементам. Это означает, что конструктор возвращает кортеж ObsView, а не ObsView кортежей.

Если вместо этого необходимо получить подмножество наблюдений, соответствующих индексам indices в исходном типе, используйте getobs.

Дополнительные сведения см. в описании ObsView.

# MLUtils.ObsViewType

ObsView(data, [indices])

Используется для представления подмножества некоторых данных data произвольного типа путем сохранения индексов наблюдений, охватываемых подмножеством. Кроме того, последующие подмножества накапливаются без необходимости доступа к фактическим данным.

Главное назначение типа ObsView — отложить доступ к данным и их перемещение до тех пор, пока пакет данных (или отдельное наблюдение) потребуется для каких-либо вычислений. Это особенно полезно в случае, когда данные находятся не в памяти, а на жестком диске или в удаленном расположении. В такой ситуации желательно загружать требуемые данные только по мере необходимости.

Доступ к данным не производится, пока не будет вызвана функция getindex, и даже функция getindex возвращает результат obsview, что в общем случае позволяет избежать перемещения данных до вызова getobs. При использовании в качестве итератора представление выполнит итерацию по набору данных один раз, что соответствует одной эпохе. При каждой итерации возвращается отложенное подмножество текущего наблюдения.

Аргументы

  • data: объект, описывающий набор данных. Может иметь любой тип, реализующий методы getobs и numobs (дополнительные сведения см. в разделе «Подробные сведения»).

  • indices: необязательно. Индекс или индексы наблюдений в data, которые должно представлять подмножество. Может иметь тип Int или некоторый подтип типа AbstractVector.

Методы

  • getindex: возвращает наблюдения, соответствующие данному индексу или индексам. Никакие данные, кроме требуемых индексов, не копируются.

  • numobs: возвращает общее количество наблюдений в подмножестве.

  • getobs: возвращает базовые данные, которые представляет объект ObsView по указанным относительным индексам. Обратите внимание, что эти индексы находятся в «пространстве подмножества» и в общем случае не соответствуют напрямую индексам в базовом наборе данных.

Подробные сведения

Для применения ObsView к какой-либо структуре данных требуемый тип MyType должен реализовывать следующий интерфейс:

  • getobs(data::MyType, idx): должен возвращать наблюдения, индексируемые по idx. Форму определяет пользователь. Имейте в виду, что idx может иметь тип Int или AbstractVector.

  • numobs(data::MyType): должен возвращать общее количество наблюдений в data.

Можно также предоставить следующие необязательные методы.

  • getobs(data::MyType): по умолчанию это функция тождественности. Если для вашего типа требуется другое поведение, следует также предоставить этот метод.

  • obsview(data::MyType, idx): если у вашего пользовательского типа собственный тип подмножества, его можно вернуть здесь. Примером может служить SubArray для представления подмножества некоторого массива AbstractArray.

  • getobs!(buffer, data::MyType, [idx]): версия getobs(data, idx), выполняемая на месте. Если этот метод предоставлен для MyType, то eachobs может предварительно размещать в памяти буфер, который будет использоваться повторно при каждой итерации. Обратите внимание: буфер buffer должен быть эквивалентен возвращаемому значению getobs(::MyType, ...), так как именно таким образом по умолчанию буфер buffer предварительно выделяется в памяти.

Примеры

X, Y = MLUtils.load_iris()

# Набор Iris содержит 150 наблюдений и 4 признака
@assert size(X) == (4,150)

# Представляет 80 наблюдений как ObsView
v = ObsView(X, 21:100)
@assert numobs(v) == 80
@assert typeof(v) <: ObsView
# индексы getobs в v
@assert getobs(v, 1:10) == X[:, 21:30]

# Используем `obsview` во избежание упаковки в ObsView
# для типов, предоставляющих пользовательское «подмножество», например массивов.
# В данном случае вместо этого создается собственный SubArray.
v = obsview(X, 1:100)
@assert numobs(v) == 100
@assert typeof(v) <: SubArray

# Также применимо для кортежей произвольной длины
subset = obsview((X, Y), 1:100)
@assert numobs(subset) == 100
@assert typeof(subset) <: Tuple # кортеж SubArray

# Используем в качестве итератора
for x in ObsView(X)
    @assert typeof(x) <: SubArray{Float64,1}
end

# перебираем каждое отдельное маркированное наблюдение
for (x, y) in ObsView((X, Y))
    @assert typeof(x) <: SubArray{Float64,1}
    @assert typeof(y) <: String
end

# то же самое, но в случайном порядке
for (x, y) in ObsView(shuffleobs((X, Y)))
    @assert typeof(x) <: SubArray{Float64,1}
    @assert typeof(y) <: String
end

# Индексирование: берем первые 10 наблюдений
x, y = ObsView((X, Y))[1:10]

См. также описание

# MLUtils.ones_likeFunction

ones_like(x, [element_type=eltype(x)], [dims=size(x)]))

Создает массив с заданным типом элементов и размером на основе исходного массива x. Всем элементам нового массива присваивается значение 1. Второй и третий аргументы являются необязательными, и по умолчанию их значениями являются тип элементов и размер исходного массива. Измерения могут быть указаны как целое число либо как кортеж.

См. также описание zeros_like и fill_like.

Примеры

julia> x = rand(Float32, 2)
2-element Vector{Float32}:
 0.8621633
 0.5158395

julia> ones_like(x, (3, 3))
3×3 Matrix{Float32}:
 1.0  1.0  1.0
 1.0  1.0  1.0
 1.0  1.0  1.0

julia> using CUDA

julia> x = CUDA.rand(2, 2)
2×2 CuArray{Float32, 2, CUDA.Mem.DeviceBuffer}:
 0.82297   0.656143
 0.701828  0.391335

julia> ones_like(x, Float64)
2×2 CuArray{Float64, 2, CUDA.Mem.DeviceBuffer}:
 1.0  1.0
 1.0  1.0

# MLUtils.oversampleFunction

oversample(data, classes; fraction=1, shuffle=true)
oversample(data::Tuple; fraction=1, shuffle=true)

Создает перебалансированную версию data путем многократной выборки существующих наблюдений таким образом, что число наблюдений в каждом классе будет равно по крайней мере fraction от их числа в самом большом классе в classes. Таким образом, во всех классах будет минимальное количество наблюдений в итоговом наборе данных относительно их количества в самом большом классе в (исходном) наборе data.

Например, по умолчанию (то есть при fraction = 1) итоговый набор данных будет сбалансирован почти идеально. В свою очередь, при fraction = 0.5 каждый класс в итоговых данных будет содержать по крайней мере 50 % от количества наблюдений в самом большом классе.

Входной объект classes — это массив той же длины, что и numobs(data).

Вспомогательный параметр shuffle определяет то, будут ли итоговые данные перемешиваться после создания. Если нет, все повторяющиеся образцы будут находиться рядом в конце с сортировкой по классам. Значение по умолчанию — true.

Результат будет содержать как данные с повторной выборкой, так и классы.

# 6 наблюдений с 3 признаками каждое
X = rand(3, 6)
# 2 сильно неуравновешенных класса
Y = ["a", "b", "b", "b", "b", "a"]

# выполняем перевыборку класса a так, чтобы он соответствовал b
X_bal, Y_bal = oversample(X, Y)

# в результате получается набор данных большего размера с повторяющимися данными
@assert size(X_bal) == (3,8)
@assert length(Y_bal) == 8

# теперь и в a, и в b по 4 наблюдения
@assert sum(Y_bal .== "a") == 4
@assert sum(Y_bal .== "b") == 4

Для работы этой функции тип data должен реализовывать методы numobs и getobs.

Обратите внимание, что если data — кортеж, а аргумент classes не задан, предполагается, что последний элемент кортежа содержит классы.

julia> data = DataFrame(X1=rand(6), X2=rand(6), Y=[:a,:b,:b,:b,:b,:a])
6×3 DataFrames.DataFrame
│ Row │ X1        │ X2          │ Y │
├─────┼───────────┼─────────────┼───┤
│ 1   │ 0.226582  │ 0.0443222   │ a │
│ 2   │ 0.504629  │ 0.722906    │ b │
│ 3   │ 0.933372  │ 0.812814    │ b │
│ 4   │ 0.522172  │ 0.245457    │ b │
│ 5   │ 0.505208  │ 0.11202     │ b │
│ 6   │ 0.0997825 │ 0.000341996 │ a │

julia> getobs(oversample(data, data.Y))
8×3 DataFrame
 Row │ X1        X2         Y
     │ Float64   Float64    Symbol
─────┼─────────────────────────────
   1 │ 0.376304  0.100022   a
   2 │ 0.467095  0.185437   b
   3 │ 0.481957  0.319906   b
   4 │ 0.336762  0.390811   b
   5 │ 0.376304  0.100022   a
   6 │ 0.427064  0.0648339  a
   7 │ 0.427064  0.0648339  a
   8 │ 0.457043  0.490688   b

Дополнительные сведения о подмножествах данных см. в описании ObsView. См. также описание undersample.

# MLUtils.randobsFunction

randobs(data, [n])

Выбирает случайное наблюдение или пакет из n случайных наблюдений из data. Для работы этой функции тип data должен реализовывать методы numobs и getobs.

# MLUtils.rand_likeFunction

rand_like([rng=default_rng()], x, [element_type=eltype(x)], [dims=size(x)])

Создает массив с заданным типом элементов и размером на основе исходного массива x. Всем элементам нового массива присваивается случайное значение. Последние два аргумента являются необязательными, и по умолчанию их значениями являются тип элементов и размер исходного массива. Измерения могут быть указаны как целое число либо как кортеж.

Используется генератор случайных чисел по умолчанию, если в первом аргументе генератор не передан явным образом.

См. также описание Base.rand и randn_like.

Примеры

julia> x = ones(Float32, 2)
2-element Vector{Float32}:
 1.0
 1.0

julia> rand_like(x, (3, 3))
3×3 Matrix{Float32}:
 0.780032  0.920552  0.53689
 0.121451  0.741334  0.5449
 0.55348   0.138136  0.556404

julia> using CUDA

julia> CUDA.ones(2, 2)
2×2 CuArray{Float32, 2, CUDA.Mem.DeviceBuffer}:
 1.0  1.0
 1.0  1.0

julia> rand_like(x, Float64)
2×2 CuArray{Float64, 2, CUDA.Mem.DeviceBuffer}:
 0.429274  0.135379
 0.718895  0.0098756

# MLUtils.randn_likeFunction

randn_like([rng=default_rng()], x, [element_type=eltype(x)], [dims=size(x)])

Создает массив с заданным типом элементов и размером на основе исходного массива x. Всем элементам нового массива присваивается случайное значение из нормального распределения. Последние два аргумента являются необязательными, и по умолчанию их значениями являются тип элементов и размер исходного массива. Измерения могут быть указаны как целое число либо как кортеж.

Используется генератор случайных чисел по умолчанию, если в первом аргументе генератор не передан явным образом.

См. также описание Base.randn и rand_like.

Примеры

julia> x = ones(Float32, 2)
2-element Vector{Float32}:
 1.0
 1.0

julia> randn_like(x, (3, 3))
3×3 Matrix{Float32}:
 -0.385331    0.956231   0.0745102
  1.43756    -0.967328   2.06311
  0.0482372   1.78728   -0.902547

julia> using CUDA

julia> CUDA.ones(2, 2)
2×2 CuArray{Float32, 2, CUDA.Mem.DeviceBuffer}:
 1.0  1.0
 1.0  1.0

julia> randn_like(x, Float64)
2×2 CuArray{Float64, 2, CUDA.Mem.DeviceBuffer}:
 -0.578527   0.823445
 -1.01338   -0.612053

# MLUtils.rpad_constantFunction

rpad_constant(v::AbstractArray, n::Union{Integer, Tuple}, val = 0; dims=:)

Возвращает заданную последовательность, дополненную значением val по измерениям dims до максимальной длины в каждом направлении, равной n.

Примеры

julia> rpad_constant([1, 2], 4, -1) # передача со значением –1 до размера 4
4-element Vector{Int64}:
 1
 2
 -1
 -1

julia> rpad_constant([1, 2, 3], 2) # если длина уже больше n, дополнение не производится
3-element Vector{Int64}:
 1
 2
 3

julia> rpad_constant([1 2; 3 4], 4; dims=1) # дополнение по первому измерению
4×2 Matrix{Int64}:
 1  2
 3  4
 0  0
 0  0

julia> rpad_constant([1 2; 3 4], 4) # дополнение по умолчанию по всем измерениям
4×2 Matrix{Int64}:
 1  2
 3  4
 0  0
 0  0

# MLUtils.shuffleobsFunction

shuffleobs([rng], data)

Возвращает «подмножество» данных data, включающее все наблюдения, но в перемешанном порядке.

Сами значения data не копируются. Перемешиваются только индексы. С этой целью данная функция вызывает obsview, поэтому возвращаемое значение, скорее всего, будет иметь тип, отличный от типа data.

# Для типа Array подмножество будет типа SubArray
@assert typeof(shuffleobs(rand(4,10))) <: SubArray

# Перебираем все наблюдения в случайном порядке
for x in eachobs(shuffleobs(X))
    ...
end

Необязательный параметр rng позволяет указать генератор случайных чисел для перемешивания. Это полезно, когда желательно получить воспроизводимые результаты. По умолчанию используется глобальный генератор случайных чисел. Дополнительные сведения см. в описании модуля Random из стандартной библиотеки Julia.

Для работы этой функции тип data должен реализовывать методы numobs и getobs. Дополнительные сведения см. в описании ObsView .

# MLUtils.splitobsFunction

splitobs(n::Int; at) -> Tuple

Вычисляет индексы для двух или нескольких непересекающихся подмножеств диапазона 1:n, разделенного по at.

Примеры

julia> splitobs(100, at=0.7)
(1:70, 71:100)

julia> splitobs(100, at=(0.1, 0.4))
(1:10, 11:50, 51:100)
splitobs(data; at, shuffle=false) -> Tuple

Разделяет data на два или несколько подмножеств. Если аргумент at — число (от 0 до 1), оно определяет долю, которая приходится на первое подмножество. Если аргумент at — кортеж, каждый его элемент определяет долю, приходящуюся на соответствующее подмножество, с долей последнего подмножества 1-sum(at). Всего возвращается length(at)+1 подмножеств.

Если shuffle=true, перед разделением наблюдения перестанавливаются случайным образом.

Поддерживает любой тип данных, реализующий интерфейсы numobs и getobs, включая массивы, кортежи и именованные кортежи массивов.

Примеры

julia> splitobs(permutedims(1:100); at=0.7)  # простое разделение матрицы на доли размером 70 и 30 %
([1 2 … 69 70], [71 72 … 99 100])

julia> data = (x=ones(2,10), n=1:10)  # именованный кортеж, согласованное последнее измерение
(x = [1.0 1.0 … 1.0 1.0; 1.0 1.0 … 1.0 1.0], n = 1:10)

julia> splitobs(data, at=(0.5, 0.3))  # разделение на 50, 30 и 20 %, например для обучающего, проверочного и валидационного наборов
((x = [1.0 1.0 … 1.0 1.0; 1.0 1.0 … 1.0 1.0], n = 1:5), (x = [1.0 1.0 1.0; 1.0 1.0 1.0], n = 6:8), (x = [1.0 1.0; 1.0 1.0], n = 9:10))

julia> train, test = splitobs((permutedims(1.0:100.0), 101:200), at=0.7, shuffle=true);  # разделение кортежа

julia> vec(test[1]) .+ 100 == test[2]
true

# MLUtils.unbatchFunction

unbatch(x)

Операция, обратная batch. Разбирает последнее измерение массива x.

См. также описание unstack и chunk.

Примеры

julia> unbatch([1 3 5 7;
                2 4 6 8])
4-element Vector{Vector{Int64}}:
 [1, 2]
 [3, 4]
 [5, 6]
 [7, 8]

# MLUtils.undersampleFunction

undersample(data, classes; shuffle=true)

Создает сбалансированную по классам версию данных data путем подвыборки их наблюдений таким образом, что итоговое количество наблюдений будет одинаковым для каждого класса. Таким образом, количество наблюдений в каждом классе итогового набора данных будет равно их количеству в самом маленьком классе в (исходном) наборе data.

Вспомогательный параметр shuffle определяет то, будут ли итоговые данные перемешиваться после создания. Если нет, все наблюдения будут располагаться в исходном порядке. Значение по умолчанию — false.

Результат будет содержать как данные с повторной выборкой, так и классы.

# 6 наблюдений с 3 признаками каждое
X = rand(3, 6)
# 2 сильно неуравновешенных класса
Y = ["a", "b", "b", "b", "b", "a"]

# выполняет подвыборку класса a так, чтобы он соответствовал b
X_bal, Y_bal = undersample(X, Y)

# результаты в наборе данных меньшего размера
@assert size(X_bal) == (3,4)
@assert length(Y_bal) == 4

# теперь и в a, и в b по 2 наблюдения
@assert sum(Y_bal .== "a") == 2
@assert sum(Y_bal .== "b") == 2

Для работы этой функции тип data должен реализовывать методы numobs и getobs.

Обратите внимание, что если data — кортеж, предполагается, что его последний элемент содержит цели.

julia> data = DataFrame(X1=rand(6), X2=rand(6), Y=[:a,:b,:b,:b,:b,:a])
6×3 DataFrames.DataFrame
│ Row │ X1        │ X2          │ Y │
├─────┼───────────┼─────────────┼───┤
│ 1   │ 0.226582  │ 0.0443222   │ a │
│ 2   │ 0.504629  │ 0.722906    │ b │
│ 3   │ 0.933372  │ 0.812814    │ b │
│ 4   │ 0.522172  │ 0.245457    │ b │
│ 5   │ 0.505208  │ 0.11202     │ b │
│ 6   │ 0.0997825 │ 0.000341996 │ a │

julia> getobs(undersample(data, data.Y))
4×3 DataFrame
 Row │ X1        X2         Y
     │ Float64   Float64    Symbol
─────┼─────────────────────────────
   1 │ 0.427064  0.0648339  a
   2 │ 0.376304  0.100022   a
   3 │ 0.467095  0.185437   b
   4 │ 0.457043  0.490688   b

Дополнительные сведения о подмножествах данных см. в описании ObsView. См. также описание oversample.

# MLUtils.unsqueezeFunction

unsqueeze(x; dims)

Возвращает объект x, преобразованный в массив на одну размерность выше, чем x, где dims указывает измерение, по которому расширяется x. Значением dims может быть целое число от 1 до ndims(x)+1.

См. также описание flatten и stack.

Примеры

julia> unsqueeze([1 2; 3 4], dims=2)
2×1×2 Array{Int64, 3}:
[:, :, 1] =
 1
 3

[:, :, 2] =
 2
 4


julia> xs = [[1, 2], [3, 4], [5, 6]]
3-element Vector{Vector{Int64}}:
 [1, 2]
 [3, 4]
 [5, 6]

julia> unsqueeze(xs, dims=1)
1×3 Matrix{Vector{Int64}}:
 [1, 2]  [3, 4]  [5, 6]
unsqueeze(; dims)

Возвращает функцию, которая при применении к массиву вставляет измерение размером 1 в dims.

Примеры

julia> rand(21, 22, 23) |> unsqueeze(dims=2) |> size
(21, 1, 22, 23)

# MLUtils.unstackFunction

unstack(xs; dims)

Развертывает объект xs в массив массивов по указанному измерению dims.

См. также описание stack, unbatch и chunk.

Примеры

julia> unstack([1 3 5 7; 2 4 6 8], dims=2)
4-element Vector{Vector{Int64}}:
 [1, 2]
 [3, 4]
 [5, 6]
 [7, 8]

# MLUtils.zeros_likeFunction

zeros_like(x, [element_type=eltype(x)], [dims=size(x)]))

Создает массив с заданным типом элементов и размером на основе исходного массива x. Всем элементам нового массива присваивается значение 0. Второй и третий аргументы являются необязательными, и по умолчанию их значениями являются тип элементов и размер исходного массива. Измерения могут быть указаны как целое число либо как кортеж.

См. также описание ones_like и fill_like.

Примеры

julia> x = rand(Float32, 2)
2-element Vector{Float32}:
 0.4005432
 0.36934233

julia> zeros_like(x, (3, 3))
3×3 Matrix{Float32}:
 0.0  0.0  0.0
 0.0  0.0  0.0
 0.0  0.0  0.0

julia> using CUDA

julia> x = CUDA.rand(2, 2)
2×2 CuArray{Float32, 2, CUDA.Mem.DeviceBuffer}:
 0.0695155  0.667979
 0.558468   0.59903

julia> zeros_like(x, Float64)
2×2 CuArray{Float64, 2, CUDA.Mem.DeviceBuffer}:
 0.0  0.0
 0.0  0.0