Каков наиболее эффективный способ преобразовать 3d-массив в 2d с определенным порядком в julia?

Рассмотрим следующий массив:

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

Но мне интересно, есть ли лучший способ сделать это? Любая встроенная функция и т.д.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
83
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Следующий код должен помочь:

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.

Третья ось завершает перестановку, и это объяснение легче понять, следуя числам в примере всех перестановок выше, чем читая ;)

reshape(permutedims(test_mat3, (3,1,2)), (2,6)) не дает ожидаемого результата. Пожалуйста, обратите внимание на конкретный порядок, который я объяснил.
Shayan 14.11.2022 11:31

@Shayan извините, слишком рано сравнивать списки чисел. Исправлен ответ (по этой причине я в любом случае изложил все перестановки в ответе).

Dan Getz 14.11.2022 11:35

Ничего страшного! Спасибо. Не могли бы вы указать причину (3, 2, 1)?

Shayan 14.11.2022 11:44
Ответ принят как подходящий

Я сделал пакет для выяснения таких вещей. Этот выглядит так:

julia> using TensorCast

julia> @cast mat[k,(j,i)] := test_mat3[i,j,k];

julia> mat == flatten_3d(test_mat3)
true

Чтобы прочитать это:

  • 3-е измерение ввода становится 1-м измерением вывода. У него временное название 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)

Dan Getz 14.11.2022 12:16

Спасибо! Я устал от комментариев, объясняющих себе в будущем, почему это было (3,1,2) здесь, а не (2,3,1) и т. д. Я не уверен, что это сэкономило время в целом ...

mcabbott 14.11.2022 18:01

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

Shayan 23.11.2022 11:12

Другие вопросы по теме