Работа с данными с помощью MLUtils.jl
Flux повторно экспортирует тип DataLoader
и вспомогательные функции для работы с данными из MLUtils.
DataLoader
DataLoader
можно использовать для создания мини-пакетов данных в формате, принимаемом train!
.
На веб-сайте Flux
есть специальное руководство по DataLoader
, в котором можно найти дополнительные сведения.
#
MLUtils.DataLoader
— Type
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.batch
— Function
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.batchsize
— Function
batchsize(data::BatchView) -> Int
Возвращает фиксированный размер каждого пакета в data
.
Примеры
using MLUtils
X, Y = MLUtils.load_iris()
A = BatchView(X, batchsize=30)
@assert batchsize(A) == 30
#
MLUtils.batchseq
— Function
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.BatchView
— Type
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.chunk
— Function
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.eachobs
— Function
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_like
— Function
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.filterobs
— Function
filterobs(f, data)
Возвращает подмножество данных из контейнера data
, включая все индексы i
, для которых f(getobs(data, i)) === true
.
data = 1:10
numobs(data) == 10
fdata = filterobs(>(5), data)
numobs(fdata) == 5
#
MLUtils.getobs
— Function
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
по умолчанию поддерживает вложение массивов, кортежей, именованных кортежей и словарей в различных сочетаниях.
Примеры
# именованные кортежи
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!
является необязательной.
#
MLUtils.joinobs
— Function
joinobs(datas...)
Выполняет конкатенацию контейнеров данных datas
.
data1, data2 = 1:10, 11:20
jdata = joinumobs(data1, data2)
getobs(jdata, 15) == 15
#
MLUtils.group_counts
— Function
group_counts(x)
Подсчитывает, сколько раз встречается каждый элемент x
.
См. также описание group_indices
.
Примеры
julia> group_counts(['a', 'b', 'b'])
Dict{Char, Int64} with 2 entries:
'a' => 1
'b' => 2
#
MLUtils.group_indices
— Function
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.groupobs
— Function
groupobs(f, data)
Разделяет контейнер данных data
на отдельные контейнеры данных, группируя наблюдения по f(obs)
.
data = -10:10
datas = groupobs(>(0), data)
length(datas) == 2
#
MLUtils.kfolds
— Function
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.leavepout
— Function
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.mapobs
— Function
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.numobs
— Function
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.normalise
— Function
normalise(x; dims=ndims(x), ϵ=1e-5)
Нормализует массив x
к среднему значению 0 и среднеквадратичному отклонению 1 по измерениям, указанным в dims
. По умолчанию dims
— это последнее измерение.
ϵ
— это небольшой дополнительный фактор, который добавляется в знаменатель для численной устойчивости.
#
MLUtils.obsview
— Function
obsview(data, [indices])
Возвращает отложенное представление наблюдений в data
, соответствующих индексам indices
. Никакие данные, кроме индексов, не копируются. Эта функция действует аналогично созданию ObsView
, но возвращает SubArray
, если типом data
является Array
или SubArray
. Кроме того, эту функцию можно расширить для пользовательских типов data
, которые также должны предоставлять собственный тип подмножества.
Если data
— кортеж, конструктор будет сопоставляться по его элементам. Это означает, что конструктор возвращает кортеж ObsView
, а не ObsView
кортежей.
Если вместо этого необходимо получить подмножество наблюдений, соответствующих индексам indices
в исходном типе, используйте getobs
.
Дополнительные сведения см. в описании ObsView
.
#
MLUtils.ObsView
— Type
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_like
— Function
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.oversample
— Function
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
— кортеж, а аргумент 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.rand_like
— Function
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_like
— Function
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_constant
— Function
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.shuffleobs
— Function
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.
#
MLUtils.splitobs
— Function
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.undersample
— Function
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
— кортеж, предполагается, что его последний элемент содержит цели.
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.unsqueeze
— Function
unsqueeze(x; dims)
Возвращает объект x
, преобразованный в массив на одну размерность выше, чем x
, где dims
указывает измерение, по которому расширяется x
. Значением dims
может быть целое число от 1 до ndims(x)+1
.
Примеры
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.zeros_like
— Function
zeros_like(x, [element_type=eltype(x)], [dims=size(x)]))
Создает массив с заданным типом элементов и размером на основе исходного массива x
. Всем элементам нового массива присваивается значение 0. Второй и третий аргументы являются необязательными, и по умолчанию их значениями являются тип элементов и размер исходного массива. Измерения могут быть указаны как целое число либо как кортеж.
Примеры
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