3

我还是 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")

有人可以向我解释不匹配的尺寸是什么,以及如何使它们匹配?

4

1 回答 1

7

介绍

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

调试形状不匹配

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

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

因此,让我们首先将第一个元素拉出您dataloss. 您没有在问题中指定损失函数或优化器。虽然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),
)

现在我们重新运行:

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 和密集层等感到困惑,所以我提供了这些简单的经验法则:

  • 当从一个时间序列映射到另一个时间序列时(例如,不断地从先前的运动预测未来的运动),您可以使用单个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 dance,然后在输出上调用 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's,但非常典型的是将浮点数用于除嵌入之外的所有内容(嵌入是一种获取非数字数据(例如字符或单词)并为它们分配数字的方法,有点像 ASCII);如果你正在处理文本,你几乎肯定会使用某种嵌入,并且嵌入将决定你的第一个 LSTM 的维度是什么,因此你的输入都将被“单热”编码。

  • softmax当您想要预测概率时使用;它将确保对于每个输入,输出都在 之间[0...1],而且它们总和为1.0,就像一个好的小概率分布应该那样。这在进行分类时最有用,当你想将你的狂野网络输出值争吵[-2, 5, 0.101]成你可以说“我们99.1%确定第二类是正确的,并且0.7%确定它是第三类”的东西时。

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

祝你的机器学习之旅好运!

于 2019-10-01T17:54:52.690 回答