Ошибка: "DimensionMismatch(" матрица A имеет размеры (1024,10), вектор B имеет длину 9")" с использованием Flux в Julia

Я все еще новичок в Джулии и в машинном обучении в целом, но я очень хочу учиться. В текущем проекте, над которым я работаю, у меня проблема с несовпадением размеров, и я не могу понять, что делать.

У меня есть два следующих массива:

x_array: 
9-element Array{Array{Int64,N} where N,1}:
 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 72, 73]
 [11, 12, 13, 14, 15, 16, 17, 72, 73]
 [18, 12, 19, 20, 21, 22, 72, 74]
 [23, 24, 12, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 72, 74]
 [36, 37, 38, 39, 40, 38, 41, 42, 72, 73]
 [43, 44, 45, 46, 47, 48, 72, 74]
 [49, 50, 51, 52, 14, 53, 72, 74]
 [54, 55, 41, 56, 57, 58, 59, 60, 61, 62, 63, 62, 64, 72, 74]
 [65, 66, 67, 68, 32, 69, 70, 71, 72, 74]


y_array:
9-element Array{Int64,1}
 75
 76
 77
 78
 79
 80
 81
 82
 83

и следующая модель с использованием Flux:

model = Chain(
    LSTM(10, 256),
    LSTM(256, 128),
    LSTM(128, 128),
    Dense(128, 9),
    softmax
)

Я заархивирую оба массива, а затем загружаю их в модель с помощью Flux.train!

data = zip(x_array, y_array)
Flux.train!(loss, Flux.params(model), data, opt)

и сразу выдает следующую ошибку:

ERROR: DimensionMismatch("matrix A has dimensions (1024,10), vector B has length 9")

Теперь я знаю, что первое измерение матрицы A - это сумма скрытых слоев (256 + 256 + 128 + 128 + 128 + 128), а второе измерение - это входной слой, который равен 10. Первое, что я сделал, это измените 10 на 9, но тогда это только выдает ошибку:

ERROR: DimensionMismatch("dimensions must match")

Может ли кто-нибудь объяснить мне, какие размеры не совпадают и как их совместить?

1 ответ

Решение

Введение

Во-первых, вы должны знать, что с архитектурной точки зрения вы запрашиваете у своей сети нечто очень сложное; softmax повторно нормализует выходы, чтобы они находились между 0 а также 1 (взвешенный как распределение вероятностей), что означает, что запрос вашей сети вывести такие значения, как 77 соответствовать yбудет невозможно. Это не то, что вызывает несоответствие размеров, но это то, о чем следует знать. Я собираюсь броситьsoftmax() в конце, чтобы дать сети шанс побороться, тем более что проблема не в этом.

Отладка несоответствия форм

Давайте рассмотрим, что на самом деле происходит внутри Flux.train!(). Определение на самом деле удивительно простое. Игнорируя все, что для нас не важно, мы получаем:

for d in data
    gs = gradient(ps) do
        loss(d...)
    end
end

Поэтому начнем с того, что вытащим первый элемент из вашего data, и бросить его в lossфункция. Вы не указали свою функцию потерь или оптимизатор в вопросе. Несмотря на то чтоsoftmax обычно означает, что вам следует использовать crossentropy потеря, твой y значения во многом не являются вероятностями, поэтому, если мы отбросим softmax мы можем просто использовать мертвенно-простой mse()потеря. Для оптимизатора мы по умолчанию используем старый добрый ADAM:

model = Chain(
    LSTM(10, 256),
    LSTM(256, 128),
    LSTM(128, 128),
    Dense(128, 9),
    #softmax,        # commented out for now
)

loss(x, y) = Flux.mse(model(x), y)
opt = ADAM(0.001)
data = zip(x_array, y_array)

Теперь, чтобы смоделировать первый запуск Flux.train!(), мы принимаем first(data) и брось это в loss():

loss(first(data)...)

Это дает нам сообщение об ошибке, которое вы видели раньше; ERROR: DimensionMismatch("matrix A has dimensions (1024,10), vector B has length 12"). Глядя на наши данные, мы видим, что да, действительно, первый элемент нашего набора данных имеет длину 12. И поэтому мы изменим нашу модель, чтобы вместо 10 ожидать 12 значений:

model = Chain(
    LSTM(12, 256),
    LSTM(256, 128),
    LSTM(128, 128),
    Dense(128, 9),
)

А теперь перезапускаем:

julia> loss(first(data)...)
       50595.52542674723 (tracked)

Ура! Это сработало! Мы можем запустить это снова:

julia> loss(first(data)...)
        50578.01417593167 (tracked)

Значение изменяется, потому что RNN хранит в себе память, которая обновляется каждый раз, когда мы запускаем сеть, иначе мы ожидали бы, что сеть даст тот же ответ для тех же входов!

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

julia> loss([d for d in data][2]...)
ERROR: DimensionMismatch("matrix A has dimensions (1024,12), vector B has length 9")

Понимание LSTM

Здесь мы больше сталкиваемся с проблемами машинного обучения, чем с проблемами программирования; проблема в том, что мы обещали накормить этого первогоLSTM сеть вектор длины 10 (Что ж, 12сейчас), и мы нарушаем это обещание. Это общее правило глубокого обучения; вы всегда должны подчиняться подписываемым вами контрактам о форме тензоров, которые проходят через вашу модель.

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

При строительстве LSTMслой в Flux, поэтому вы объявляете не длину последовательности, которую вы будете вводить, а, скорее, размерность каждой временной точки; представьте, что у вас есть показания акселерометра длиной 1000 точек, которые дают вам значения X, Y, Z в каждый момент времени; чтобы прочитать это, вы должны создатьLSTM который принимает размерность 3, затем накорми его 1000 раз.

Написание собственного цикла обучения

Я считаю очень поучительным написать собственный цикл обучения и функцию выполнения модели, чтобы у нас был полный контроль над всем. Имея дело с временными рядами, часто легко запутаться в том, как вызывать LSTM, плотные слои и тому подобное, поэтому я предлагаю следующие простые практические правила:

  • При отображении одного временного ряда в другой (например, постоянное прогнозирование будущего движения на основе предыдущего движения) вы можете использовать один Chainи вызовите его в цикле; для каждой временной точки ввода вы выводите другую.

  • При отображении временного ряда на один "вывод" (например, сокращение предложения до "счастливого настроения" или "грустного настроения") вы должны сначала сжать все данные и уменьшить их до фиксированного размера; вы подкармливаете много вещей, но в конце выходит только одно.

Мы собираемся переделать нашу модель на две части; сначала повторяющаяся секция "pacman", где мы прерываем временную последовательность переменной длины во внутренний вектор состояния заранее определенной длины, затем секцию прямой связи, которая берет этот внутренний вектор состояния и сокращает его до единственного выхода:

pacman = Chain(
    LSTM(1, 128),    # map from timepoint size 1 to 128
    LSTM(128, 256),  # blow it up even larger to 256
    LSTM(256, 128),  # bottleneck back down to 128
)

reducer = Chain(
    Dense(128, 9),
    #softmax,        # keep this commented out for now
)

Причина, по которой мы разбили его на две части, как это, состоит в том, что в формулировке задачи требуется, чтобы мы уменьшили входную серию переменной длины до одного числа; мы находимся во втором пункте выше. Поэтому наш код, естественно, должен это учитывать; мы напишем нашloss(x, y) функция вместо вызова model(x), вместо этого он будет танцевать pacman, а затем вызовет редуктор на выходе. Обратите внимание, что мы также должныreset!() состояние RNN, так что внутреннее состояние очищается для каждого независимого обучающего примера:

function loss(x, y)
    # Reset internal RNN state so that it doesn't "carry over" from
    # the previous invocation of `loss()`.
    Flux.reset!(pacman)

    # Iterate over every timepoint in `x`
    for x_t in x
        y_hat = pacman(x_t)
    end

    # Take the very last output from the recurrent section, reduce it
    y_hat = reducer(y_hat)

    # Calculate reduced output difference against `y`
    return Flux.mse(y_hat, y)
end

Подавая это в Flux.train!()на самом деле тренируется, правда, не очень хорошо.;)

Заключительные наблюдения

  • Хотя твои данные все Int64s, довольно типично использовать числа с плавающей запятой со всем, кроме вложений (встраивание - это способ брать нечисловые данные, такие как символы или слова, и присваивать им числа, вроде как ASCII); если вы имеете дело с текстом, вы почти наверняка будете работать с каким-то встраиванием, и это вложение будет определять размерность вашего первого LSTM, после чего все ваши входные данные будут закодированы "горячо".

  • softmaxиспользуется, когда вы хотите предсказать вероятности; он будет гарантировать, что для каждого входа все выходы находятся между[0...1] и более того, они составляют 1.0, вроде должно хорошее распределение вероятностей. Это наиболее полезно при выполнении классификации, когда вы хотите разобраться в своих диких сетевых выходных значениях[-2, 5, 0.101] во что-то, где вы можете сказать "у нас есть 99.1% уверенность в правильности второго класса и 0.7% уверенность, это третий класс ".

  • При обучении этих сетей вам часто может потребоваться объединить сразу несколько временных рядов в сеть из соображений эффективности оборудования; это и просто, и сложно, потому что, с одной стороны, это просто означает, что вместо передачи одногоSx1 вектор через (где S размер вашего вложения) вместо этого вы собираетесь проходить через SxN матрица, но это также означает, что количество временных шагов всего в вашем пакете должно совпадать (потому что SxN должен оставаться неизменным на всех временных шагах, поэтому, если один временной ряд заканчивается раньше других в вашем пакете, вы не можете просто отбросить его и тем самым уменьшить Nв середине партии). Поэтому большинство людей делают все свои таймсерии одинаковой длины.

Удачи в вашем пути к ML!

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