Error: "DimensionMismatch("matrix A has dimensions (1024,10), vector B has length 9")" using Flux in Julia

Error: "DimensionMismatch("matrix A has dimensions (1024,10), vector B has length 9")" using Flux in Julia

我在 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")

谁能给我解释一下不匹配的维度是什么,以及如何使它们匹配?

简介

首先,您应该知道,从架构的角度来看,您正在向您的网络提出一些非常困难的问题; softmax 将输出重新归一化到 01 之间(像概率分布一样加权),这意味着要求您的网络输出像 77 这样的值来匹配 y 将是不可能的。这不是导致尺寸不匹配的原因,但需要注意。我将在最后删除 softmax() 以给网络一个战斗的机会,特别是因为它不是导致问题的原因。

调试形状不匹配

让我们来看看 Flux.train!() 内部实际发生的事情。 The definition 实际上非常简单。忽略对我们来说无关紧要的一切,我们只剩下:

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。因此我们将更改我们的模型以期望 12 个值而不是 10:

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

现在我们重新[​​=136=]:

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 这样的循环架构的美妙之处在于它们能够将信息从一个执行传递到另一个执行,因此它们能够在一个又一个时间点应用时建立序列的内部表示。

因此,在 Flux 中构建 LSTM 层时,您声明的不是要输入的序列的长度,而是每个时间点的维数;想象一下,如果您有一个 1000 点长的加速度计读数,并在每个时间点为您提供 X、Y、Z 值;要读入它,您将创建一个 LSTM,它接受 3 的维度,然后将其输入 1000 次。

编写我们自己的训练循环

我发现编写自己的训练循环和模型执行函数非常有指导意义,这样我们就可以完全控制一切。在处理时间序列时,通常很容易混淆如何调用 LSTM 和 Dense 层等等,所以我提供这些简单的经验法则:

  • 当从一个时间序列映射到另一个时间序列时(例如,不断地从以前的运动预测未来的运动),您可以使用单个 Chain 并在循环中调用它;对于每个输入时间点,你输出另一个。

  • 从时间序列映射到单个 "output" 时(例如,将句子缩减为 "happy sentiment" 或 "sad sentiment"),您必须首先将所有数据压缩并将其缩小到固定大小;你喂了很多东西,但最后只有一个出来。

我们将把我们的模型重新构建成两部分;首先是循环 "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 舞蹈,然后在输出上调用 reducer。请注意,我们还必须 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!() 实际上可以训练,尽管不是很好。 ;)

最终观察

  • 虽然您的数据都是 Int64 的,但使用浮点数是非常典型的,除了嵌入(嵌入是一种获取非数字数据的方法,例如字符或单词并为它们分配数字,有点像 ASCII);如果你正在处理文本,你几乎肯定会使用某种嵌入,而该嵌入将决定你的第一个 LSTM 的维数,因此你的输入将全部被 "one-hot" 编码。

  • softmax用于预测概率;它将确保对于每个输入,输出都在 [0...1] 之间,而且它们总和为 1.0,就像一个好的小概率分布应该的那样。这在进行分类时最有用,当您想将 [-2, 5, 0.101] 的狂野网络输出值整理成可以说 "we have 99.1% certainty that the second class is correct, and 0.7% certainty it's the third class."

  • 的内容时
  • 在训练这些网络时,出于硬件效率的原因,您通常会希望通过网络一次批处理多个时间序列;这既简单又复杂,因为一方面它只是意味着不是通过单个 Sx1 向量(其中 S 是嵌入的大小)而是要通过一个 SxN 矩阵,但这也意味着批次中所有内容的时间步数必须匹配(因为 SxN 必须在所有时间步中保持相同,所以如果一个时间序列在任何时间序列之前结束你的批次中的其他人你不能只是放弃它,从而在批次中途减少 N)。所以大多数人所做的就是将他们的时间序列填充到相同的长度。

祝你在机器学习之旅中好运!