我在构建一个使用循环层(GRU)将字符串转换为另一个字符串的模型。我尝试在倒数第二层使用了Dense层和TimeDistributed(Dense)层,但当使用return_sequences=True时,我不明白这两者之间的区别,特别是它们似乎拥有相同数量的参数。
我的简化模型如下:
InputSize = 15MaxLen = 64HiddenSize = 16inputs = keras.layers.Input(shape=(MaxLen, InputSize))x = keras.layers.recurrent.GRU(HiddenSize, return_sequences=True)(inputs)x = keras.layers.TimeDistributed(keras.layers.Dense(InputSize))(x)predictions = keras.layers.Activation('softmax')(x)
网络的摘要是:
_________________________________________________________________Layer (type) Output Shape Param # =================================================================input_1 (InputLayer) (None, 64, 15) 0 _________________________________________________________________gru_1 (GRU) (None, 64, 16) 1536 _________________________________________________________________time_distributed_1 (TimeDist (None, 64, 15) 255 _________________________________________________________________activation_1 (Activation) (None, 64, 15) 0 =================================================================
我认为这是合理的,因为我理解的TimeDistributed是它会在所有时间点应用相同的层,因此Dense层有16*15+15=255个参数(权重+偏置)。
然而,如果我切换到一个简单的Dense层:
inputs = keras.layers.Input(shape=(MaxLen, InputSize))x = keras.layers.recurrent.GRU(HiddenSize, return_sequences=True)(inputs)x = keras.layers.Dense(InputSize)(x)predictions = keras.layers.Activation('softmax')(x)
我仍然只有255个参数:
_________________________________________________________________Layer (type) Output Shape Param # =================================================================input_1 (InputLayer) (None, 64, 15) 0 _________________________________________________________________gru_1 (GRU) (None, 64, 16) 1536 _________________________________________________________________dense_1 (Dense) (None, 64, 15) 255 _________________________________________________________________activation_1 (Activation) (None, 64, 15) 0 =================================================================
我怀疑这是因为Dense()只使用形状中的最后一个维度,并有效地将其他一切视为批次类似的维度。但这样一来,我就不确定Dense和TimeDistributed(Dense)之间的区别是什么了。
更新 查看https://github.com/fchollet/keras/blob/master/keras/layers/core.py,确实看起来Dense只使用最后一个维度来调整自身大小:
def build(self, input_shape): assert len(input_shape) >= 2 input_dim = input_shape[-1] self.kernel = self.add_weight(shape=(input_dim, self.units),
它还使用keras.dot来应用权重:
def call(self, inputs): output = K.dot(inputs, self.kernel)
keras.dot的文档暗示它能很好地处理n维张量。我想知道它的确切行为是否意味着Dense()实际上会在每个时间步被调用。如果是这样,问题仍然存在:在这种情况下,TimeDistributed()实现了什么功能?
回答:
TimeDistributedDense
在GRU/LSTM单元展开过程中将相同的密集层应用于每个时间步。因此,误差函数将在预测标签序列和实际标签序列之间计算。(这通常是序列到序列标记问题的要求)。
然而,当return_sequences=False
时,Dense
层只在最后一个单元应用一次。这通常是使用RNN进行分类问题的情况。如果return_sequences=True
,则Dense
层就像TimeDistributedDense
一样应用于每个时间步。
因此,根据你的模型,两者是相同的,但如果你将第二个模型改为return_sequences=False
,那么Dense
将只在最后一个单元应用。尝试更改它,模型将抛出错误,因为那时Y
的大小将是[Batch_size, InputSize]
,它不再是序列到序列,而是一个完整序列到标签的问题。
from keras.models import Sequentialfrom keras.layers import Dense, Activation, TimeDistributedfrom keras.layers.recurrent import GRUimport numpy as npInputSize = 15MaxLen = 64HiddenSize = 16OutputSize = 8n_samples = 1000model1 = Sequential()model1.add(GRU(HiddenSize, return_sequences=True, input_shape=(MaxLen, InputSize)))model1.add(TimeDistributed(Dense(OutputSize)))model1.add(Activation('softmax'))model1.compile(loss='categorical_crossentropy', optimizer='rmsprop')model2 = Sequential()model2.add(GRU(HiddenSize, return_sequences=True, input_shape=(MaxLen, InputSize)))model2.add(Dense(OutputSize))model2.add(Activation('softmax'))model2.compile(loss='categorical_crossentropy', optimizer='rmsprop')model3 = Sequential()model3.add(GRU(HiddenSize, return_sequences=False, input_shape=(MaxLen, InputSize)))model3.add(Dense(OutputSize))model3.add(Activation('softmax'))model3.compile(loss='categorical_crossentropy', optimizer='rmsprop')X = np.random.random([n_samples,MaxLen,InputSize])Y1 = np.random.random([n_samples,MaxLen,OutputSize])Y2 = np.random.random([n_samples, OutputSize])model1.fit(X, Y1, batch_size=128, nb_epoch=1)model2.fit(X, Y1, batch_size=128, nb_epoch=1)model3.fit(X, Y2, batch_size=128, nb_epoch=1)print(model1.summary())print(model2.summary())print(model3.summary())
在上面的示例中,model1
和model2
的架构是样本(序列到序列模型),而model3
是一个完整序列到标签的模型。