第一个模型:全连接网络

让我们从简单一点的模型开始。
Sequential当然是实现全连接网络的最好方式,但我们从简单的全连接网络开始,有助于我们学习这部分的内容。在开始前,有几个概念需要澄清:

  • 层对象接受张量为参数,返回一个张量。
  • 输入是张量,输出也是张量的一个框架就是一个模型,通过Model定义。
  • 这样的模型可以被像Keras的Sequential一样被训练

例如:

  1. from keras.layers import Input, Dense
  2. from keras.models import Model
  3. # This returns a tensor
  4. inputs = Input(shape=(784,))
  5. # a layer instance is callable on a tensor, and returns a tensor
  6. x = Dense(64, activation='relu')(inputs)
  7. x = Dense(64, activation='relu')(x)
  8. predictions = Dense(10, activation='softmax')(x)
  9. # This creates a model that includes
  10. # the Input layer and three Dense layers
  11. model = Model(inputs=inputs, outputs=predictions)
  12. model.compile(optimizer='rmsprop',
  13. loss='categorical_crossentropy',
  14. metrics=['accuracy'])
  15. model.fit(data, labels) # starts training

所有的模型都是可调用的,就像层一样

利用函数式模型的接口,我们可以很容易的重用已经训练好的模型:你可以把模型当作一个层一样,通过提供一个tensor来调用它。注意当你调用一个模型时,你不仅仅重用了它的结构,也重用了它的权重。

  1. x = Input(shape=(784,))
  2. # This works, and returns the 10-way softmax we defined above.
  3. y = model(x)

这种方式可以允许你快速的创建能处理序列信号的模型,你可以很快将一个图像分类的模型变为一个对视频分类的模型,只需要一行代码:

  1. from keras.layers import TimeDistributed
  2. # Input tensor for sequences of 20 timesteps,
  3. # each containing a 784-dimensional vector
  4. input_sequences = Input(shape=(20, 784))
  5. # This applies our previous model to every timestep in the input sequences.
  6. # the output of the previous model was a 10-way softmax,
  7. # so the output of the layer below will be a sequence of 20 vectors of size 10.
  8. processed_sequences = TimeDistributed(model)(input_sequences)

多输入和多输出模型

注意我们可以给任意层传递name参数来给层命名。之后模型编译和训练时可以用到。
详细例子见官方文档:https://keras-cn.readthedocs.io/en/latest/getting_started/functional_API/

共享层

另一个使用函数式模型的场合是使用共享层的时候。
考虑微博数据,我们希望建立模型来判别两条微博是否是来自同一个用户,这个需求同样可以用来判断一个用户的两条微博的相似性。
一种实现方式是,我们建立一个模型,它分别将两条微博的数据映射到两个特征向量上,然后将特征向量串联并加一个logistic回归层,输出它们来自同一个用户的概率。这种模型的训练数据是一对对的微博。
因为这个问题是对称的,所以处理第一条微博的模型当然也能重用于处理第二条微博。所以这里我们使用一个共享的LSTM层来进行映射。
例子:

  • 首先,我们将微博的数据转为(140,256)的矩阵,即每条微博有140个字符,每个单词的特征由一个256维的词向量表示,向量的每个元素为1表示某个字符出现,为0表示不出现,这是一个one-hot编码。
    • 之所以是(140,256)是因为一条微博最多有140个字符,而扩展的ASCII码表编码了常见的256个字符。原文中此处为Tweet,所以对外国人而言这是合理的。如果考虑中文字符,那一个单词的词向量就不止256了。
  • 若要对不同的输入共享同一层,就初始化该层一次,然后多次调用它 ```python import keras from keras.layers import Input, LSTM, Dense from keras.models import Model

tweet_a = Input(shape=(140, 256)) tweet_b = Input(shape=(140, 256))

This layer can take as input a matrix

and will return a vector of size 64

shared_lstm = LSTM(64)

When we reuse the same layer instance

multiple times, the weights of the layer

are also being reused

(it is effectively the same layer)

encoded_a = shared_lstm(tweet_a) encoded_b = shared_lstm(tweet_b)

We can then concatenate the two vectors:

merged_vector = keras.layers.concatenate([encoded_a, encoded_b], axis=-1)

And add a logistic regression on top

predictions = Dense(1, activation=’sigmoid’)(merged_vector)

We define a trainable model linking the

tweet inputs to the predictions

model = Model(inputs=[tweet_a, tweet_b], outputs=predictions)

model.compile(optimizer=’rmsprop’, loss=’binary_crossentropy’, metrics=[‘accuracy’]) model.fit([data_a, data_b], labels, epochs=10)

  1. :::info
  2. 先暂停一下,看看共享层到底输出了什么,它的输出数据shape又是什么
  3. :::
  4. <a name="n6rk4"></a>
  5. #### 层“节点”的概念
  6. - 无论何时,当你在某个输入上调用层时,你就创建了一个新的张量(即该层的输出),同时你也在为这个层增加一个“(计算)节点”。这个节点将输入张量映射为输出张量。当你多次调用该层时,这个层就有了多个节点,其下标分别为012...
  7. - 在上一版本的Keras中,你可以通过layer.get_output()方法来获得层的输出张量,或者通过layer.output_shape获得其输出张量的shape。这个版本的Keras你仍然可以这么做(除了layer.get_output()被output替换)。但如果一个层与多个输入相连,会出现什么情况呢?
  8. - 如果层只与一个输入相连,那没有任何困惑的地方。.output将会返回该层唯一的输出
  9. ```python
  10. a = Input(shape=(140, 256))
  11. lstm = LSTM(32)
  12. encoded_a = lstm(a)
  13. assert lstm.output == encoded_a

但当层与多个输入相连时,会出现问题

  1. a = Input(shape=(140, 256))
  2. b = Input(shape=(140, 256))
  3. lstm = LSTM(32)
  4. encoded_a = lstm(a)
  5. encoded_b = lstm(b)
  6. lstm.output
  7. # 这段代码会报错,如下:
  8. >> AssertionError: Layer lstm_1 has multiple inbound nodes,
  9. hence the notion of "layer output" is ill-defined.
  10. Use `get_output_at(node_index)` instead.

通过下面这种调用方法解决:

  1. assert lstm.get_output_at(0) == encoded_a
  2. assert lstm.get_output_at(1) == encoded_b

对于input_shapeoutput_shape也是一样,如果一个层只有一个节点,或所有的节点都有相同的输入或输出shape,那么input_shapeoutput_shape都是没有歧义的,并也只返回一个值。但是,例如你把一个相同的Conv2D应用于一个大小为(32,32,3)的数据,然后又将其应用于一个(64,64,3)的数据,那么此时该层就具有了多个输入和输出的shape,你就需要显式的指定节点的下标,来表明你想取的是哪个了

  1. a = Input(shape=(32, 32, 3))
  2. b = Input(shape=(64, 64, 3))
  3. conv = Conv2D(16, (3, 3), padding='same')
  4. conved_a = conv(a)
  5. # Only one input so far, the following will work:
  6. assert conv.input_shape == (None, 32, 32, 3)
  7. conved_b = conv(b)
  8. # now the `.input_shape` property wouldn't work, but this does:
  9. assert conv.get_input_shape_at(0) == (None, 32, 32, 3)
  10. assert conv.get_input_shape_at(1) == (None, 64, 64, 3)

更多的例子

详见文档:https://keras-cn.readthedocs.io/en/latest/getting_started/functional_API/