Рассмотрим следующий массив:
julia> test_mat3 = rand(2, 3, 2)
2×3×2 Array{Float64, 3}:
[:, :, 1] =
0.539697 0.17689 0.944783
0.537687 0.660573 0.19497
[:, :, 2] =
0.726739 0.798789 0.0900478
0.00744727 0.837076 0.25798
Я хочу преобразовать его в 2d-массив с определенным порядком. Во-первых, я хочу объединить каждую строку каждой внутренней матрицы (здесь с формой 2x3). Итак, для первого шага я ожидаю несколько внутренних матриц 1x6. Во-вторых, я хочу объединить эти матрицы по вертикали. Ожидаемый результат на test_mat3
будет:
2×6 Array{Float64, 2}:
0.539697 0.17689 0.944783 0.537687 0.660573 0.19497
0.726739 0.798789 0.0900478 0.00744727 0.837076 0.25798
Однако я написал этот код:
using SplitApplyCombine
function flatten_3d(arr::Array{Float64, 3})
mat = zeros(
Float64,
size(arr, 3),
size(arr, 1)*size(arr, 2)
)
for (idx, arr)=enumerate(splitdimsview(arr, 3))
the_vec = vcat(eachrow(arr)...)
mat[idx, :] = the_vec
end
return mat
end
Скрипт выполняет свою работу:
julia> flatten_3d(test_mat3)
2×6 Matrix{Float64}:
0.539697 0.17689 0.944783 0.537687 0.660573 0.19497
0.726739 0.798789 0.0900478 0.00744727 0.837076 0.25798
Но мне интересно, есть ли лучший способ сделать это? Любая встроенная функция и т.д.
Следующий код должен помочь:
using Combinatorics
test_mat3 = zeros(2, 3, 2);
test_mat3[:, :, 1] =
[0.539697 0.17689 0.944783 ;
0.537687 0.660573 0.19497 ]
test_mat3[:, :, 2] =
[ 0.726739 0.798789 0.0900478;
0.00744727 0.837076 0.25798]
for p in permutations(1:3)
A = reshape(permutedims(test_mat3,p),(2,6))
@show p
display(A)
end
Дает:
p = [1, 2, 3]
2×6 Matrix{Float64}:
0.539697 0.17689 0.944783 0.726739 0.798789 0.0900478
0.537687 0.660573 0.19497 0.00744727 0.837076 0.25798
p = [1, 3, 2]
2×6 Matrix{Float64}:
0.539697 0.726739 0.17689 0.798789 0.944783 0.0900478
0.537687 0.00744727 0.660573 0.837076 0.19497 0.25798
p = [2, 1, 3]
2×6 Matrix{Float64}:
0.539697 0.944783 0.660573 0.726739 0.0900478 0.837076
0.17689 0.537687 0.19497 0.798789 0.00744727 0.25798
p = [2, 3, 1]
2×6 Matrix{Float64}:
0.539697 0.944783 0.798789 0.537687 0.19497 0.837076
0.17689 0.726739 0.0900478 0.660573 0.00744727 0.25798
p = [3, 1, 2]
2×6 Matrix{Float64}:
0.539697 0.537687 0.17689 0.660573 0.944783 0.19497
0.726739 0.00744727 0.798789 0.837076 0.0900478 0.25798
p = [3, 2, 1]
2×6 Matrix{Float64}:
0.539697 0.17689 0.944783 0.537687 0.660573 0.19497
0.726739 0.798789 0.0900478 0.00744727 0.837076 0.25798
Итак, требуемая перестановка:
julia> reshape(permutedims(test_mat3, (3,2,1)), (2,6))
2×6 Matrix{Float64}:
0.539697 0.17689 0.944783 0.537687 0.660573 0.19497
0.726739 0.798789 0.0900478 0.00744727 0.837076 0.25798
ПРИЛОЖЕНИЕ: Почему (3,2,1)?
permutedim
использует перестановку, чтобы превратить исходную ось № 3 в ось № 1 в новом массиве (и в целом (3,2,1)
означает 3 => 1, 2 => 2, 1 => 3).
Юлия считает столбцы первой осью (строки — второй). Что, кстати, является причиной того, что всегда предпочтительнее читать/записывать элементы столбца последовательно для быстрого доступа к памяти.
В OP 0.5396
и 0.7267
— это первый выходной столбец, но на входе они находятся в одной строке/столбце, но в разных плоскостях (третья ось). Таким образом, мы переставляем ось 3 => 1.
В OP 0.5396
и 0.1768
— это значения первой выходной строки, но на входе они находятся в одной строке/плоскости, но в разных столбцах (вторая ось). После reshape
объединяются вторая и третья оси, опять же, более ранняя ось меняется быстрее. Мы хотим сначала перейти от 0.53
к 0.17
, а не к 0.5376
(перемещаясь по первой входной оси), поэтому мы переставляем ось 2=>2.
Третья ось завершает перестановку, и это объяснение легче понять, следуя числам в примере всех перестановок выше, чем читая ;)
@Shayan извините, слишком рано сравнивать списки чисел. Исправлен ответ (по этой причине я в любом случае изложил все перестановки в ответе).
Ничего страшного! Спасибо. Не могли бы вы указать причину (3, 2, 1)
?
Я сделал пакет для выяснения таких вещей. Этот выглядит так:
julia> using TensorCast
julia> @cast mat[k,(j,i)] := test_mat3[i,j,k];
julia> mat == flatten_3d(test_mat3)
true
Чтобы прочитать это:
k
.i, j
. Транспонирование будет j, i
, а объединение их в одно измерение с vec
будет (i,j)
, в котором i
изменяется быстрее всего (поскольку массивы являются столбцами).(j,i)
это j
изменяется быстрее всего, например, транспонировать, а затем век. Это помещается во 2-й тусклый выход.То, что он производит (в данном случае), это просто permutedims
, а затем reshape
. Один из способов его использования - это просто способ выяснить правильную перестановку и т.д.:
julia> @pretty @cast mat[k,(j,i)] := test_mat3[i,j,k]
begin
@boundscheck ndims(test_mat3) == 3 || throw(ArgumentError("expected a 3-tensor test_mat3[i, j, k]"))
local (ax_i, ax_j, ax_k) = (axes(test_mat3, 1), axes(test_mat3, 2), axes(test_mat3, 3))
local elephant = transmute(test_mat3, Val((3, 2, 1)))
mat = reshape(elephant, (ax_k, star(ax_j, ax_i)))
end
На самом деле это больше похоже на PermutedDimsArray
плюс reshape
: по умолчанию это возвращает представление ввода, когда это возможно, а не копию. Это очень быстро, но следующая операция может быть или не быть быстрой. Чтобы вместо этого сделать Matrix
, он заимствует лучший алгоритм перестановок из TensorOperations.jl, который быстрее работает на больших массивах:
julia> flatten_lazy(x) = @cast _[k,(j,i)] := x[i,j,k];
julia> flatten_eager(x) = @cast _[k,(j,i)] |= x[i,j,k];
julia> using BenchmarkTools
julia> let test_mat = rand(20,30,200)
a = @btime flatten_3d($test_mat)
b = @btime reshape(permutedims($test_mat, (3,2,1)), (200, 600))
c = @btime flatten_lazy($test_mat)
d = @btime flatten_eager($test_mat)
a == b == c == d
end
956.708 μs (17002 allocations: 3.17 MiB)
143.875 μs (3 allocations: 937.62 KiB)
63.520 ns (0 allocations: 0 bytes) # note the units, no time at all!
53.291 μs (63 allocations: 942.45 KiB)
true
Документация пакета находится здесь: https://mcabbott.github.io/TensorCast.jl/dev/basics/
Крутой пакет! (github.com/mcabbott/TensorCast.jl)
Спасибо! Я устал от комментариев, объясняющих себе в будущем, почему это было (3,1,2)
здесь, а не (2,3,1)
и т. д. Я не уверен, что это сэкономило время в целом ...
Спасибо. Этот пакет великолепен. Со дня этого ответа я читаю документ. Это будет вишенкой на торте, если вы улучшите его с помощью различных примеров.
reshape(permutedims(test_mat3, (3,1,2)), (2,6))
не дает ожидаемого результата. Пожалуйста, обратите внимание на конкретный порядок, который я объяснил.