Keras学习手册(三),开始使用 Keras 函数式 API

感谢作者分享-http://bjbsair.com/2020-04-07/tech-info/30658.html

Keras 函数式 API 是定义复杂模型(如多输出模型、有向无环图,或具有共享层的模型)的方法。

这部分文档假设你已经对 Sequential 顺序模型比较熟悉。

让我们先从一些简单的例子开始。


例一:全连接网络

Sequential 模型可能是实现这种网络的一个更好选择,但这个例子能够帮助我们进行一些简单的理解。

  • 网络层的实例是可调用的,它以张量为参数,并且返回一个张量
  • 输入和输出均为张量,它们都可以用来定义一个模型(Model)
  • 这样的模型同 Keras 的 Sequential 模型一样,都可以被训练
from keras.layers import Input, Dense
from keras.models import Model  

# 这部分返回一个张量
inputs = Input(shape=(784,))  

# 层的实例是可调用的,它以张量为参数,并且返回一个张量
x = Dense(64, activation=‘relu‘)(inputs)
x = Dense(64, activation=‘relu‘)(x)
predictions = Dense(10, activation=‘softmax‘)(x)  

# 这部分创建了一个包含输入层和三个全连接层的模型
model = Model(inputs=inputs, outputs=predictions)
model.compile(optimizer=‘rmsprop‘,
              loss=‘categorical_crossentropy‘,
              metrics=[‘accuracy‘])
model.fit(data, labels)  # 开始训练  


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

利用函数式 API,可以轻易地重用训练好的模型:可以将任何模型看作是一个层,然后通过传递一个张量来调用它。注意,在调用模型时,您不仅重用模型的结构,还重用了它的权重。

x = Input(shape=(784,))
# 这是可行的,并且返回上面定义的 10-way softmax。
y = model(x)  

这种方式能允许我们快速创建可以处理序列输入的模型。只需一行代码,你就将图像分类模型转换为视频分类模型。

from keras.layers import TimeDistributed  

# 输入张量是 20 个时间步的序列,
# 每一个时间为一个 784 维的向量
input_sequences = Input(shape=(20, 784))  

# 这部分将我们之前定义的模型应用于输入序列中的每个时间步。
# 之前定义的模型的输出是一个 10-way softmax,
# 因而下面的层的输出将是维度为 10 的 20 个向量的序列。
processed_sequences = TimeDistributed(model)(input_sequences)  


多输入多输出模型

以下是函数式 API 的一个很好的例子:具有多个输入和输出的模型。函数式 API 使处理大量交织的数据流变得容易。

来考虑下面的模型。我们试图预测 Twitter 上的一条新闻标题有多少转发和点赞数。模型的主要输入将是新闻标题本身,即一系列词语,但是为了增添趣味,我们的模型还添加了其他的辅助输入来接收额外的数据,例如新闻标题的发布的时间等。 该模型也将通过两个损失函数进行监督学习。较早地在模型中使用主损失函数,是深度学习模型的一个良好正则方法。

模型结构如下图所示:

让我们用函数式 API 来实现它。

主要输入接收新闻标题本身,即一个整数序列(每个整数编码一个词)。 这些整数在 1 到 10,000 之间(10,000 个词的词汇表),且序列长度为 100 个词。

from keras.layers import Input, Embedding, LSTM, Dense
from keras.models import Model  

# 标题输入:接收一个含有 100 个整数的序列,每个整数在 1 到 10000 之间。
# 注意我们可以通过传递一个 "name" 参数来命名任何层。
main_input = Input(shape=(100,), dtype=‘int32‘, name=‘main_input‘)  

# Embedding 层将输入序列编码为一个稠密向量的序列,
# 每个向量维度为 512。
x = Embedding(output_dim=512, input_dim=10000, input_length=100)(main_input)  

# LSTM 层把向量序列转换成单个向量,
# 它包含整个序列的上下文信息
lstm_out = LSTM(32)(x)  

在这里,我们插入辅助损失,使得即使在模型主损失很高的情况下,LSTM 层和 Embedding 层都能被平稳地训练。

auxiliary_output = Dense(1, activation=‘sigmoid‘, name=‘aux_output‘)(lstm_out)  

此时,我们将辅助输入数据与 LSTM 层的输出连接起来,输入到模型中:

auxiliary_input = Input(shape=(5,), name=‘aux_input‘)
x = keras.layers.concatenate([lstm_out, auxiliary_input])  

# 堆叠多个全连接网络层
x = Dense(64, activation=‘relu‘)(x)
x = Dense(64, activation=‘relu‘)(x)
x = Dense(64, activation=‘relu‘)(x)  

# 最后添加主要的逻辑回归层
main_output = Dense(1, activation=‘sigmoid‘, name=‘main_output‘)(x)  

然后定义一个具有两个输入和两个输出的模型:

model = Model(inputs=[main_input, auxiliary_input], outputs=[main_output, auxiliary_output])  

现在编译模型,并给辅助损失分配一个 0.2 的权重。如果要为不同的输出指定不同的 loss_weights 或 loss,可以使用列表或字典。 在这里,我们给 loss 参数传递单个损失函数,这个损失将用于所有的输出。

model.compile(optimizer=‘rmsprop‘, loss=‘binary_crossentropy‘,
              loss_weights=[1., 0.2])  

我们可以通过传递输入数组和目标数组的列表来训练模型:

model.fit([headline_data, additional_data], [labels, labels],
          epochs=50, batch_size=32)  

由于输入和输出均被命名了(在定义时传递了一个 name 参数),我们也可以通过以下方式编译模型:

model.compile(optimizer=‘rmsprop‘,
              loss={‘main_output‘: ‘binary_crossentropy‘, ‘aux_output‘: ‘binary_crossentropy‘},
              loss_weights={‘main_output‘: 1., ‘aux_output‘: 0.2})  

# 然后使用以下方式训练:
model.fit({‘main_input‘: headline_data, ‘aux_input‘: additional_data},
          {‘main_output‘: labels, ‘aux_output‘: labels},
          epochs=50, batch_size=32)  


共享网络层

函数式 API 的另一个用途是使用共享网络层的模型。我们来看看共享层。

来考虑推特推文数据集。我们想要建立一个模型来分辨两条推文是否来自同一个人(例如,通过推文的相似性来对用户进行比较)。

由于这个问题是对称的,编码第一条推文的机制应该被完全重用来编码第二条推文(权重及其他全部)。这里我们使用一个共享的 LSTM 层来编码推文。

让我们使用函数式 API 来构建它。首先我们将一条推特转换为一个尺寸为 (280, 256) 的矩阵,即每条推特 280 字符,每个字符为 256 维的 one-hot 编码向量 (取 256 个常用字符)。

import keras
from keras.layers import Input, LSTM, Dense
from keras.models import Model  

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

要在不同的输入上共享同一个层,只需实例化该层一次,然后根据需要传入你想要的输入即可:

# 这一层可以输入一个矩阵,并返回一个 64 维的向量
shared_lstm = LSTM(64)  

# 当我们重用相同的图层实例多次,图层的权重也会被重用 (它其实就是同一层)
encoded_a = shared_lstm(tweet_a)
encoded_b = shared_lstm(tweet_b)  

# 然后再连接两个向量:
merged_vector = keras.layers.concatenate([encoded_a, encoded_b], axis=-1)  

# 再在上面添加一个逻辑回归层
predictions = Dense(1, activation=‘sigmoid‘)(merged_vector)  

# 定义一个连接推特输入和预测的可训练的模型
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)  

让我们暂停一会,看看如何读取共享层的输出或输出尺寸。


层「节点」的概念

每当你在某个输入上调用一个层时,都将创建一个新的张量(层的输出),并且为该层添加一个「节点」,将输入张量连接到输出张量。当多次调用同一个图层时,该图层将拥有多个节点索引 (0, 1, 2...)。

在之前版本的 Keras 中,可以通过 layer.get_output() 来获得层实例的输出张量,或者通过 layer.output_shape 来获取其输出形状。现在你依然可以这么做(除了 get_output() 已经被 output 属性替代)。但是如果一个层与多个输入连接呢?

只要一个层仅仅连接到一个输入,就不会有困惑,.output 会返回层的唯一输出:

a = Input(shape=(280, 256))  

lstm = LSTM(32)
encoded_a = lstm(a)  

assert lstm.output == encoded_a  

但是如果该层有多个输入,那就会出现问题:

a = Input(shape=(280, 256))
b = Input(shape=(280, 256))  

lstm = LSTM(32)
encoded_a = lstm(a)
encoded_b = lstm(b)  

lstm.output  

>> AttributeError: Layer lstm_1 has multiple inbound nodes,
hence the notion of "layer output" is ill-defined.
Use `get_output_at(node_index)` instead.  

好吧,通过下面的方法可以解决:

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

够简单,对吧?

input_shape 和 output_shape 这两个属性也是如此:只要该层只有一个节点,或者只要所有节点具有相同的输入/输出尺寸,那么「层输出/输入尺寸」的概念就被很好地定义,并且将由 layer.output_shape / layer.input_shape 返回。但是比如说,如果将一个 Conv2D 层先应用于尺寸为 (32,32,3) 的输入,再应用于尺寸为 (64, 64, 3) 的输入,那么这个层就会有多个输入/输出尺寸,你将不得不通过指定它们所属节点的索引来获取它们:

a = Input(shape=(32, 32, 3))
b = Input(shape=(64, 64, 3))  

conv = Conv2D(16, (3, 3), padding=‘same‘)
conved_a = conv(a)  

# 到目前为止只有一个输入,以下可行:
assert conv.input_shape == (None, 32, 32, 3)  

conved_b = conv(b)
# 现在 `.input_shape` 属性不可行,但是这样可以:
assert conv.get_input_shape_at(0) == (None, 32, 32, 3)
assert conv.get_input_shape_at(1) == (None, 64, 64, 3)  


更多的例子

代码示例仍然是起步的最佳方式,所以这里还有更多的例子。

Inception 模型

有关 Inception 结构的更多信息,请参阅 Going Deeper with Convolutions。

from keras.layers import Conv2D, MaxPooling2D, Input  

input_img = Input(shape=(256, 256, 3))  

tower_1 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(input_img)
tower_1 = Conv2D(64, (3, 3), padding=‘same‘, activation=‘relu‘)(tower_1)  

tower_2 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(input_img)
tower_2 = Conv2D(64, (5, 5), padding=‘same‘, activation=‘relu‘)(tower_2)  

tower_3 = MaxPooling2D((3, 3), strides=(1, 1), padding=‘same‘)(input_img)
tower_3 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(tower_3)  

output = keras.layers.concatenate([tower_1, tower_2, tower_3], axis=1)  

卷积层上的残差连接

有关残差网络 (Residual Network) 的更多信息,请参阅 Deep Residual Learning for Image Recognition。

from keras.layers import Conv2D, Input  

# 输入张量为 3 通道 256x256 图像
x = Input(shape=(256, 256, 3))
# 3 输出通道(与输入通道相同)的 3x3 卷积核
y = Conv2D(3, (3, 3), padding=‘same‘)(x)
# 返回 x + y
z = keras.layers.add([x, y])  

共享视觉模型

该模型在两个输入上重复使用同一个图像处理模块,以判断两个 MNIST 数字是否为相同的数字。

from keras.layers import Conv2D, MaxPooling2D, Input, Dense, Flatten
from keras.models import Model  

# 首先,定义视觉模型
digit_input = Input(shape=(27, 27, 1))
x = Conv2D(64, (3, 3))(digit_input)
x = Conv2D(64, (3, 3))(x)
x = MaxPooling2D((2, 2))(x)
out = Flatten()(x)  

vision_model = Model(digit_input, out)  

# 然后,定义区分数字的模型
digit_a = Input(shape=(27, 27, 1))
digit_b = Input(shape=(27, 27, 1))  

# 视觉模型将被共享,包括权重和其他所有
out_a = vision_model(digit_a)
out_b = vision_model(digit_b)  

concatenated = keras.layers.concatenate([out_a, out_b])
out = Dense(1, activation=‘sigmoid‘)(concatenated)  

classification_model = Model([digit_a, digit_b], out)  

视觉问答模型

当被问及关于图片的自然语言问题时,该模型可以选择正确的单词作答。

它通过将问题和图像编码成向量,然后连接两者,在上面训练一个逻辑回归,来从词汇表中挑选一个可能的单词作答。

from keras.layers import Conv2D, MaxPooling2D, Flatten
from keras.layers import Input, LSTM, Embedding, Dense
from keras.models import Model, Sequential  

# 首先,让我们用 Sequential 来定义一个视觉模型。
# 这个模型会把一张图像编码为向量。
vision_model = Sequential()
vision_model.add(Conv2D(64, (3, 3), activation=‘relu‘, padding=‘same‘, input_shape=(224, 224, 3)))
vision_model.add(Conv2D(64, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Conv2D(128, (3, 3), activation=‘relu‘, padding=‘same‘))
vision_model.add(Conv2D(128, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘, padding=‘same‘))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Flatten())  

# 现在让我们用视觉模型来得到一个输出张量:
image_input = Input(shape=(224, 224, 3))
encoded_image = vision_model(image_input)  

# 接下来,定义一个语言模型来将问题编码成一个向量。
# 每个问题最长 100 个词,词的索引从 1 到 9999.
question_input = Input(shape=(100,), dtype=‘int32‘)
embedded_question = Embedding(input_dim=10000, output_dim=256, input_length=100)(question_input)
encoded_question = LSTM(256)(embedded_question)  

# 连接问题向量和图像向量:
merged = keras.layers.concatenate([encoded_question, encoded_image])  

# 然后在上面训练一个 1000 词的逻辑回归模型:
output = Dense(1000, activation=‘softmax‘)(merged)  

# 最终模型:
vqa_model = Model(inputs=[image_input, question_input], outputs=output)  

# 下一步就是在真实数据上训练模型。  

视频问答模型

现在我们已经训练了图像问答模型,我们可以很快地将它转换为视频问答模型。在适当的训练下,你可以给它展示一小段视频(例如 100 帧的人体动作),然后问它一个关于这段视频的问题(例如,「这个人在做什么运动?」 -> 「足球」)。

from keras.layers import TimeDistributed  

video_input = Input(shape=(100, 224, 224, 3))
# 这是基于之前定义的视觉模型(权重被重用)构建的视频编码
encoded_frame_sequence = TimeDistributed(vision_model)(video_input)  # 输出为向量的序列
encoded_video = LSTM(256)(encoded_frame_sequence)  # 输出为一个向量  

# 这是问题编码器的模型级表示,重复使用与之前相同的权重:
question_encoder = Model(inputs=question_input, outputs=encoded_question)  

# 让我们用它来编码这个问题:
video_question_input = Input(shape=(100,), dtype=‘int32‘)
encoded_video_question = question_encoder(video_question_input)  

# 这就是我们的视频问答模式:
merged = keras.layers.concatenate([encoded_video, encoded_video_question])
output = Dense(1000, activation=‘softmax‘)(merged)
video_qa_model = Model(inputs=[video_input, video_question_input], outputs=output)  

Next Previous感谢作者分享-http://bjbsair.com/2020-04-07/tech-info/30658.html

Keras 函数式 API 是定义复杂模型(如多输出模型、有向无环图,或具有共享层的模型)的方法。

这部分文档假设你已经对 Sequential 顺序模型比较熟悉。

让我们先从一些简单的例子开始。


例一:全连接网络

Sequential 模型可能是实现这种网络的一个更好选择,但这个例子能够帮助我们进行一些简单的理解。

  • 网络层的实例是可调用的,它以张量为参数,并且返回一个张量
  • 输入和输出均为张量,它们都可以用来定义一个模型(Model)
  • 这样的模型同 Keras 的 Sequential 模型一样,都可以被训练
from keras.layers import Input, Dense
from keras.models import Model  

# 这部分返回一个张量
inputs = Input(shape=(784,))  

# 层的实例是可调用的,它以张量为参数,并且返回一个张量
x = Dense(64, activation=‘relu‘)(inputs)
x = Dense(64, activation=‘relu‘)(x)
predictions = Dense(10, activation=‘softmax‘)(x)  

# 这部分创建了一个包含输入层和三个全连接层的模型
model = Model(inputs=inputs, outputs=predictions)
model.compile(optimizer=‘rmsprop‘,
              loss=‘categorical_crossentropy‘,
              metrics=[‘accuracy‘])
model.fit(data, labels)  # 开始训练  


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

利用函数式 API,可以轻易地重用训练好的模型:可以将任何模型看作是一个层,然后通过传递一个张量来调用它。注意,在调用模型时,您不仅重用模型的结构,还重用了它的权重。

x = Input(shape=(784,))
# 这是可行的,并且返回上面定义的 10-way softmax。
y = model(x)  

这种方式能允许我们快速创建可以处理序列输入的模型。只需一行代码,你就将图像分类模型转换为视频分类模型。

from keras.layers import TimeDistributed  

# 输入张量是 20 个时间步的序列,
# 每一个时间为一个 784 维的向量
input_sequences = Input(shape=(20, 784))  

# 这部分将我们之前定义的模型应用于输入序列中的每个时间步。
# 之前定义的模型的输出是一个 10-way softmax,
# 因而下面的层的输出将是维度为 10 的 20 个向量的序列。
processed_sequences = TimeDistributed(model)(input_sequences)  


多输入多输出模型

以下是函数式 API 的一个很好的例子:具有多个输入和输出的模型。函数式 API 使处理大量交织的数据流变得容易。

来考虑下面的模型。我们试图预测 Twitter 上的一条新闻标题有多少转发和点赞数。模型的主要输入将是新闻标题本身,即一系列词语,但是为了增添趣味,我们的模型还添加了其他的辅助输入来接收额外的数据,例如新闻标题的发布的时间等。 该模型也将通过两个损失函数进行监督学习。较早地在模型中使用主损失函数,是深度学习模型的一个良好正则方法。

模型结构如下图所示:

让我们用函数式 API 来实现它。

主要输入接收新闻标题本身,即一个整数序列(每个整数编码一个词)。 这些整数在 1 到 10,000 之间(10,000 个词的词汇表),且序列长度为 100 个词。

from keras.layers import Input, Embedding, LSTM, Dense
from keras.models import Model  

# 标题输入:接收一个含有 100 个整数的序列,每个整数在 1 到 10000 之间。
# 注意我们可以通过传递一个 "name" 参数来命名任何层。
main_input = Input(shape=(100,), dtype=‘int32‘, name=‘main_input‘)  

# Embedding 层将输入序列编码为一个稠密向量的序列,
# 每个向量维度为 512。
x = Embedding(output_dim=512, input_dim=10000, input_length=100)(main_input)  

# LSTM 层把向量序列转换成单个向量,
# 它包含整个序列的上下文信息
lstm_out = LSTM(32)(x)  

在这里,我们插入辅助损失,使得即使在模型主损失很高的情况下,LSTM 层和 Embedding 层都能被平稳地训练。

auxiliary_output = Dense(1, activation=‘sigmoid‘, name=‘aux_output‘)(lstm_out)  

此时,我们将辅助输入数据与 LSTM 层的输出连接起来,输入到模型中:

auxiliary_input = Input(shape=(5,), name=‘aux_input‘)
x = keras.layers.concatenate([lstm_out, auxiliary_input])  

# 堆叠多个全连接网络层
x = Dense(64, activation=‘relu‘)(x)
x = Dense(64, activation=‘relu‘)(x)
x = Dense(64, activation=‘relu‘)(x)  

# 最后添加主要的逻辑回归层
main_output = Dense(1, activation=‘sigmoid‘, name=‘main_output‘)(x)  

然后定义一个具有两个输入和两个输出的模型:

model = Model(inputs=[main_input, auxiliary_input], outputs=[main_output, auxiliary_output])  

现在编译模型,并给辅助损失分配一个 0.2 的权重。如果要为不同的输出指定不同的 loss_weights 或 loss,可以使用列表或字典。 在这里,我们给 loss 参数传递单个损失函数,这个损失将用于所有的输出。

model.compile(optimizer=‘rmsprop‘, loss=‘binary_crossentropy‘,
              loss_weights=[1., 0.2])  

我们可以通过传递输入数组和目标数组的列表来训练模型:

model.fit([headline_data, additional_data], [labels, labels],
          epochs=50, batch_size=32)  

由于输入和输出均被命名了(在定义时传递了一个 name 参数),我们也可以通过以下方式编译模型:

model.compile(optimizer=‘rmsprop‘,
              loss={‘main_output‘: ‘binary_crossentropy‘, ‘aux_output‘: ‘binary_crossentropy‘},
              loss_weights={‘main_output‘: 1., ‘aux_output‘: 0.2})  

# 然后使用以下方式训练:
model.fit({‘main_input‘: headline_data, ‘aux_input‘: additional_data},
          {‘main_output‘: labels, ‘aux_output‘: labels},
          epochs=50, batch_size=32)  


共享网络层

函数式 API 的另一个用途是使用共享网络层的模型。我们来看看共享层。

来考虑推特推文数据集。我们想要建立一个模型来分辨两条推文是否来自同一个人(例如,通过推文的相似性来对用户进行比较)。

由于这个问题是对称的,编码第一条推文的机制应该被完全重用来编码第二条推文(权重及其他全部)。这里我们使用一个共享的 LSTM 层来编码推文。

让我们使用函数式 API 来构建它。首先我们将一条推特转换为一个尺寸为 (280, 256) 的矩阵,即每条推特 280 字符,每个字符为 256 维的 one-hot 编码向量 (取 256 个常用字符)。

import keras
from keras.layers import Input, LSTM, Dense
from keras.models import Model  

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

要在不同的输入上共享同一个层,只需实例化该层一次,然后根据需要传入你想要的输入即可:

# 这一层可以输入一个矩阵,并返回一个 64 维的向量
shared_lstm = LSTM(64)  

# 当我们重用相同的图层实例多次,图层的权重也会被重用 (它其实就是同一层)
encoded_a = shared_lstm(tweet_a)
encoded_b = shared_lstm(tweet_b)  

# 然后再连接两个向量:
merged_vector = keras.layers.concatenate([encoded_a, encoded_b], axis=-1)  

# 再在上面添加一个逻辑回归层
predictions = Dense(1, activation=‘sigmoid‘)(merged_vector)  

# 定义一个连接推特输入和预测的可训练的模型
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)  

让我们暂停一会,看看如何读取共享层的输出或输出尺寸。


层「节点」的概念

每当你在某个输入上调用一个层时,都将创建一个新的张量(层的输出),并且为该层添加一个「节点」,将输入张量连接到输出张量。当多次调用同一个图层时,该图层将拥有多个节点索引 (0, 1, 2...)。

在之前版本的 Keras 中,可以通过 layer.get_output() 来获得层实例的输出张量,或者通过 layer.output_shape 来获取其输出形状。现在你依然可以这么做(除了 get_output() 已经被 output 属性替代)。但是如果一个层与多个输入连接呢?

只要一个层仅仅连接到一个输入,就不会有困惑,.output 会返回层的唯一输出:

a = Input(shape=(280, 256))  

lstm = LSTM(32)
encoded_a = lstm(a)  

assert lstm.output == encoded_a  

但是如果该层有多个输入,那就会出现问题:

a = Input(shape=(280, 256))
b = Input(shape=(280, 256))  

lstm = LSTM(32)
encoded_a = lstm(a)
encoded_b = lstm(b)  

lstm.output  

>> AttributeError: Layer lstm_1 has multiple inbound nodes,
hence the notion of "layer output" is ill-defined.
Use `get_output_at(node_index)` instead.  

好吧,通过下面的方法可以解决:

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

够简单,对吧?

input_shape 和 output_shape 这两个属性也是如此:只要该层只有一个节点,或者只要所有节点具有相同的输入/输出尺寸,那么「层输出/输入尺寸」的概念就被很好地定义,并且将由 layer.output_shape / layer.input_shape 返回。但是比如说,如果将一个 Conv2D 层先应用于尺寸为 (32,32,3) 的输入,再应用于尺寸为 (64, 64, 3) 的输入,那么这个层就会有多个输入/输出尺寸,你将不得不通过指定它们所属节点的索引来获取它们:

a = Input(shape=(32, 32, 3))
b = Input(shape=(64, 64, 3))  

conv = Conv2D(16, (3, 3), padding=‘same‘)
conved_a = conv(a)  

# 到目前为止只有一个输入,以下可行:
assert conv.input_shape == (None, 32, 32, 3)  

conved_b = conv(b)
# 现在 `.input_shape` 属性不可行,但是这样可以:
assert conv.get_input_shape_at(0) == (None, 32, 32, 3)
assert conv.get_input_shape_at(1) == (None, 64, 64, 3)  


更多的例子

代码示例仍然是起步的最佳方式,所以这里还有更多的例子。

Inception 模型

有关 Inception 结构的更多信息,请参阅 Going Deeper with Convolutions。

from keras.layers import Conv2D, MaxPooling2D, Input  

input_img = Input(shape=(256, 256, 3))  

tower_1 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(input_img)
tower_1 = Conv2D(64, (3, 3), padding=‘same‘, activation=‘relu‘)(tower_1)  

tower_2 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(input_img)
tower_2 = Conv2D(64, (5, 5), padding=‘same‘, activation=‘relu‘)(tower_2)  

tower_3 = MaxPooling2D((3, 3), strides=(1, 1), padding=‘same‘)(input_img)
tower_3 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(tower_3)  

output = keras.layers.concatenate([tower_1, tower_2, tower_3], axis=1)  

卷积层上的残差连接

有关残差网络 (Residual Network) 的更多信息,请参阅 Deep Residual Learning for Image Recognition。

from keras.layers import Conv2D, Input  

# 输入张量为 3 通道 256x256 图像
x = Input(shape=(256, 256, 3))
# 3 输出通道(与输入通道相同)的 3x3 卷积核
y = Conv2D(3, (3, 3), padding=‘same‘)(x)
# 返回 x + y
z = keras.layers.add([x, y])  

共享视觉模型

该模型在两个输入上重复使用同一个图像处理模块,以判断两个 MNIST 数字是否为相同的数字。

from keras.layers import Conv2D, MaxPooling2D, Input, Dense, Flatten
from keras.models import Model  

# 首先,定义视觉模型
digit_input = Input(shape=(27, 27, 1))
x = Conv2D(64, (3, 3))(digit_input)
x = Conv2D(64, (3, 3))(x)
x = MaxPooling2D((2, 2))(x)
out = Flatten()(x)  

vision_model = Model(digit_input, out)  

# 然后,定义区分数字的模型
digit_a = Input(shape=(27, 27, 1))
digit_b = Input(shape=(27, 27, 1))  

# 视觉模型将被共享,包括权重和其他所有
out_a = vision_model(digit_a)
out_b = vision_model(digit_b)  

concatenated = keras.layers.concatenate([out_a, out_b])
out = Dense(1, activation=‘sigmoid‘)(concatenated)  

classification_model = Model([digit_a, digit_b], out)  

视觉问答模型

当被问及关于图片的自然语言问题时,该模型可以选择正确的单词作答。

它通过将问题和图像编码成向量,然后连接两者,在上面训练一个逻辑回归,来从词汇表中挑选一个可能的单词作答。

from keras.layers import Conv2D, MaxPooling2D, Flatten
from keras.layers import Input, LSTM, Embedding, Dense
from keras.models import Model, Sequential  

# 首先,让我们用 Sequential 来定义一个视觉模型。
# 这个模型会把一张图像编码为向量。
vision_model = Sequential()
vision_model.add(Conv2D(64, (3, 3), activation=‘relu‘, padding=‘same‘, input_shape=(224, 224, 3)))
vision_model.add(Conv2D(64, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Conv2D(128, (3, 3), activation=‘relu‘, padding=‘same‘))
vision_model.add(Conv2D(128, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘, padding=‘same‘))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Flatten())  

# 现在让我们用视觉模型来得到一个输出张量:
image_input = Input(shape=(224, 224, 3))
encoded_image = vision_model(image_input)  

# 接下来,定义一个语言模型来将问题编码成一个向量。
# 每个问题最长 100 个词,词的索引从 1 到 9999.
question_input = Input(shape=(100,), dtype=‘int32‘)
embedded_question = Embedding(input_dim=10000, output_dim=256, input_length=100)(question_input)
encoded_question = LSTM(256)(embedded_question)  

# 连接问题向量和图像向量:
merged = keras.layers.concatenate([encoded_question, encoded_image])  

# 然后在上面训练一个 1000 词的逻辑回归模型:
output = Dense(1000, activation=‘softmax‘)(merged)  

# 最终模型:
vqa_model = Model(inputs=[image_input, question_input], outputs=output)  

# 下一步就是在真实数据上训练模型。  

视频问答模型

现在我们已经训练了图像问答模型,我们可以很快地将它转换为视频问答模型。在适当的训练下,你可以给它展示一小段视频(例如 100 帧的人体动作),然后问它一个关于这段视频的问题(例如,「这个人在做什么运动?」 -> 「足球」)。

from keras.layers import TimeDistributed  

video_input = Input(shape=(100, 224, 224, 3))
# 这是基于之前定义的视觉模型(权重被重用)构建的视频编码
encoded_frame_sequence = TimeDistributed(vision_model)(video_input)  # 输出为向量的序列
encoded_video = LSTM(256)(encoded_frame_sequence)  # 输出为一个向量  

# 这是问题编码器的模型级表示,重复使用与之前相同的权重:
question_encoder = Model(inputs=question_input, outputs=encoded_question)  

# 让我们用它来编码这个问题:
video_question_input = Input(shape=(100,), dtype=‘int32‘)
encoded_video_question = question_encoder(video_question_input)  

# 这就是我们的视频问答模式:
merged = keras.layers.concatenate([encoded_video, encoded_video_question])
output = Dense(1000, activation=‘softmax‘)(merged)
video_qa_model = Model(inputs=[video_input, video_question_input], outputs=output)  

Next Previous感谢作者分享-http://bjbsair.com/2020-04-07/tech-info/30658.html

Keras 函数式 API 是定义复杂模型(如多输出模型、有向无环图,或具有共享层的模型)的方法。

这部分文档假设你已经对 Sequential 顺序模型比较熟悉。

让我们先从一些简单的例子开始。


例一:全连接网络

Sequential 模型可能是实现这种网络的一个更好选择,但这个例子能够帮助我们进行一些简单的理解。

  • 网络层的实例是可调用的,它以张量为参数,并且返回一个张量
  • 输入和输出均为张量,它们都可以用来定义一个模型(Model)
  • 这样的模型同 Keras 的 Sequential 模型一样,都可以被训练
from keras.layers import Input, Dense
from keras.models import Model  

# 这部分返回一个张量
inputs = Input(shape=(784,))  

# 层的实例是可调用的,它以张量为参数,并且返回一个张量
x = Dense(64, activation=‘relu‘)(inputs)
x = Dense(64, activation=‘relu‘)(x)
predictions = Dense(10, activation=‘softmax‘)(x)  

# 这部分创建了一个包含输入层和三个全连接层的模型
model = Model(inputs=inputs, outputs=predictions)
model.compile(optimizer=‘rmsprop‘,
              loss=‘categorical_crossentropy‘,
              metrics=[‘accuracy‘])
model.fit(data, labels)  # 开始训练  


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

利用函数式 API,可以轻易地重用训练好的模型:可以将任何模型看作是一个层,然后通过传递一个张量来调用它。注意,在调用模型时,您不仅重用模型的结构,还重用了它的权重。

x = Input(shape=(784,))
# 这是可行的,并且返回上面定义的 10-way softmax。
y = model(x)  

这种方式能允许我们快速创建可以处理序列输入的模型。只需一行代码,你就将图像分类模型转换为视频分类模型。

from keras.layers import TimeDistributed  

# 输入张量是 20 个时间步的序列,
# 每一个时间为一个 784 维的向量
input_sequences = Input(shape=(20, 784))  

# 这部分将我们之前定义的模型应用于输入序列中的每个时间步。
# 之前定义的模型的输出是一个 10-way softmax,
# 因而下面的层的输出将是维度为 10 的 20 个向量的序列。
processed_sequences = TimeDistributed(model)(input_sequences)  


多输入多输出模型

以下是函数式 API 的一个很好的例子:具有多个输入和输出的模型。函数式 API 使处理大量交织的数据流变得容易。

来考虑下面的模型。我们试图预测 Twitter 上的一条新闻标题有多少转发和点赞数。模型的主要输入将是新闻标题本身,即一系列词语,但是为了增添趣味,我们的模型还添加了其他的辅助输入来接收额外的数据,例如新闻标题的发布的时间等。 该模型也将通过两个损失函数进行监督学习。较早地在模型中使用主损失函数,是深度学习模型的一个良好正则方法。

模型结构如下图所示:

让我们用函数式 API 来实现它。

主要输入接收新闻标题本身,即一个整数序列(每个整数编码一个词)。 这些整数在 1 到 10,000 之间(10,000 个词的词汇表),且序列长度为 100 个词。

from keras.layers import Input, Embedding, LSTM, Dense
from keras.models import Model  

# 标题输入:接收一个含有 100 个整数的序列,每个整数在 1 到 10000 之间。
# 注意我们可以通过传递一个 "name" 参数来命名任何层。
main_input = Input(shape=(100,), dtype=‘int32‘, name=‘main_input‘)  

# Embedding 层将输入序列编码为一个稠密向量的序列,
# 每个向量维度为 512。
x = Embedding(output_dim=512, input_dim=10000, input_length=100)(main_input)  

# LSTM 层把向量序列转换成单个向量,
# 它包含整个序列的上下文信息
lstm_out = LSTM(32)(x)  

在这里,我们插入辅助损失,使得即使在模型主损失很高的情况下,LSTM 层和 Embedding 层都能被平稳地训练。

auxiliary_output = Dense(1, activation=‘sigmoid‘, name=‘aux_output‘)(lstm_out)  

此时,我们将辅助输入数据与 LSTM 层的输出连接起来,输入到模型中:

auxiliary_input = Input(shape=(5,), name=‘aux_input‘)
x = keras.layers.concatenate([lstm_out, auxiliary_input])  

# 堆叠多个全连接网络层
x = Dense(64, activation=‘relu‘)(x)
x = Dense(64, activation=‘relu‘)(x)
x = Dense(64, activation=‘relu‘)(x)  

# 最后添加主要的逻辑回归层
main_output = Dense(1, activation=‘sigmoid‘, name=‘main_output‘)(x)  

然后定义一个具有两个输入和两个输出的模型:

model = Model(inputs=[main_input, auxiliary_input], outputs=[main_output, auxiliary_output])  

现在编译模型,并给辅助损失分配一个 0.2 的权重。如果要为不同的输出指定不同的 loss_weights 或 loss,可以使用列表或字典。 在这里,我们给 loss 参数传递单个损失函数,这个损失将用于所有的输出。

model.compile(optimizer=‘rmsprop‘, loss=‘binary_crossentropy‘,
              loss_weights=[1., 0.2])  

我们可以通过传递输入数组和目标数组的列表来训练模型:

model.fit([headline_data, additional_data], [labels, labels],
          epochs=50, batch_size=32)  

由于输入和输出均被命名了(在定义时传递了一个 name 参数),我们也可以通过以下方式编译模型:

model.compile(optimizer=‘rmsprop‘,
              loss={‘main_output‘: ‘binary_crossentropy‘, ‘aux_output‘: ‘binary_crossentropy‘},
              loss_weights={‘main_output‘: 1., ‘aux_output‘: 0.2})  

# 然后使用以下方式训练:
model.fit({‘main_input‘: headline_data, ‘aux_input‘: additional_data},
          {‘main_output‘: labels, ‘aux_output‘: labels},
          epochs=50, batch_size=32)  


共享网络层

函数式 API 的另一个用途是使用共享网络层的模型。我们来看看共享层。

来考虑推特推文数据集。我们想要建立一个模型来分辨两条推文是否来自同一个人(例如,通过推文的相似性来对用户进行比较)。

由于这个问题是对称的,编码第一条推文的机制应该被完全重用来编码第二条推文(权重及其他全部)。这里我们使用一个共享的 LSTM 层来编码推文。

让我们使用函数式 API 来构建它。首先我们将一条推特转换为一个尺寸为 (280, 256) 的矩阵,即每条推特 280 字符,每个字符为 256 维的 one-hot 编码向量 (取 256 个常用字符)。

import keras
from keras.layers import Input, LSTM, Dense
from keras.models import Model  

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

要在不同的输入上共享同一个层,只需实例化该层一次,然后根据需要传入你想要的输入即可:

# 这一层可以输入一个矩阵,并返回一个 64 维的向量
shared_lstm = LSTM(64)  

# 当我们重用相同的图层实例多次,图层的权重也会被重用 (它其实就是同一层)
encoded_a = shared_lstm(tweet_a)
encoded_b = shared_lstm(tweet_b)  

# 然后再连接两个向量:
merged_vector = keras.layers.concatenate([encoded_a, encoded_b], axis=-1)  

# 再在上面添加一个逻辑回归层
predictions = Dense(1, activation=‘sigmoid‘)(merged_vector)  

# 定义一个连接推特输入和预测的可训练的模型
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)  

让我们暂停一会,看看如何读取共享层的输出或输出尺寸。


层「节点」的概念

每当你在某个输入上调用一个层时,都将创建一个新的张量(层的输出),并且为该层添加一个「节点」,将输入张量连接到输出张量。当多次调用同一个图层时,该图层将拥有多个节点索引 (0, 1, 2...)。

在之前版本的 Keras 中,可以通过 layer.get_output() 来获得层实例的输出张量,或者通过 layer.output_shape 来获取其输出形状。现在你依然可以这么做(除了 get_output() 已经被 output 属性替代)。但是如果一个层与多个输入连接呢?

只要一个层仅仅连接到一个输入,就不会有困惑,.output 会返回层的唯一输出:

a = Input(shape=(280, 256))  

lstm = LSTM(32)
encoded_a = lstm(a)  

assert lstm.output == encoded_a  

但是如果该层有多个输入,那就会出现问题:

a = Input(shape=(280, 256))
b = Input(shape=(280, 256))  

lstm = LSTM(32)
encoded_a = lstm(a)
encoded_b = lstm(b)  

lstm.output  

>> AttributeError: Layer lstm_1 has multiple inbound nodes,
hence the notion of "layer output" is ill-defined.
Use `get_output_at(node_index)` instead.  

好吧,通过下面的方法可以解决:

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

够简单,对吧?

input_shape 和 output_shape 这两个属性也是如此:只要该层只有一个节点,或者只要所有节点具有相同的输入/输出尺寸,那么「层输出/输入尺寸」的概念就被很好地定义,并且将由 layer.output_shape / layer.input_shape 返回。但是比如说,如果将一个 Conv2D 层先应用于尺寸为 (32,32,3) 的输入,再应用于尺寸为 (64, 64, 3) 的输入,那么这个层就会有多个输入/输出尺寸,你将不得不通过指定它们所属节点的索引来获取它们:

a = Input(shape=(32, 32, 3))
b = Input(shape=(64, 64, 3))  

conv = Conv2D(16, (3, 3), padding=‘same‘)
conved_a = conv(a)  

# 到目前为止只有一个输入,以下可行:
assert conv.input_shape == (None, 32, 32, 3)  

conved_b = conv(b)
# 现在 `.input_shape` 属性不可行,但是这样可以:
assert conv.get_input_shape_at(0) == (None, 32, 32, 3)
assert conv.get_input_shape_at(1) == (None, 64, 64, 3)  


更多的例子

代码示例仍然是起步的最佳方式,所以这里还有更多的例子。

Inception 模型

有关 Inception 结构的更多信息,请参阅 Going Deeper with Convolutions。

from keras.layers import Conv2D, MaxPooling2D, Input  

input_img = Input(shape=(256, 256, 3))  

tower_1 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(input_img)
tower_1 = Conv2D(64, (3, 3), padding=‘same‘, activation=‘relu‘)(tower_1)  

tower_2 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(input_img)
tower_2 = Conv2D(64, (5, 5), padding=‘same‘, activation=‘relu‘)(tower_2)  

tower_3 = MaxPooling2D((3, 3), strides=(1, 1), padding=‘same‘)(input_img)
tower_3 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(tower_3)  

output = keras.layers.concatenate([tower_1, tower_2, tower_3], axis=1)  

卷积层上的残差连接

有关残差网络 (Residual Network) 的更多信息,请参阅 Deep Residual Learning for Image Recognition。

from keras.layers import Conv2D, Input  

# 输入张量为 3 通道 256x256 图像
x = Input(shape=(256, 256, 3))
# 3 输出通道(与输入通道相同)的 3x3 卷积核
y = Conv2D(3, (3, 3), padding=‘same‘)(x)
# 返回 x + y
z = keras.layers.add([x, y])  

共享视觉模型

该模型在两个输入上重复使用同一个图像处理模块,以判断两个 MNIST 数字是否为相同的数字。

from keras.layers import Conv2D, MaxPooling2D, Input, Dense, Flatten
from keras.models import Model  

# 首先,定义视觉模型
digit_input = Input(shape=(27, 27, 1))
x = Conv2D(64, (3, 3))(digit_input)
x = Conv2D(64, (3, 3))(x)
x = MaxPooling2D((2, 2))(x)
out = Flatten()(x)  

vision_model = Model(digit_input, out)  

# 然后,定义区分数字的模型
digit_a = Input(shape=(27, 27, 1))
digit_b = Input(shape=(27, 27, 1))  

# 视觉模型将被共享,包括权重和其他所有
out_a = vision_model(digit_a)
out_b = vision_model(digit_b)  

concatenated = keras.layers.concatenate([out_a, out_b])
out = Dense(1, activation=‘sigmoid‘)(concatenated)  

classification_model = Model([digit_a, digit_b], out)  

视觉问答模型

当被问及关于图片的自然语言问题时,该模型可以选择正确的单词作答。

它通过将问题和图像编码成向量,然后连接两者,在上面训练一个逻辑回归,来从词汇表中挑选一个可能的单词作答。

from keras.layers import Conv2D, MaxPooling2D, Flatten
from keras.layers import Input, LSTM, Embedding, Dense
from keras.models import Model, Sequential  

# 首先,让我们用 Sequential 来定义一个视觉模型。
# 这个模型会把一张图像编码为向量。
vision_model = Sequential()
vision_model.add(Conv2D(64, (3, 3), activation=‘relu‘, padding=‘same‘, input_shape=(224, 224, 3)))
vision_model.add(Conv2D(64, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Conv2D(128, (3, 3), activation=‘relu‘, padding=‘same‘))
vision_model.add(Conv2D(128, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘, padding=‘same‘))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Flatten())  

# 现在让我们用视觉模型来得到一个输出张量:
image_input = Input(shape=(224, 224, 3))
encoded_image = vision_model(image_input)  

# 接下来,定义一个语言模型来将问题编码成一个向量。
# 每个问题最长 100 个词,词的索引从 1 到 9999.
question_input = Input(shape=(100,), dtype=‘int32‘)
embedded_question = Embedding(input_dim=10000, output_dim=256, input_length=100)(question_input)
encoded_question = LSTM(256)(embedded_question)  

# 连接问题向量和图像向量:
merged = keras.layers.concatenate([encoded_question, encoded_image])  

# 然后在上面训练一个 1000 词的逻辑回归模型:
output = Dense(1000, activation=‘softmax‘)(merged)  

# 最终模型:
vqa_model = Model(inputs=[image_input, question_input], outputs=output)  

# 下一步就是在真实数据上训练模型。  

视频问答模型

现在我们已经训练了图像问答模型,我们可以很快地将它转换为视频问答模型。在适当的训练下,你可以给它展示一小段视频(例如 100 帧的人体动作),然后问它一个关于这段视频的问题(例如,「这个人在做什么运动?」 -> 「足球」)。

from keras.layers import TimeDistributed  

video_input = Input(shape=(100, 224, 224, 3))
# 这是基于之前定义的视觉模型(权重被重用)构建的视频编码
encoded_frame_sequence = TimeDistributed(vision_model)(video_input)  # 输出为向量的序列
encoded_video = LSTM(256)(encoded_frame_sequence)  # 输出为一个向量  

# 这是问题编码器的模型级表示,重复使用与之前相同的权重:
question_encoder = Model(inputs=question_input, outputs=encoded_question)  

# 让我们用它来编码这个问题:
video_question_input = Input(shape=(100,), dtype=‘int32‘)
encoded_video_question = question_encoder(video_question_input)  

# 这就是我们的视频问答模式:
merged = keras.layers.concatenate([encoded_video, encoded_video_question])
output = Dense(1000, activation=‘softmax‘)(merged)
video_qa_model = Model(inputs=[video_input, video_question_input], outputs=output)  

Next Previous感谢作者分享-http://bjbsair.com/2020-04-07/tech-info/30658.html

Keras 函数式 API 是定义复杂模型(如多输出模型、有向无环图,或具有共享层的模型)的方法。

这部分文档假设你已经对 Sequential 顺序模型比较熟悉。

让我们先从一些简单的例子开始。


例一:全连接网络

Sequential 模型可能是实现这种网络的一个更好选择,但这个例子能够帮助我们进行一些简单的理解。

  • 网络层的实例是可调用的,它以张量为参数,并且返回一个张量
  • 输入和输出均为张量,它们都可以用来定义一个模型(Model)
  • 这样的模型同 Keras 的 Sequential 模型一样,都可以被训练
from keras.layers import Input, Dense
from keras.models import Model  

# 这部分返回一个张量
inputs = Input(shape=(784,))  

# 层的实例是可调用的,它以张量为参数,并且返回一个张量
x = Dense(64, activation=‘relu‘)(inputs)
x = Dense(64, activation=‘relu‘)(x)
predictions = Dense(10, activation=‘softmax‘)(x)  

# 这部分创建了一个包含输入层和三个全连接层的模型
model = Model(inputs=inputs, outputs=predictions)
model.compile(optimizer=‘rmsprop‘,
              loss=‘categorical_crossentropy‘,
              metrics=[‘accuracy‘])
model.fit(data, labels)  # 开始训练  


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

利用函数式 API,可以轻易地重用训练好的模型:可以将任何模型看作是一个层,然后通过传递一个张量来调用它。注意,在调用模型时,您不仅重用模型的结构,还重用了它的权重。

x = Input(shape=(784,))
# 这是可行的,并且返回上面定义的 10-way softmax。
y = model(x)  

这种方式能允许我们快速创建可以处理序列输入的模型。只需一行代码,你就将图像分类模型转换为视频分类模型。

from keras.layers import TimeDistributed  

# 输入张量是 20 个时间步的序列,
# 每一个时间为一个 784 维的向量
input_sequences = Input(shape=(20, 784))  

# 这部分将我们之前定义的模型应用于输入序列中的每个时间步。
# 之前定义的模型的输出是一个 10-way softmax,
# 因而下面的层的输出将是维度为 10 的 20 个向量的序列。
processed_sequences = TimeDistributed(model)(input_sequences)  


多输入多输出模型

以下是函数式 API 的一个很好的例子:具有多个输入和输出的模型。函数式 API 使处理大量交织的数据流变得容易。

来考虑下面的模型。我们试图预测 Twitter 上的一条新闻标题有多少转发和点赞数。模型的主要输入将是新闻标题本身,即一系列词语,但是为了增添趣味,我们的模型还添加了其他的辅助输入来接收额外的数据,例如新闻标题的发布的时间等。 该模型也将通过两个损失函数进行监督学习。较早地在模型中使用主损失函数,是深度学习模型的一个良好正则方法。

模型结构如下图所示:

让我们用函数式 API 来实现它。

主要输入接收新闻标题本身,即一个整数序列(每个整数编码一个词)。 这些整数在 1 到 10,000 之间(10,000 个词的词汇表),且序列长度为 100 个词。

from keras.layers import Input, Embedding, LSTM, Dense
from keras.models import Model  

# 标题输入:接收一个含有 100 个整数的序列,每个整数在 1 到 10000 之间。
# 注意我们可以通过传递一个 "name" 参数来命名任何层。
main_input = Input(shape=(100,), dtype=‘int32‘, name=‘main_input‘)  

# Embedding 层将输入序列编码为一个稠密向量的序列,
# 每个向量维度为 512。
x = Embedding(output_dim=512, input_dim=10000, input_length=100)(main_input)  

# LSTM 层把向量序列转换成单个向量,
# 它包含整个序列的上下文信息
lstm_out = LSTM(32)(x)  

在这里,我们插入辅助损失,使得即使在模型主损失很高的情况下,LSTM 层和 Embedding 层都能被平稳地训练。

auxiliary_output = Dense(1, activation=‘sigmoid‘, name=‘aux_output‘)(lstm_out)  

此时,我们将辅助输入数据与 LSTM 层的输出连接起来,输入到模型中:

auxiliary_input = Input(shape=(5,), name=‘aux_input‘)
x = keras.layers.concatenate([lstm_out, auxiliary_input])  

# 堆叠多个全连接网络层
x = Dense(64, activation=‘relu‘)(x)
x = Dense(64, activation=‘relu‘)(x)
x = Dense(64, activation=‘relu‘)(x)  

# 最后添加主要的逻辑回归层
main_output = Dense(1, activation=‘sigmoid‘, name=‘main_output‘)(x)  

然后定义一个具有两个输入和两个输出的模型:

model = Model(inputs=[main_input, auxiliary_input], outputs=[main_output, auxiliary_output])  

现在编译模型,并给辅助损失分配一个 0.2 的权重。如果要为不同的输出指定不同的 loss_weights 或 loss,可以使用列表或字典。 在这里,我们给 loss 参数传递单个损失函数,这个损失将用于所有的输出。

model.compile(optimizer=‘rmsprop‘, loss=‘binary_crossentropy‘,
              loss_weights=[1., 0.2])  

我们可以通过传递输入数组和目标数组的列表来训练模型:

model.fit([headline_data, additional_data], [labels, labels],
          epochs=50, batch_size=32)  

由于输入和输出均被命名了(在定义时传递了一个 name 参数),我们也可以通过以下方式编译模型:

model.compile(optimizer=‘rmsprop‘,
              loss={‘main_output‘: ‘binary_crossentropy‘, ‘aux_output‘: ‘binary_crossentropy‘},
              loss_weights={‘main_output‘: 1., ‘aux_output‘: 0.2})  

# 然后使用以下方式训练:
model.fit({‘main_input‘: headline_data, ‘aux_input‘: additional_data},
          {‘main_output‘: labels, ‘aux_output‘: labels},
          epochs=50, batch_size=32)  


共享网络层

函数式 API 的另一个用途是使用共享网络层的模型。我们来看看共享层。

来考虑推特推文数据集。我们想要建立一个模型来分辨两条推文是否来自同一个人(例如,通过推文的相似性来对用户进行比较)。

由于这个问题是对称的,编码第一条推文的机制应该被完全重用来编码第二条推文(权重及其他全部)。这里我们使用一个共享的 LSTM 层来编码推文。

让我们使用函数式 API 来构建它。首先我们将一条推特转换为一个尺寸为 (280, 256) 的矩阵,即每条推特 280 字符,每个字符为 256 维的 one-hot 编码向量 (取 256 个常用字符)。

import keras
from keras.layers import Input, LSTM, Dense
from keras.models import Model  

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

要在不同的输入上共享同一个层,只需实例化该层一次,然后根据需要传入你想要的输入即可:

# 这一层可以输入一个矩阵,并返回一个 64 维的向量
shared_lstm = LSTM(64)  

# 当我们重用相同的图层实例多次,图层的权重也会被重用 (它其实就是同一层)
encoded_a = shared_lstm(tweet_a)
encoded_b = shared_lstm(tweet_b)  

# 然后再连接两个向量:
merged_vector = keras.layers.concatenate([encoded_a, encoded_b], axis=-1)  

# 再在上面添加一个逻辑回归层
predictions = Dense(1, activation=‘sigmoid‘)(merged_vector)  

# 定义一个连接推特输入和预测的可训练的模型
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)  

让我们暂停一会,看看如何读取共享层的输出或输出尺寸。


层「节点」的概念

每当你在某个输入上调用一个层时,都将创建一个新的张量(层的输出),并且为该层添加一个「节点」,将输入张量连接到输出张量。当多次调用同一个图层时,该图层将拥有多个节点索引 (0, 1, 2...)。

在之前版本的 Keras 中,可以通过 layer.get_output() 来获得层实例的输出张量,或者通过 layer.output_shape 来获取其输出形状。现在你依然可以这么做(除了 get_output() 已经被 output 属性替代)。但是如果一个层与多个输入连接呢?

只要一个层仅仅连接到一个输入,就不会有困惑,.output 会返回层的唯一输出:

a = Input(shape=(280, 256))  

lstm = LSTM(32)
encoded_a = lstm(a)  

assert lstm.output == encoded_a  

但是如果该层有多个输入,那就会出现问题:

a = Input(shape=(280, 256))
b = Input(shape=(280, 256))  

lstm = LSTM(32)
encoded_a = lstm(a)
encoded_b = lstm(b)  

lstm.output  

>> AttributeError: Layer lstm_1 has multiple inbound nodes,
hence the notion of "layer output" is ill-defined.
Use `get_output_at(node_index)` instead.  

好吧,通过下面的方法可以解决:

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

够简单,对吧?

input_shape 和 output_shape 这两个属性也是如此:只要该层只有一个节点,或者只要所有节点具有相同的输入/输出尺寸,那么「层输出/输入尺寸」的概念就被很好地定义,并且将由 layer.output_shape / layer.input_shape 返回。但是比如说,如果将一个 Conv2D 层先应用于尺寸为 (32,32,3) 的输入,再应用于尺寸为 (64, 64, 3) 的输入,那么这个层就会有多个输入/输出尺寸,你将不得不通过指定它们所属节点的索引来获取它们:

a = Input(shape=(32, 32, 3))
b = Input(shape=(64, 64, 3))  

conv = Conv2D(16, (3, 3), padding=‘same‘)
conved_a = conv(a)  

# 到目前为止只有一个输入,以下可行:
assert conv.input_shape == (None, 32, 32, 3)  

conved_b = conv(b)
# 现在 `.input_shape` 属性不可行,但是这样可以:
assert conv.get_input_shape_at(0) == (None, 32, 32, 3)
assert conv.get_input_shape_at(1) == (None, 64, 64, 3)  


更多的例子

代码示例仍然是起步的最佳方式,所以这里还有更多的例子。

Inception 模型

有关 Inception 结构的更多信息,请参阅 Going Deeper with Convolutions。

from keras.layers import Conv2D, MaxPooling2D, Input  

input_img = Input(shape=(256, 256, 3))  

tower_1 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(input_img)
tower_1 = Conv2D(64, (3, 3), padding=‘same‘, activation=‘relu‘)(tower_1)  

tower_2 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(input_img)
tower_2 = Conv2D(64, (5, 5), padding=‘same‘, activation=‘relu‘)(tower_2)  

tower_3 = MaxPooling2D((3, 3), strides=(1, 1), padding=‘same‘)(input_img)
tower_3 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(tower_3)  

output = keras.layers.concatenate([tower_1, tower_2, tower_3], axis=1)  

卷积层上的残差连接

有关残差网络 (Residual Network) 的更多信息,请参阅 Deep Residual Learning for Image Recognition。

from keras.layers import Conv2D, Input  

# 输入张量为 3 通道 256x256 图像
x = Input(shape=(256, 256, 3))
# 3 输出通道(与输入通道相同)的 3x3 卷积核
y = Conv2D(3, (3, 3), padding=‘same‘)(x)
# 返回 x + y
z = keras.layers.add([x, y])  

共享视觉模型

该模型在两个输入上重复使用同一个图像处理模块,以判断两个 MNIST 数字是否为相同的数字。

from keras.layers import Conv2D, MaxPooling2D, Input, Dense, Flatten
from keras.models import Model  

# 首先,定义视觉模型
digit_input = Input(shape=(27, 27, 1))
x = Conv2D(64, (3, 3))(digit_input)
x = Conv2D(64, (3, 3))(x)
x = MaxPooling2D((2, 2))(x)
out = Flatten()(x)  

vision_model = Model(digit_input, out)  

# 然后,定义区分数字的模型
digit_a = Input(shape=(27, 27, 1))
digit_b = Input(shape=(27, 27, 1))  

# 视觉模型将被共享,包括权重和其他所有
out_a = vision_model(digit_a)
out_b = vision_model(digit_b)  

concatenated = keras.layers.concatenate([out_a, out_b])
out = Dense(1, activation=‘sigmoid‘)(concatenated)  

classification_model = Model([digit_a, digit_b], out)  

视觉问答模型

当被问及关于图片的自然语言问题时,该模型可以选择正确的单词作答。

它通过将问题和图像编码成向量,然后连接两者,在上面训练一个逻辑回归,来从词汇表中挑选一个可能的单词作答。

from keras.layers import Conv2D, MaxPooling2D, Flatten
from keras.layers import Input, LSTM, Embedding, Dense
from keras.models import Model, Sequential  

# 首先,让我们用 Sequential 来定义一个视觉模型。
# 这个模型会把一张图像编码为向量。
vision_model = Sequential()
vision_model.add(Conv2D(64, (3, 3), activation=‘relu‘, padding=‘same‘, input_shape=(224, 224, 3)))
vision_model.add(Conv2D(64, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Conv2D(128, (3, 3), activation=‘relu‘, padding=‘same‘))
vision_model.add(Conv2D(128, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘, padding=‘same‘))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Flatten())  

# 现在让我们用视觉模型来得到一个输出张量:
image_input = Input(shape=(224, 224, 3))
encoded_image = vision_model(image_input)  

# 接下来,定义一个语言模型来将问题编码成一个向量。
# 每个问题最长 100 个词,词的索引从 1 到 9999.
question_input = Input(shape=(100,), dtype=‘int32‘)
embedded_question = Embedding(input_dim=10000, output_dim=256, input_length=100)(question_input)
encoded_question = LSTM(256)(embedded_question)  

# 连接问题向量和图像向量:
merged = keras.layers.concatenate([encoded_question, encoded_image])  

# 然后在上面训练一个 1000 词的逻辑回归模型:
output = Dense(1000, activation=‘softmax‘)(merged)  

# 最终模型:
vqa_model = Model(inputs=[image_input, question_input], outputs=output)  

# 下一步就是在真实数据上训练模型。  

视频问答模型

现在我们已经训练了图像问答模型,我们可以很快地将它转换为视频问答模型。在适当的训练下,你可以给它展示一小段视频(例如 100 帧的人体动作),然后问它一个关于这段视频的问题(例如,「这个人在做什么运动?」 -> 「足球」)。

from keras.layers import TimeDistributed  

video_input = Input(shape=(100, 224, 224, 3))
# 这是基于之前定义的视觉模型(权重被重用)构建的视频编码
encoded_frame_sequence = TimeDistributed(vision_model)(video_input)  # 输出为向量的序列
encoded_video = LSTM(256)(encoded_frame_sequence)  # 输出为一个向量  

# 这是问题编码器的模型级表示,重复使用与之前相同的权重:
question_encoder = Model(inputs=question_input, outputs=encoded_question)  

# 让我们用它来编码这个问题:
video_question_input = Input(shape=(100,), dtype=‘int32‘)
encoded_video_question = question_encoder(video_question_input)  

# 这就是我们的视频问答模式:
merged = keras.layers.concatenate([encoded_video, encoded_video_question])
output = Dense(1000, activation=‘softmax‘)(merged)
video_qa_model = Model(inputs=[video_input, video_question_input], outputs=output)  

Next Previous感谢作者分享-http://bjbsair.com/2020-04-07/tech-info/30658.html

Keras 函数式 API 是定义复杂模型(如多输出模型、有向无环图,或具有共享层的模型)的方法。

这部分文档假设你已经对 Sequential 顺序模型比较熟悉。

让我们先从一些简单的例子开始。


例一:全连接网络

Sequential 模型可能是实现这种网络的一个更好选择,但这个例子能够帮助我们进行一些简单的理解。

  • 网络层的实例是可调用的,它以张量为参数,并且返回一个张量
  • 输入和输出均为张量,它们都可以用来定义一个模型(Model)
  • 这样的模型同 Keras 的 Sequential 模型一样,都可以被训练
from keras.layers import Input, Dense
from keras.models import Model  

# 这部分返回一个张量
inputs = Input(shape=(784,))  

# 层的实例是可调用的,它以张量为参数,并且返回一个张量
x = Dense(64, activation=‘relu‘)(inputs)
x = Dense(64, activation=‘relu‘)(x)
predictions = Dense(10, activation=‘softmax‘)(x)  

# 这部分创建了一个包含输入层和三个全连接层的模型
model = Model(inputs=inputs, outputs=predictions)
model.compile(optimizer=‘rmsprop‘,
              loss=‘categorical_crossentropy‘,
              metrics=[‘accuracy‘])
model.fit(data, labels)  # 开始训练  


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

利用函数式 API,可以轻易地重用训练好的模型:可以将任何模型看作是一个层,然后通过传递一个张量来调用它。注意,在调用模型时,您不仅重用模型的结构,还重用了它的权重。

x = Input(shape=(784,))
# 这是可行的,并且返回上面定义的 10-way softmax。
y = model(x)  

这种方式能允许我们快速创建可以处理序列输入的模型。只需一行代码,你就将图像分类模型转换为视频分类模型。

from keras.layers import TimeDistributed  

# 输入张量是 20 个时间步的序列,
# 每一个时间为一个 784 维的向量
input_sequences = Input(shape=(20, 784))  

# 这部分将我们之前定义的模型应用于输入序列中的每个时间步。
# 之前定义的模型的输出是一个 10-way softmax,
# 因而下面的层的输出将是维度为 10 的 20 个向量的序列。
processed_sequences = TimeDistributed(model)(input_sequences)  


多输入多输出模型

以下是函数式 API 的一个很好的例子:具有多个输入和输出的模型。函数式 API 使处理大量交织的数据流变得容易。

来考虑下面的模型。我们试图预测 Twitter 上的一条新闻标题有多少转发和点赞数。模型的主要输入将是新闻标题本身,即一系列词语,但是为了增添趣味,我们的模型还添加了其他的辅助输入来接收额外的数据,例如新闻标题的发布的时间等。 该模型也将通过两个损失函数进行监督学习。较早地在模型中使用主损失函数,是深度学习模型的一个良好正则方法。

模型结构如下图所示:

让我们用函数式 API 来实现它。

主要输入接收新闻标题本身,即一个整数序列(每个整数编码一个词)。 这些整数在 1 到 10,000 之间(10,000 个词的词汇表),且序列长度为 100 个词。

from keras.layers import Input, Embedding, LSTM, Dense
from keras.models import Model  

# 标题输入:接收一个含有 100 个整数的序列,每个整数在 1 到 10000 之间。
# 注意我们可以通过传递一个 "name" 参数来命名任何层。
main_input = Input(shape=(100,), dtype=‘int32‘, name=‘main_input‘)  

# Embedding 层将输入序列编码为一个稠密向量的序列,
# 每个向量维度为 512。
x = Embedding(output_dim=512, input_dim=10000, input_length=100)(main_input)  

# LSTM 层把向量序列转换成单个向量,
# 它包含整个序列的上下文信息
lstm_out = LSTM(32)(x)  

在这里,我们插入辅助损失,使得即使在模型主损失很高的情况下,LSTM 层和 Embedding 层都能被平稳地训练。

auxiliary_output = Dense(1, activation=‘sigmoid‘, name=‘aux_output‘)(lstm_out)  

此时,我们将辅助输入数据与 LSTM 层的输出连接起来,输入到模型中:

auxiliary_input = Input(shape=(5,), name=‘aux_input‘)
x = keras.layers.concatenate([lstm_out, auxiliary_input])  

# 堆叠多个全连接网络层
x = Dense(64, activation=‘relu‘)(x)
x = Dense(64, activation=‘relu‘)(x)
x = Dense(64, activation=‘relu‘)(x)  

# 最后添加主要的逻辑回归层
main_output = Dense(1, activation=‘sigmoid‘, name=‘main_output‘)(x)  

然后定义一个具有两个输入和两个输出的模型:

model = Model(inputs=[main_input, auxiliary_input], outputs=[main_output, auxiliary_output])  

现在编译模型,并给辅助损失分配一个 0.2 的权重。如果要为不同的输出指定不同的 loss_weights 或 loss,可以使用列表或字典。 在这里,我们给 loss 参数传递单个损失函数,这个损失将用于所有的输出。

model.compile(optimizer=‘rmsprop‘, loss=‘binary_crossentropy‘,
              loss_weights=[1., 0.2])  

我们可以通过传递输入数组和目标数组的列表来训练模型:

model.fit([headline_data, additional_data], [labels, labels],
          epochs=50, batch_size=32)  

由于输入和输出均被命名了(在定义时传递了一个 name 参数),我们也可以通过以下方式编译模型:

model.compile(optimizer=‘rmsprop‘,
              loss={‘main_output‘: ‘binary_crossentropy‘, ‘aux_output‘: ‘binary_crossentropy‘},
              loss_weights={‘main_output‘: 1., ‘aux_output‘: 0.2})  

# 然后使用以下方式训练:
model.fit({‘main_input‘: headline_data, ‘aux_input‘: additional_data},
          {‘main_output‘: labels, ‘aux_output‘: labels},
          epochs=50, batch_size=32)  


共享网络层

函数式 API 的另一个用途是使用共享网络层的模型。我们来看看共享层。

来考虑推特推文数据集。我们想要建立一个模型来分辨两条推文是否来自同一个人(例如,通过推文的相似性来对用户进行比较)。

由于这个问题是对称的,编码第一条推文的机制应该被完全重用来编码第二条推文(权重及其他全部)。这里我们使用一个共享的 LSTM 层来编码推文。

让我们使用函数式 API 来构建它。首先我们将一条推特转换为一个尺寸为 (280, 256) 的矩阵,即每条推特 280 字符,每个字符为 256 维的 one-hot 编码向量 (取 256 个常用字符)。

import keras
from keras.layers import Input, LSTM, Dense
from keras.models import Model  

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

要在不同的输入上共享同一个层,只需实例化该层一次,然后根据需要传入你想要的输入即可:

# 这一层可以输入一个矩阵,并返回一个 64 维的向量
shared_lstm = LSTM(64)  

# 当我们重用相同的图层实例多次,图层的权重也会被重用 (它其实就是同一层)
encoded_a = shared_lstm(tweet_a)
encoded_b = shared_lstm(tweet_b)  

# 然后再连接两个向量:
merged_vector = keras.layers.concatenate([encoded_a, encoded_b], axis=-1)  

# 再在上面添加一个逻辑回归层
predictions = Dense(1, activation=‘sigmoid‘)(merged_vector)  

# 定义一个连接推特输入和预测的可训练的模型
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)  

让我们暂停一会,看看如何读取共享层的输出或输出尺寸。


层「节点」的概念

每当你在某个输入上调用一个层时,都将创建一个新的张量(层的输出),并且为该层添加一个「节点」,将输入张量连接到输出张量。当多次调用同一个图层时,该图层将拥有多个节点索引 (0, 1, 2...)。

在之前版本的 Keras 中,可以通过 layer.get_output() 来获得层实例的输出张量,或者通过 layer.output_shape 来获取其输出形状。现在你依然可以这么做(除了 get_output() 已经被 output 属性替代)。但是如果一个层与多个输入连接呢?

只要一个层仅仅连接到一个输入,就不会有困惑,.output 会返回层的唯一输出:

a = Input(shape=(280, 256))  

lstm = LSTM(32)
encoded_a = lstm(a)  

assert lstm.output == encoded_a  

但是如果该层有多个输入,那就会出现问题:

a = Input(shape=(280, 256))
b = Input(shape=(280, 256))  

lstm = LSTM(32)
encoded_a = lstm(a)
encoded_b = lstm(b)  

lstm.output  

>> AttributeError: Layer lstm_1 has multiple inbound nodes,
hence the notion of "layer output" is ill-defined.
Use `get_output_at(node_index)` instead.  

好吧,通过下面的方法可以解决:

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

够简单,对吧?

input_shape 和 output_shape 这两个属性也是如此:只要该层只有一个节点,或者只要所有节点具有相同的输入/输出尺寸,那么「层输出/输入尺寸」的概念就被很好地定义,并且将由 layer.output_shape / layer.input_shape 返回。但是比如说,如果将一个 Conv2D 层先应用于尺寸为 (32,32,3) 的输入,再应用于尺寸为 (64, 64, 3) 的输入,那么这个层就会有多个输入/输出尺寸,你将不得不通过指定它们所属节点的索引来获取它们:

a = Input(shape=(32, 32, 3))
b = Input(shape=(64, 64, 3))  

conv = Conv2D(16, (3, 3), padding=‘same‘)
conved_a = conv(a)  

# 到目前为止只有一个输入,以下可行:
assert conv.input_shape == (None, 32, 32, 3)  

conved_b = conv(b)
# 现在 `.input_shape` 属性不可行,但是这样可以:
assert conv.get_input_shape_at(0) == (None, 32, 32, 3)
assert conv.get_input_shape_at(1) == (None, 64, 64, 3)  


更多的例子

代码示例仍然是起步的最佳方式,所以这里还有更多的例子。

Inception 模型

有关 Inception 结构的更多信息,请参阅 Going Deeper with Convolutions。

from keras.layers import Conv2D, MaxPooling2D, Input  

input_img = Input(shape=(256, 256, 3))  

tower_1 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(input_img)
tower_1 = Conv2D(64, (3, 3), padding=‘same‘, activation=‘relu‘)(tower_1)  

tower_2 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(input_img)
tower_2 = Conv2D(64, (5, 5), padding=‘same‘, activation=‘relu‘)(tower_2)  

tower_3 = MaxPooling2D((3, 3), strides=(1, 1), padding=‘same‘)(input_img)
tower_3 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(tower_3)  

output = keras.layers.concatenate([tower_1, tower_2, tower_3], axis=1)  

卷积层上的残差连接

有关残差网络 (Residual Network) 的更多信息,请参阅 Deep Residual Learning for Image Recognition。

from keras.layers import Conv2D, Input  

# 输入张量为 3 通道 256x256 图像
x = Input(shape=(256, 256, 3))
# 3 输出通道(与输入通道相同)的 3x3 卷积核
y = Conv2D(3, (3, 3), padding=‘same‘)(x)
# 返回 x + y
z = keras.layers.add([x, y])  

共享视觉模型

该模型在两个输入上重复使用同一个图像处理模块,以判断两个 MNIST 数字是否为相同的数字。

from keras.layers import Conv2D, MaxPooling2D, Input, Dense, Flatten
from keras.models import Model  

# 首先,定义视觉模型
digit_input = Input(shape=(27, 27, 1))
x = Conv2D(64, (3, 3))(digit_input)
x = Conv2D(64, (3, 3))(x)
x = MaxPooling2D((2, 2))(x)
out = Flatten()(x)  

vision_model = Model(digit_input, out)  

# 然后,定义区分数字的模型
digit_a = Input(shape=(27, 27, 1))
digit_b = Input(shape=(27, 27, 1))  

# 视觉模型将被共享,包括权重和其他所有
out_a = vision_model(digit_a)
out_b = vision_model(digit_b)  

concatenated = keras.layers.concatenate([out_a, out_b])
out = Dense(1, activation=‘sigmoid‘)(concatenated)  

classification_model = Model([digit_a, digit_b], out)  

视觉问答模型

当被问及关于图片的自然语言问题时,该模型可以选择正确的单词作答。

它通过将问题和图像编码成向量,然后连接两者,在上面训练一个逻辑回归,来从词汇表中挑选一个可能的单词作答。

from keras.layers import Conv2D, MaxPooling2D, Flatten
from keras.layers import Input, LSTM, Embedding, Dense
from keras.models import Model, Sequential  

# 首先,让我们用 Sequential 来定义一个视觉模型。
# 这个模型会把一张图像编码为向量。
vision_model = Sequential()
vision_model.add(Conv2D(64, (3, 3), activation=‘relu‘, padding=‘same‘, input_shape=(224, 224, 3)))
vision_model.add(Conv2D(64, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Conv2D(128, (3, 3), activation=‘relu‘, padding=‘same‘))
vision_model.add(Conv2D(128, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘, padding=‘same‘))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Flatten())  

# 现在让我们用视觉模型来得到一个输出张量:
image_input = Input(shape=(224, 224, 3))
encoded_image = vision_model(image_input)  

# 接下来,定义一个语言模型来将问题编码成一个向量。
# 每个问题最长 100 个词,词的索引从 1 到 9999.
question_input = Input(shape=(100,), dtype=‘int32‘)
embedded_question = Embedding(input_dim=10000, output_dim=256, input_length=100)(question_input)
encoded_question = LSTM(256)(embedded_question)  

# 连接问题向量和图像向量:
merged = keras.layers.concatenate([encoded_question, encoded_image])  

# 然后在上面训练一个 1000 词的逻辑回归模型:
output = Dense(1000, activation=‘softmax‘)(merged)  

# 最终模型:
vqa_model = Model(inputs=[image_input, question_input], outputs=output)  

# 下一步就是在真实数据上训练模型。  

视频问答模型

现在我们已经训练了图像问答模型,我们可以很快地将它转换为视频问答模型。在适当的训练下,你可以给它展示一小段视频(例如 100 帧的人体动作),然后问它一个关于这段视频的问题(例如,「这个人在做什么运动?」 -> 「足球」)。

from keras.layers import TimeDistributed  

video_input = Input(shape=(100, 224, 224, 3))
# 这是基于之前定义的视觉模型(权重被重用)构建的视频编码
encoded_frame_sequence = TimeDistributed(vision_model)(video_input)  # 输出为向量的序列
encoded_video = LSTM(256)(encoded_frame_sequence)  # 输出为一个向量  

# 这是问题编码器的模型级表示,重复使用与之前相同的权重:
question_encoder = Model(inputs=question_input, outputs=encoded_question)  

# 让我们用它来编码这个问题:
video_question_input = Input(shape=(100,), dtype=‘int32‘)
encoded_video_question = question_encoder(video_question_input)  

# 这就是我们的视频问答模式:
merged = keras.layers.concatenate([encoded_video, encoded_video_question])
output = Dense(1000, activation=‘softmax‘)(merged)
video_qa_model = Model(inputs=[video_input, video_question_input], outputs=output)  

Next Previous感谢作者分享-http://bjbsair.com/2020-04-07/tech-info/30658.html

Keras 函数式 API 是定义复杂模型(如多输出模型、有向无环图,或具有共享层的模型)的方法。

这部分文档假设你已经对 Sequential 顺序模型比较熟悉。

让我们先从一些简单的例子开始。


例一:全连接网络

Sequential 模型可能是实现这种网络的一个更好选择,但这个例子能够帮助我们进行一些简单的理解。

  • 网络层的实例是可调用的,它以张量为参数,并且返回一个张量
  • 输入和输出均为张量,它们都可以用来定义一个模型(Model)
  • 这样的模型同 Keras 的 Sequential 模型一样,都可以被训练
from keras.layers import Input, Dense
from keras.models import Model  

# 这部分返回一个张量
inputs = Input(shape=(784,))  

# 层的实例是可调用的,它以张量为参数,并且返回一个张量
x = Dense(64, activation=‘relu‘)(inputs)
x = Dense(64, activation=‘relu‘)(x)
predictions = Dense(10, activation=‘softmax‘)(x)  

# 这部分创建了一个包含输入层和三个全连接层的模型
model = Model(inputs=inputs, outputs=predictions)
model.compile(optimizer=‘rmsprop‘,
              loss=‘categorical_crossentropy‘,
              metrics=[‘accuracy‘])
model.fit(data, labels)  # 开始训练  


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

利用函数式 API,可以轻易地重用训练好的模型:可以将任何模型看作是一个层,然后通过传递一个张量来调用它。注意,在调用模型时,您不仅重用模型的结构,还重用了它的权重。

x = Input(shape=(784,))
# 这是可行的,并且返回上面定义的 10-way softmax。
y = model(x)  

这种方式能允许我们快速创建可以处理序列输入的模型。只需一行代码,你就将图像分类模型转换为视频分类模型。

from keras.layers import TimeDistributed  

# 输入张量是 20 个时间步的序列,
# 每一个时间为一个 784 维的向量
input_sequences = Input(shape=(20, 784))  

# 这部分将我们之前定义的模型应用于输入序列中的每个时间步。
# 之前定义的模型的输出是一个 10-way softmax,
# 因而下面的层的输出将是维度为 10 的 20 个向量的序列。
processed_sequences = TimeDistributed(model)(input_sequences)  


多输入多输出模型

以下是函数式 API 的一个很好的例子:具有多个输入和输出的模型。函数式 API 使处理大量交织的数据流变得容易。

来考虑下面的模型。我们试图预测 Twitter 上的一条新闻标题有多少转发和点赞数。模型的主要输入将是新闻标题本身,即一系列词语,但是为了增添趣味,我们的模型还添加了其他的辅助输入来接收额外的数据,例如新闻标题的发布的时间等。 该模型也将通过两个损失函数进行监督学习。较早地在模型中使用主损失函数,是深度学习模型的一个良好正则方法。

模型结构如下图所示:

让我们用函数式 API 来实现它。

主要输入接收新闻标题本身,即一个整数序列(每个整数编码一个词)。 这些整数在 1 到 10,000 之间(10,000 个词的词汇表),且序列长度为 100 个词。

from keras.layers import Input, Embedding, LSTM, Dense
from keras.models import Model  

# 标题输入:接收一个含有 100 个整数的序列,每个整数在 1 到 10000 之间。
# 注意我们可以通过传递一个 "name" 参数来命名任何层。
main_input = Input(shape=(100,), dtype=‘int32‘, name=‘main_input‘)  

# Embedding 层将输入序列编码为一个稠密向量的序列,
# 每个向量维度为 512。
x = Embedding(output_dim=512, input_dim=10000, input_length=100)(main_input)  

# LSTM 层把向量序列转换成单个向量,
# 它包含整个序列的上下文信息
lstm_out = LSTM(32)(x)  

在这里,我们插入辅助损失,使得即使在模型主损失很高的情况下,LSTM 层和 Embedding 层都能被平稳地训练。

auxiliary_output = Dense(1, activation=‘sigmoid‘, name=‘aux_output‘)(lstm_out)  

此时,我们将辅助输入数据与 LSTM 层的输出连接起来,输入到模型中:

auxiliary_input = Input(shape=(5,), name=‘aux_input‘)
x = keras.layers.concatenate([lstm_out, auxiliary_input])  

# 堆叠多个全连接网络层
x = Dense(64, activation=‘relu‘)(x)
x = Dense(64, activation=‘relu‘)(x)
x = Dense(64, activation=‘relu‘)(x)  

# 最后添加主要的逻辑回归层
main_output = Dense(1, activation=‘sigmoid‘, name=‘main_output‘)(x)  

然后定义一个具有两个输入和两个输出的模型:

model = Model(inputs=[main_input, auxiliary_input], outputs=[main_output, auxiliary_output])  

现在编译模型,并给辅助损失分配一个 0.2 的权重。如果要为不同的输出指定不同的 loss_weights 或 loss,可以使用列表或字典。 在这里,我们给 loss 参数传递单个损失函数,这个损失将用于所有的输出。

model.compile(optimizer=‘rmsprop‘, loss=‘binary_crossentropy‘,
              loss_weights=[1., 0.2])  

我们可以通过传递输入数组和目标数组的列表来训练模型:

model.fit([headline_data, additional_data], [labels, labels],
          epochs=50, batch_size=32)  

由于输入和输出均被命名了(在定义时传递了一个 name 参数),我们也可以通过以下方式编译模型:

model.compile(optimizer=‘rmsprop‘,
              loss={‘main_output‘: ‘binary_crossentropy‘, ‘aux_output‘: ‘binary_crossentropy‘},
              loss_weights={‘main_output‘: 1., ‘aux_output‘: 0.2})  

# 然后使用以下方式训练:
model.fit({‘main_input‘: headline_data, ‘aux_input‘: additional_data},
          {‘main_output‘: labels, ‘aux_output‘: labels},
          epochs=50, batch_size=32)  


共享网络层

函数式 API 的另一个用途是使用共享网络层的模型。我们来看看共享层。

来考虑推特推文数据集。我们想要建立一个模型来分辨两条推文是否来自同一个人(例如,通过推文的相似性来对用户进行比较)。

由于这个问题是对称的,编码第一条推文的机制应该被完全重用来编码第二条推文(权重及其他全部)。这里我们使用一个共享的 LSTM 层来编码推文。

让我们使用函数式 API 来构建它。首先我们将一条推特转换为一个尺寸为 (280, 256) 的矩阵,即每条推特 280 字符,每个字符为 256 维的 one-hot 编码向量 (取 256 个常用字符)。

import keras
from keras.layers import Input, LSTM, Dense
from keras.models import Model  

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

要在不同的输入上共享同一个层,只需实例化该层一次,然后根据需要传入你想要的输入即可:

# 这一层可以输入一个矩阵,并返回一个 64 维的向量
shared_lstm = LSTM(64)  

# 当我们重用相同的图层实例多次,图层的权重也会被重用 (它其实就是同一层)
encoded_a = shared_lstm(tweet_a)
encoded_b = shared_lstm(tweet_b)  

# 然后再连接两个向量:
merged_vector = keras.layers.concatenate([encoded_a, encoded_b], axis=-1)  

# 再在上面添加一个逻辑回归层
predictions = Dense(1, activation=‘sigmoid‘)(merged_vector)  

# 定义一个连接推特输入和预测的可训练的模型
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)  

让我们暂停一会,看看如何读取共享层的输出或输出尺寸。


层「节点」的概念

每当你在某个输入上调用一个层时,都将创建一个新的张量(层的输出),并且为该层添加一个「节点」,将输入张量连接到输出张量。当多次调用同一个图层时,该图层将拥有多个节点索引 (0, 1, 2...)。

在之前版本的 Keras 中,可以通过 layer.get_output() 来获得层实例的输出张量,或者通过 layer.output_shape 来获取其输出形状。现在你依然可以这么做(除了 get_output() 已经被 output 属性替代)。但是如果一个层与多个输入连接呢?

只要一个层仅仅连接到一个输入,就不会有困惑,.output 会返回层的唯一输出:

a = Input(shape=(280, 256))  

lstm = LSTM(32)
encoded_a = lstm(a)  

assert lstm.output == encoded_a  

但是如果该层有多个输入,那就会出现问题:

a = Input(shape=(280, 256))
b = Input(shape=(280, 256))  

lstm = LSTM(32)
encoded_a = lstm(a)
encoded_b = lstm(b)  

lstm.output  

>> AttributeError: Layer lstm_1 has multiple inbound nodes,
hence the notion of "layer output" is ill-defined.
Use `get_output_at(node_index)` instead.  

好吧,通过下面的方法可以解决:

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

够简单,对吧?

input_shape 和 output_shape 这两个属性也是如此:只要该层只有一个节点,或者只要所有节点具有相同的输入/输出尺寸,那么「层输出/输入尺寸」的概念就被很好地定义,并且将由 layer.output_shape / layer.input_shape 返回。但是比如说,如果将一个 Conv2D 层先应用于尺寸为 (32,32,3) 的输入,再应用于尺寸为 (64, 64, 3) 的输入,那么这个层就会有多个输入/输出尺寸,你将不得不通过指定它们所属节点的索引来获取它们:

a = Input(shape=(32, 32, 3))
b = Input(shape=(64, 64, 3))  

conv = Conv2D(16, (3, 3), padding=‘same‘)
conved_a = conv(a)  

# 到目前为止只有一个输入,以下可行:
assert conv.input_shape == (None, 32, 32, 3)  

conved_b = conv(b)
# 现在 `.input_shape` 属性不可行,但是这样可以:
assert conv.get_input_shape_at(0) == (None, 32, 32, 3)
assert conv.get_input_shape_at(1) == (None, 64, 64, 3)  


更多的例子

代码示例仍然是起步的最佳方式,所以这里还有更多的例子。

Inception 模型

有关 Inception 结构的更多信息,请参阅 Going Deeper with Convolutions。

from keras.layers import Conv2D, MaxPooling2D, Input  

input_img = Input(shape=(256, 256, 3))  

tower_1 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(input_img)
tower_1 = Conv2D(64, (3, 3), padding=‘same‘, activation=‘relu‘)(tower_1)  

tower_2 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(input_img)
tower_2 = Conv2D(64, (5, 5), padding=‘same‘, activation=‘relu‘)(tower_2)  

tower_3 = MaxPooling2D((3, 3), strides=(1, 1), padding=‘same‘)(input_img)
tower_3 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(tower_3)  

output = keras.layers.concatenate([tower_1, tower_2, tower_3], axis=1)  

卷积层上的残差连接

有关残差网络 (Residual Network) 的更多信息,请参阅 Deep Residual Learning for Image Recognition。

from keras.layers import Conv2D, Input  

# 输入张量为 3 通道 256x256 图像
x = Input(shape=(256, 256, 3))
# 3 输出通道(与输入通道相同)的 3x3 卷积核
y = Conv2D(3, (3, 3), padding=‘same‘)(x)
# 返回 x + y
z = keras.layers.add([x, y])  

共享视觉模型

该模型在两个输入上重复使用同一个图像处理模块,以判断两个 MNIST 数字是否为相同的数字。

from keras.layers import Conv2D, MaxPooling2D, Input, Dense, Flatten
from keras.models import Model  

# 首先,定义视觉模型
digit_input = Input(shape=(27, 27, 1))
x = Conv2D(64, (3, 3))(digit_input)
x = Conv2D(64, (3, 3))(x)
x = MaxPooling2D((2, 2))(x)
out = Flatten()(x)  

vision_model = Model(digit_input, out)  

# 然后,定义区分数字的模型
digit_a = Input(shape=(27, 27, 1))
digit_b = Input(shape=(27, 27, 1))  

# 视觉模型将被共享,包括权重和其他所有
out_a = vision_model(digit_a)
out_b = vision_model(digit_b)  

concatenated = keras.layers.concatenate([out_a, out_b])
out = Dense(1, activation=‘sigmoid‘)(concatenated)  

classification_model = Model([digit_a, digit_b], out)  

视觉问答模型

当被问及关于图片的自然语言问题时,该模型可以选择正确的单词作答。

它通过将问题和图像编码成向量,然后连接两者,在上面训练一个逻辑回归,来从词汇表中挑选一个可能的单词作答。

from keras.layers import Conv2D, MaxPooling2D, Flatten
from keras.layers import Input, LSTM, Embedding, Dense
from keras.models import Model, Sequential  

# 首先,让我们用 Sequential 来定义一个视觉模型。
# 这个模型会把一张图像编码为向量。
vision_model = Sequential()
vision_model.add(Conv2D(64, (3, 3), activation=‘relu‘, padding=‘same‘, input_shape=(224, 224, 3)))
vision_model.add(Conv2D(64, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Conv2D(128, (3, 3), activation=‘relu‘, padding=‘same‘))
vision_model.add(Conv2D(128, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘, padding=‘same‘))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Flatten())  

# 现在让我们用视觉模型来得到一个输出张量:
image_input = Input(shape=(224, 224, 3))
encoded_image = vision_model(image_input)  

# 接下来,定义一个语言模型来将问题编码成一个向量。
# 每个问题最长 100 个词,词的索引从 1 到 9999.
question_input = Input(shape=(100,), dtype=‘int32‘)
embedded_question = Embedding(input_dim=10000, output_dim=256, input_length=100)(question_input)
encoded_question = LSTM(256)(embedded_question)  

# 连接问题向量和图像向量:
merged = keras.layers.concatenate([encoded_question, encoded_image])  

# 然后在上面训练一个 1000 词的逻辑回归模型:
output = Dense(1000, activation=‘softmax‘)(merged)  

# 最终模型:
vqa_model = Model(inputs=[image_input, question_input], outputs=output)  

# 下一步就是在真实数据上训练模型。  

视频问答模型

现在我们已经训练了图像问答模型,我们可以很快地将它转换为视频问答模型。在适当的训练下,你可以给它展示一小段视频(例如 100 帧的人体动作),然后问它一个关于这段视频的问题(例如,「这个人在做什么运动?」 -> 「足球」)。

from keras.layers import TimeDistributed  

video_input = Input(shape=(100, 224, 224, 3))
# 这是基于之前定义的视觉模型(权重被重用)构建的视频编码
encoded_frame_sequence = TimeDistributed(vision_model)(video_input)  # 输出为向量的序列
encoded_video = LSTM(256)(encoded_frame_sequence)  # 输出为一个向量  

# 这是问题编码器的模型级表示,重复使用与之前相同的权重:
question_encoder = Model(inputs=question_input, outputs=encoded_question)  

# 让我们用它来编码这个问题:
video_question_input = Input(shape=(100,), dtype=‘int32‘)
encoded_video_question = question_encoder(video_question_input)  

# 这就是我们的视频问答模式:
merged = keras.layers.concatenate([encoded_video, encoded_video_question])
output = Dense(1000, activation=‘softmax‘)(merged)
video_qa_model = Model(inputs=[video_input, video_question_input], outputs=output)  

Next Previous感谢作者分享-http://bjbsair.com/2020-04-07/tech-info/30658.html

Keras 函数式 API 是定义复杂模型(如多输出模型、有向无环图,或具有共享层的模型)的方法。

这部分文档假设你已经对 Sequential 顺序模型比较熟悉。

让我们先从一些简单的例子开始。


例一:全连接网络

Sequential 模型可能是实现这种网络的一个更好选择,但这个例子能够帮助我们进行一些简单的理解。

  • 网络层的实例是可调用的,它以张量为参数,并且返回一个张量
  • 输入和输出均为张量,它们都可以用来定义一个模型(Model)
  • 这样的模型同 Keras 的 Sequential 模型一样,都可以被训练
from keras.layers import Input, Dense
from keras.models import Model  

# 这部分返回一个张量
inputs = Input(shape=(784,))  

# 层的实例是可调用的,它以张量为参数,并且返回一个张量
x = Dense(64, activation=‘relu‘)(inputs)
x = Dense(64, activation=‘relu‘)(x)
predictions = Dense(10, activation=‘softmax‘)(x)  

# 这部分创建了一个包含输入层和三个全连接层的模型
model = Model(inputs=inputs, outputs=predictions)
model.compile(optimizer=‘rmsprop‘,
              loss=‘categorical_crossentropy‘,
              metrics=[‘accuracy‘])
model.fit(data, labels)  # 开始训练  


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

利用函数式 API,可以轻易地重用训练好的模型:可以将任何模型看作是一个层,然后通过传递一个张量来调用它。注意,在调用模型时,您不仅重用模型的结构,还重用了它的权重。

x = Input(shape=(784,))
# 这是可行的,并且返回上面定义的 10-way softmax。
y = model(x)  

这种方式能允许我们快速创建可以处理序列输入的模型。只需一行代码,你就将图像分类模型转换为视频分类模型。

from keras.layers import TimeDistributed  

# 输入张量是 20 个时间步的序列,
# 每一个时间为一个 784 维的向量
input_sequences = Input(shape=(20, 784))  

# 这部分将我们之前定义的模型应用于输入序列中的每个时间步。
# 之前定义的模型的输出是一个 10-way softmax,
# 因而下面的层的输出将是维度为 10 的 20 个向量的序列。
processed_sequences = TimeDistributed(model)(input_sequences)  


多输入多输出模型

以下是函数式 API 的一个很好的例子:具有多个输入和输出的模型。函数式 API 使处理大量交织的数据流变得容易。

来考虑下面的模型。我们试图预测 Twitter 上的一条新闻标题有多少转发和点赞数。模型的主要输入将是新闻标题本身,即一系列词语,但是为了增添趣味,我们的模型还添加了其他的辅助输入来接收额外的数据,例如新闻标题的发布的时间等。 该模型也将通过两个损失函数进行监督学习。较早地在模型中使用主损失函数,是深度学习模型的一个良好正则方法。

模型结构如下图所示:

让我们用函数式 API 来实现它。

主要输入接收新闻标题本身,即一个整数序列(每个整数编码一个词)。 这些整数在 1 到 10,000 之间(10,000 个词的词汇表),且序列长度为 100 个词。

from keras.layers import Input, Embedding, LSTM, Dense
from keras.models import Model  

# 标题输入:接收一个含有 100 个整数的序列,每个整数在 1 到 10000 之间。
# 注意我们可以通过传递一个 "name" 参数来命名任何层。
main_input = Input(shape=(100,), dtype=‘int32‘, name=‘main_input‘)  

# Embedding 层将输入序列编码为一个稠密向量的序列,
# 每个向量维度为 512。
x = Embedding(output_dim=512, input_dim=10000, input_length=100)(main_input)  

# LSTM 层把向量序列转换成单个向量,
# 它包含整个序列的上下文信息
lstm_out = LSTM(32)(x)  

在这里,我们插入辅助损失,使得即使在模型主损失很高的情况下,LSTM 层和 Embedding 层都能被平稳地训练。

auxiliary_output = Dense(1, activation=‘sigmoid‘, name=‘aux_output‘)(lstm_out)  

此时,我们将辅助输入数据与 LSTM 层的输出连接起来,输入到模型中:

auxiliary_input = Input(shape=(5,), name=‘aux_input‘)
x = keras.layers.concatenate([lstm_out, auxiliary_input])  

# 堆叠多个全连接网络层
x = Dense(64, activation=‘relu‘)(x)
x = Dense(64, activation=‘relu‘)(x)
x = Dense(64, activation=‘relu‘)(x)  

# 最后添加主要的逻辑回归层
main_output = Dense(1, activation=‘sigmoid‘, name=‘main_output‘)(x)  

然后定义一个具有两个输入和两个输出的模型:

model = Model(inputs=[main_input, auxiliary_input], outputs=[main_output, auxiliary_output])  

现在编译模型,并给辅助损失分配一个 0.2 的权重。如果要为不同的输出指定不同的 loss_weights 或 loss,可以使用列表或字典。 在这里,我们给 loss 参数传递单个损失函数,这个损失将用于所有的输出。

model.compile(optimizer=‘rmsprop‘, loss=‘binary_crossentropy‘,
              loss_weights=[1., 0.2])  

我们可以通过传递输入数组和目标数组的列表来训练模型:

model.fit([headline_data, additional_data], [labels, labels],
          epochs=50, batch_size=32)  

由于输入和输出均被命名了(在定义时传递了一个 name 参数),我们也可以通过以下方式编译模型:

model.compile(optimizer=‘rmsprop‘,
              loss={‘main_output‘: ‘binary_crossentropy‘, ‘aux_output‘: ‘binary_crossentropy‘},
              loss_weights={‘main_output‘: 1., ‘aux_output‘: 0.2})  

# 然后使用以下方式训练:
model.fit({‘main_input‘: headline_data, ‘aux_input‘: additional_data},
          {‘main_output‘: labels, ‘aux_output‘: labels},
          epochs=50, batch_size=32)  


共享网络层

函数式 API 的另一个用途是使用共享网络层的模型。我们来看看共享层。

来考虑推特推文数据集。我们想要建立一个模型来分辨两条推文是否来自同一个人(例如,通过推文的相似性来对用户进行比较)。

由于这个问题是对称的,编码第一条推文的机制应该被完全重用来编码第二条推文(权重及其他全部)。这里我们使用一个共享的 LSTM 层来编码推文。

让我们使用函数式 API 来构建它。首先我们将一条推特转换为一个尺寸为 (280, 256) 的矩阵,即每条推特 280 字符,每个字符为 256 维的 one-hot 编码向量 (取 256 个常用字符)。

import keras
from keras.layers import Input, LSTM, Dense
from keras.models import Model  

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

要在不同的输入上共享同一个层,只需实例化该层一次,然后根据需要传入你想要的输入即可:

# 这一层可以输入一个矩阵,并返回一个 64 维的向量
shared_lstm = LSTM(64)  

# 当我们重用相同的图层实例多次,图层的权重也会被重用 (它其实就是同一层)
encoded_a = shared_lstm(tweet_a)
encoded_b = shared_lstm(tweet_b)  

# 然后再连接两个向量:
merged_vector = keras.layers.concatenate([encoded_a, encoded_b], axis=-1)  

# 再在上面添加一个逻辑回归层
predictions = Dense(1, activation=‘sigmoid‘)(merged_vector)  

# 定义一个连接推特输入和预测的可训练的模型
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)  

让我们暂停一会,看看如何读取共享层的输出或输出尺寸。


层「节点」的概念

每当你在某个输入上调用一个层时,都将创建一个新的张量(层的输出),并且为该层添加一个「节点」,将输入张量连接到输出张量。当多次调用同一个图层时,该图层将拥有多个节点索引 (0, 1, 2...)。

在之前版本的 Keras 中,可以通过 layer.get_output() 来获得层实例的输出张量,或者通过 layer.output_shape 来获取其输出形状。现在你依然可以这么做(除了 get_output() 已经被 output 属性替代)。但是如果一个层与多个输入连接呢?

只要一个层仅仅连接到一个输入,就不会有困惑,.output 会返回层的唯一输出:

a = Input(shape=(280, 256))  

lstm = LSTM(32)
encoded_a = lstm(a)  

assert lstm.output == encoded_a  

但是如果该层有多个输入,那就会出现问题:

a = Input(shape=(280, 256))
b = Input(shape=(280, 256))  

lstm = LSTM(32)
encoded_a = lstm(a)
encoded_b = lstm(b)  

lstm.output  

>> AttributeError: Layer lstm_1 has multiple inbound nodes,
hence the notion of "layer output" is ill-defined.
Use `get_output_at(node_index)` instead.  

好吧,通过下面的方法可以解决:

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

够简单,对吧?

input_shape 和 output_shape 这两个属性也是如此:只要该层只有一个节点,或者只要所有节点具有相同的输入/输出尺寸,那么「层输出/输入尺寸」的概念就被很好地定义,并且将由 layer.output_shape / layer.input_shape 返回。但是比如说,如果将一个 Conv2D 层先应用于尺寸为 (32,32,3) 的输入,再应用于尺寸为 (64, 64, 3) 的输入,那么这个层就会有多个输入/输出尺寸,你将不得不通过指定它们所属节点的索引来获取它们:

a = Input(shape=(32, 32, 3))
b = Input(shape=(64, 64, 3))  

conv = Conv2D(16, (3, 3), padding=‘same‘)
conved_a = conv(a)  

# 到目前为止只有一个输入,以下可行:
assert conv.input_shape == (None, 32, 32, 3)  

conved_b = conv(b)
# 现在 `.input_shape` 属性不可行,但是这样可以:
assert conv.get_input_shape_at(0) == (None, 32, 32, 3)
assert conv.get_input_shape_at(1) == (None, 64, 64, 3)  


更多的例子

代码示例仍然是起步的最佳方式,所以这里还有更多的例子。

Inception 模型

有关 Inception 结构的更多信息,请参阅 Going Deeper with Convolutions。

from keras.layers import Conv2D, MaxPooling2D, Input  

input_img = Input(shape=(256, 256, 3))  

tower_1 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(input_img)
tower_1 = Conv2D(64, (3, 3), padding=‘same‘, activation=‘relu‘)(tower_1)  

tower_2 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(input_img)
tower_2 = Conv2D(64, (5, 5), padding=‘same‘, activation=‘relu‘)(tower_2)  

tower_3 = MaxPooling2D((3, 3), strides=(1, 1), padding=‘same‘)(input_img)
tower_3 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(tower_3)  

output = keras.layers.concatenate([tower_1, tower_2, tower_3], axis=1)  

卷积层上的残差连接

有关残差网络 (Residual Network) 的更多信息,请参阅 Deep Residual Learning for Image Recognition。

from keras.layers import Conv2D, Input  

# 输入张量为 3 通道 256x256 图像
x = Input(shape=(256, 256, 3))
# 3 输出通道(与输入通道相同)的 3x3 卷积核
y = Conv2D(3, (3, 3), padding=‘same‘)(x)
# 返回 x + y
z = keras.layers.add([x, y])  

共享视觉模型

该模型在两个输入上重复使用同一个图像处理模块,以判断两个 MNIST 数字是否为相同的数字。

from keras.layers import Conv2D, MaxPooling2D, Input, Dense, Flatten
from keras.models import Model  

# 首先,定义视觉模型
digit_input = Input(shape=(27, 27, 1))
x = Conv2D(64, (3, 3))(digit_input)
x = Conv2D(64, (3, 3))(x)
x = MaxPooling2D((2, 2))(x)
out = Flatten()(x)  

vision_model = Model(digit_input, out)  

# 然后,定义区分数字的模型
digit_a = Input(shape=(27, 27, 1))
digit_b = Input(shape=(27, 27, 1))  

# 视觉模型将被共享,包括权重和其他所有
out_a = vision_model(digit_a)
out_b = vision_model(digit_b)  

concatenated = keras.layers.concatenate([out_a, out_b])
out = Dense(1, activation=‘sigmoid‘)(concatenated)  

classification_model = Model([digit_a, digit_b], out)  

视觉问答模型

当被问及关于图片的自然语言问题时,该模型可以选择正确的单词作答。

它通过将问题和图像编码成向量,然后连接两者,在上面训练一个逻辑回归,来从词汇表中挑选一个可能的单词作答。

from keras.layers import Conv2D, MaxPooling2D, Flatten
from keras.layers import Input, LSTM, Embedding, Dense
from keras.models import Model, Sequential  

# 首先,让我们用 Sequential 来定义一个视觉模型。
# 这个模型会把一张图像编码为向量。
vision_model = Sequential()
vision_model.add(Conv2D(64, (3, 3), activation=‘relu‘, padding=‘same‘, input_shape=(224, 224, 3)))
vision_model.add(Conv2D(64, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Conv2D(128, (3, 3), activation=‘relu‘, padding=‘same‘))
vision_model.add(Conv2D(128, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘, padding=‘same‘))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Flatten())  

# 现在让我们用视觉模型来得到一个输出张量:
image_input = Input(shape=(224, 224, 3))
encoded_image = vision_model(image_input)  

# 接下来,定义一个语言模型来将问题编码成一个向量。
# 每个问题最长 100 个词,词的索引从 1 到 9999.
question_input = Input(shape=(100,), dtype=‘int32‘)
embedded_question = Embedding(input_dim=10000, output_dim=256, input_length=100)(question_input)
encoded_question = LSTM(256)(embedded_question)  

# 连接问题向量和图像向量:
merged = keras.layers.concatenate([encoded_question, encoded_image])  

# 然后在上面训练一个 1000 词的逻辑回归模型:
output = Dense(1000, activation=‘softmax‘)(merged)  

# 最终模型:
vqa_model = Model(inputs=[image_input, question_input], outputs=output)  

# 下一步就是在真实数据上训练模型。  

视频问答模型

现在我们已经训练了图像问答模型,我们可以很快地将它转换为视频问答模型。在适当的训练下,你可以给它展示一小段视频(例如 100 帧的人体动作),然后问它一个关于这段视频的问题(例如,「这个人在做什么运动?」 -> 「足球」)。

from keras.layers import TimeDistributed  

video_input = Input(shape=(100, 224, 224, 3))
# 这是基于之前定义的视觉模型(权重被重用)构建的视频编码
encoded_frame_sequence = TimeDistributed(vision_model)(video_input)  # 输出为向量的序列
encoded_video = LSTM(256)(encoded_frame_sequence)  # 输出为一个向量  

# 这是问题编码器的模型级表示,重复使用与之前相同的权重:
question_encoder = Model(inputs=question_input, outputs=encoded_question)  

# 让我们用它来编码这个问题:
video_question_input = Input(shape=(100,), dtype=‘int32‘)
encoded_video_question = question_encoder(video_question_input)  

# 这就是我们的视频问答模式:
merged = keras.layers.concatenate([encoded_video, encoded_video_question])
output = Dense(1000, activation=‘softmax‘)(merged)
video_qa_model = Model(inputs=[video_input, video_question_input], outputs=output)  

Next Previous感谢作者分享-http://bjbsair.com/2020-04-07/tech-info/30658.html

Keras 函数式 API 是定义复杂模型(如多输出模型、有向无环图,或具有共享层的模型)的方法。

这部分文档假设你已经对 Sequential 顺序模型比较熟悉。

让我们先从一些简单的例子开始。


例一:全连接网络

Sequential 模型可能是实现这种网络的一个更好选择,但这个例子能够帮助我们进行一些简单的理解。

  • 网络层的实例是可调用的,它以张量为参数,并且返回一个张量
  • 输入和输出均为张量,它们都可以用来定义一个模型(Model)
  • 这样的模型同 Keras 的 Sequential 模型一样,都可以被训练
from keras.layers import Input, Dense
from keras.models import Model  

# 这部分返回一个张量
inputs = Input(shape=(784,))  

# 层的实例是可调用的,它以张量为参数,并且返回一个张量
x = Dense(64, activation=‘relu‘)(inputs)
x = Dense(64, activation=‘relu‘)(x)
predictions = Dense(10, activation=‘softmax‘)(x)  

# 这部分创建了一个包含输入层和三个全连接层的模型
model = Model(inputs=inputs, outputs=predictions)
model.compile(optimizer=‘rmsprop‘,
              loss=‘categorical_crossentropy‘,
              metrics=[‘accuracy‘])
model.fit(data, labels)  # 开始训练  


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

利用函数式 API,可以轻易地重用训练好的模型:可以将任何模型看作是一个层,然后通过传递一个张量来调用它。注意,在调用模型时,您不仅重用模型的结构,还重用了它的权重。

x = Input(shape=(784,))
# 这是可行的,并且返回上面定义的 10-way softmax。
y = model(x)  

这种方式能允许我们快速创建可以处理序列输入的模型。只需一行代码,你就将图像分类模型转换为视频分类模型。

from keras.layers import TimeDistributed  

# 输入张量是 20 个时间步的序列,
# 每一个时间为一个 784 维的向量
input_sequences = Input(shape=(20, 784))  

# 这部分将我们之前定义的模型应用于输入序列中的每个时间步。
# 之前定义的模型的输出是一个 10-way softmax,
# 因而下面的层的输出将是维度为 10 的 20 个向量的序列。
processed_sequences = TimeDistributed(model)(input_sequences)  


多输入多输出模型

以下是函数式 API 的一个很好的例子:具有多个输入和输出的模型。函数式 API 使处理大量交织的数据流变得容易。

来考虑下面的模型。我们试图预测 Twitter 上的一条新闻标题有多少转发和点赞数。模型的主要输入将是新闻标题本身,即一系列词语,但是为了增添趣味,我们的模型还添加了其他的辅助输入来接收额外的数据,例如新闻标题的发布的时间等。 该模型也将通过两个损失函数进行监督学习。较早地在模型中使用主损失函数,是深度学习模型的一个良好正则方法。

模型结构如下图所示:

让我们用函数式 API 来实现它。

主要输入接收新闻标题本身,即一个整数序列(每个整数编码一个词)。 这些整数在 1 到 10,000 之间(10,000 个词的词汇表),且序列长度为 100 个词。

from keras.layers import Input, Embedding, LSTM, Dense
from keras.models import Model  

# 标题输入:接收一个含有 100 个整数的序列,每个整数在 1 到 10000 之间。
# 注意我们可以通过传递一个 "name" 参数来命名任何层。
main_input = Input(shape=(100,), dtype=‘int32‘, name=‘main_input‘)  

# Embedding 层将输入序列编码为一个稠密向量的序列,
# 每个向量维度为 512。
x = Embedding(output_dim=512, input_dim=10000, input_length=100)(main_input)  

# LSTM 层把向量序列转换成单个向量,
# 它包含整个序列的上下文信息
lstm_out = LSTM(32)(x)  

在这里,我们插入辅助损失,使得即使在模型主损失很高的情况下,LSTM 层和 Embedding 层都能被平稳地训练。

auxiliary_output = Dense(1, activation=‘sigmoid‘, name=‘aux_output‘)(lstm_out)  

此时,我们将辅助输入数据与 LSTM 层的输出连接起来,输入到模型中:

auxiliary_input = Input(shape=(5,), name=‘aux_input‘)
x = keras.layers.concatenate([lstm_out, auxiliary_input])  

# 堆叠多个全连接网络层
x = Dense(64, activation=‘relu‘)(x)
x = Dense(64, activation=‘relu‘)(x)
x = Dense(64, activation=‘relu‘)(x)  

# 最后添加主要的逻辑回归层
main_output = Dense(1, activation=‘sigmoid‘, name=‘main_output‘)(x)  

然后定义一个具有两个输入和两个输出的模型:

model = Model(inputs=[main_input, auxiliary_input], outputs=[main_output, auxiliary_output])  

现在编译模型,并给辅助损失分配一个 0.2 的权重。如果要为不同的输出指定不同的 loss_weights 或 loss,可以使用列表或字典。 在这里,我们给 loss 参数传递单个损失函数,这个损失将用于所有的输出。

model.compile(optimizer=‘rmsprop‘, loss=‘binary_crossentropy‘,
              loss_weights=[1., 0.2])  

我们可以通过传递输入数组和目标数组的列表来训练模型:

model.fit([headline_data, additional_data], [labels, labels],
          epochs=50, batch_size=32)  

由于输入和输出均被命名了(在定义时传递了一个 name 参数),我们也可以通过以下方式编译模型:

model.compile(optimizer=‘rmsprop‘,
              loss={‘main_output‘: ‘binary_crossentropy‘, ‘aux_output‘: ‘binary_crossentropy‘},
              loss_weights={‘main_output‘: 1., ‘aux_output‘: 0.2})  

# 然后使用以下方式训练:
model.fit({‘main_input‘: headline_data, ‘aux_input‘: additional_data},
          {‘main_output‘: labels, ‘aux_output‘: labels},
          epochs=50, batch_size=32)  


共享网络层

函数式 API 的另一个用途是使用共享网络层的模型。我们来看看共享层。

来考虑推特推文数据集。我们想要建立一个模型来分辨两条推文是否来自同一个人(例如,通过推文的相似性来对用户进行比较)。

由于这个问题是对称的,编码第一条推文的机制应该被完全重用来编码第二条推文(权重及其他全部)。这里我们使用一个共享的 LSTM 层来编码推文。

让我们使用函数式 API 来构建它。首先我们将一条推特转换为一个尺寸为 (280, 256) 的矩阵,即每条推特 280 字符,每个字符为 256 维的 one-hot 编码向量 (取 256 个常用字符)。

import keras
from keras.layers import Input, LSTM, Dense
from keras.models import Model  

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

要在不同的输入上共享同一个层,只需实例化该层一次,然后根据需要传入你想要的输入即可:

# 这一层可以输入一个矩阵,并返回一个 64 维的向量
shared_lstm = LSTM(64)  

# 当我们重用相同的图层实例多次,图层的权重也会被重用 (它其实就是同一层)
encoded_a = shared_lstm(tweet_a)
encoded_b = shared_lstm(tweet_b)  

# 然后再连接两个向量:
merged_vector = keras.layers.concatenate([encoded_a, encoded_b], axis=-1)  

# 再在上面添加一个逻辑回归层
predictions = Dense(1, activation=‘sigmoid‘)(merged_vector)  

# 定义一个连接推特输入和预测的可训练的模型
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)  

让我们暂停一会,看看如何读取共享层的输出或输出尺寸。


层「节点」的概念

每当你在某个输入上调用一个层时,都将创建一个新的张量(层的输出),并且为该层添加一个「节点」,将输入张量连接到输出张量。当多次调用同一个图层时,该图层将拥有多个节点索引 (0, 1, 2...)。

在之前版本的 Keras 中,可以通过 layer.get_output() 来获得层实例的输出张量,或者通过 layer.output_shape 来获取其输出形状。现在你依然可以这么做(除了 get_output() 已经被 output 属性替代)。但是如果一个层与多个输入连接呢?

只要一个层仅仅连接到一个输入,就不会有困惑,.output 会返回层的唯一输出:

a = Input(shape=(280, 256))  

lstm = LSTM(32)
encoded_a = lstm(a)  

assert lstm.output == encoded_a  

但是如果该层有多个输入,那就会出现问题:

a = Input(shape=(280, 256))
b = Input(shape=(280, 256))  

lstm = LSTM(32)
encoded_a = lstm(a)
encoded_b = lstm(b)  

lstm.output  

>> AttributeError: Layer lstm_1 has multiple inbound nodes,
hence the notion of "layer output" is ill-defined.
Use `get_output_at(node_index)` instead.  

好吧,通过下面的方法可以解决:

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

够简单,对吧?

input_shape 和 output_shape 这两个属性也是如此:只要该层只有一个节点,或者只要所有节点具有相同的输入/输出尺寸,那么「层输出/输入尺寸」的概念就被很好地定义,并且将由 layer.output_shape / layer.input_shape 返回。但是比如说,如果将一个 Conv2D 层先应用于尺寸为 (32,32,3) 的输入,再应用于尺寸为 (64, 64, 3) 的输入,那么这个层就会有多个输入/输出尺寸,你将不得不通过指定它们所属节点的索引来获取它们:

a = Input(shape=(32, 32, 3))
b = Input(shape=(64, 64, 3))  

conv = Conv2D(16, (3, 3), padding=‘same‘)
conved_a = conv(a)  

# 到目前为止只有一个输入,以下可行:
assert conv.input_shape == (None, 32, 32, 3)  

conved_b = conv(b)
# 现在 `.input_shape` 属性不可行,但是这样可以:
assert conv.get_input_shape_at(0) == (None, 32, 32, 3)
assert conv.get_input_shape_at(1) == (None, 64, 64, 3)  


更多的例子

代码示例仍然是起步的最佳方式,所以这里还有更多的例子。

Inception 模型

有关 Inception 结构的更多信息,请参阅 Going Deeper with Convolutions。

from keras.layers import Conv2D, MaxPooling2D, Input  

input_img = Input(shape=(256, 256, 3))  

tower_1 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(input_img)
tower_1 = Conv2D(64, (3, 3), padding=‘same‘, activation=‘relu‘)(tower_1)  

tower_2 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(input_img)
tower_2 = Conv2D(64, (5, 5), padding=‘same‘, activation=‘relu‘)(tower_2)  

tower_3 = MaxPooling2D((3, 3), strides=(1, 1), padding=‘same‘)(input_img)
tower_3 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(tower_3)  

output = keras.layers.concatenate([tower_1, tower_2, tower_3], axis=1)  

卷积层上的残差连接

有关残差网络 (Residual Network) 的更多信息,请参阅 Deep Residual Learning for Image Recognition。

from keras.layers import Conv2D, Input  

# 输入张量为 3 通道 256x256 图像
x = Input(shape=(256, 256, 3))
# 3 输出通道(与输入通道相同)的 3x3 卷积核
y = Conv2D(3, (3, 3), padding=‘same‘)(x)
# 返回 x + y
z = keras.layers.add([x, y])  

共享视觉模型

该模型在两个输入上重复使用同一个图像处理模块,以判断两个 MNIST 数字是否为相同的数字。

from keras.layers import Conv2D, MaxPooling2D, Input, Dense, Flatten
from keras.models import Model  

# 首先,定义视觉模型
digit_input = Input(shape=(27, 27, 1))
x = Conv2D(64, (3, 3))(digit_input)
x = Conv2D(64, (3, 3))(x)
x = MaxPooling2D((2, 2))(x)
out = Flatten()(x)  

vision_model = Model(digit_input, out)  

# 然后,定义区分数字的模型
digit_a = Input(shape=(27, 27, 1))
digit_b = Input(shape=(27, 27, 1))  

# 视觉模型将被共享,包括权重和其他所有
out_a = vision_model(digit_a)
out_b = vision_model(digit_b)  

concatenated = keras.layers.concatenate([out_a, out_b])
out = Dense(1, activation=‘sigmoid‘)(concatenated)  

classification_model = Model([digit_a, digit_b], out)  

视觉问答模型

当被问及关于图片的自然语言问题时,该模型可以选择正确的单词作答。

它通过将问题和图像编码成向量,然后连接两者,在上面训练一个逻辑回归,来从词汇表中挑选一个可能的单词作答。

from keras.layers import Conv2D, MaxPooling2D, Flatten
from keras.layers import Input, LSTM, Embedding, Dense
from keras.models import Model, Sequential  

# 首先,让我们用 Sequential 来定义一个视觉模型。
# 这个模型会把一张图像编码为向量。
vision_model = Sequential()
vision_model.add(Conv2D(64, (3, 3), activation=‘relu‘, padding=‘same‘, input_shape=(224, 224, 3)))
vision_model.add(Conv2D(64, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Conv2D(128, (3, 3), activation=‘relu‘, padding=‘same‘))
vision_model.add(Conv2D(128, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘, padding=‘same‘))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Flatten())  

# 现在让我们用视觉模型来得到一个输出张量:
image_input = Input(shape=(224, 224, 3))
encoded_image = vision_model(image_input)  

# 接下来,定义一个语言模型来将问题编码成一个向量。
# 每个问题最长 100 个词,词的索引从 1 到 9999.
question_input = Input(shape=(100,), dtype=‘int32‘)
embedded_question = Embedding(input_dim=10000, output_dim=256, input_length=100)(question_input)
encoded_question = LSTM(256)(embedded_question)  

# 连接问题向量和图像向量:
merged = keras.layers.concatenate([encoded_question, encoded_image])  

# 然后在上面训练一个 1000 词的逻辑回归模型:
output = Dense(1000, activation=‘softmax‘)(merged)  

# 最终模型:
vqa_model = Model(inputs=[image_input, question_input], outputs=output)  

# 下一步就是在真实数据上训练模型。  

视频问答模型

现在我们已经训练了图像问答模型,我们可以很快地将它转换为视频问答模型。在适当的训练下,你可以给它展示一小段视频(例如 100 帧的人体动作),然后问它一个关于这段视频的问题(例如,「这个人在做什么运动?」 -> 「足球」)。

from keras.layers import TimeDistributed  

video_input = Input(shape=(100, 224, 224, 3))
# 这是基于之前定义的视觉模型(权重被重用)构建的视频编码
encoded_frame_sequence = TimeDistributed(vision_model)(video_input)  # 输出为向量的序列
encoded_video = LSTM(256)(encoded_frame_sequence)  # 输出为一个向量  

# 这是问题编码器的模型级表示,重复使用与之前相同的权重:
question_encoder = Model(inputs=question_input, outputs=encoded_question)  

# 让我们用它来编码这个问题:
video_question_input = Input(shape=(100,), dtype=‘int32‘)
encoded_video_question = question_encoder(video_question_input)  

# 这就是我们的视频问答模式:
merged = keras.layers.concatenate([encoded_video, encoded_video_question])
output = Dense(1000, activation=‘softmax‘)(merged)
video_qa_model = Model(inputs=[video_input, video_question_input], outputs=output)  

Next Previous感谢作者分享-http://bjbsair.com/2020-04-07/tech-info/30658.html

Keras 函数式 API 是定义复杂模型(如多输出模型、有向无环图,或具有共享层的模型)的方法。

这部分文档假设你已经对 Sequential 顺序模型比较熟悉。

让我们先从一些简单的例子开始。


例一:全连接网络

Sequential 模型可能是实现这种网络的一个更好选择,但这个例子能够帮助我们进行一些简单的理解。

  • 网络层的实例是可调用的,它以张量为参数,并且返回一个张量
  • 输入和输出均为张量,它们都可以用来定义一个模型(Model)
  • 这样的模型同 Keras 的 Sequential 模型一样,都可以被训练
from keras.layers import Input, Dense
from keras.models import Model  

# 这部分返回一个张量
inputs = Input(shape=(784,))  

# 层的实例是可调用的,它以张量为参数,并且返回一个张量
x = Dense(64, activation=‘relu‘)(inputs)
x = Dense(64, activation=‘relu‘)(x)
predictions = Dense(10, activation=‘softmax‘)(x)  

# 这部分创建了一个包含输入层和三个全连接层的模型
model = Model(inputs=inputs, outputs=predictions)
model.compile(optimizer=‘rmsprop‘,
              loss=‘categorical_crossentropy‘,
              metrics=[‘accuracy‘])
model.fit(data, labels)  # 开始训练  


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

利用函数式 API,可以轻易地重用训练好的模型:可以将任何模型看作是一个层,然后通过传递一个张量来调用它。注意,在调用模型时,您不仅重用模型的结构,还重用了它的权重。

x = Input(shape=(784,))
# 这是可行的,并且返回上面定义的 10-way softmax。
y = model(x)  

这种方式能允许我们快速创建可以处理序列输入的模型。只需一行代码,你就将图像分类模型转换为视频分类模型。

from keras.layers import TimeDistributed  

# 输入张量是 20 个时间步的序列,
# 每一个时间为一个 784 维的向量
input_sequences = Input(shape=(20, 784))  

# 这部分将我们之前定义的模型应用于输入序列中的每个时间步。
# 之前定义的模型的输出是一个 10-way softmax,
# 因而下面的层的输出将是维度为 10 的 20 个向量的序列。
processed_sequences = TimeDistributed(model)(input_sequences)  


多输入多输出模型

以下是函数式 API 的一个很好的例子:具有多个输入和输出的模型。函数式 API 使处理大量交织的数据流变得容易。

来考虑下面的模型。我们试图预测 Twitter 上的一条新闻标题有多少转发和点赞数。模型的主要输入将是新闻标题本身,即一系列词语,但是为了增添趣味,我们的模型还添加了其他的辅助输入来接收额外的数据,例如新闻标题的发布的时间等。 该模型也将通过两个损失函数进行监督学习。较早地在模型中使用主损失函数,是深度学习模型的一个良好正则方法。

模型结构如下图所示:

让我们用函数式 API 来实现它。

主要输入接收新闻标题本身,即一个整数序列(每个整数编码一个词)。 这些整数在 1 到 10,000 之间(10,000 个词的词汇表),且序列长度为 100 个词。

from keras.layers import Input, Embedding, LSTM, Dense
from keras.models import Model  

# 标题输入:接收一个含有 100 个整数的序列,每个整数在 1 到 10000 之间。
# 注意我们可以通过传递一个 "name" 参数来命名任何层。
main_input = Input(shape=(100,), dtype=‘int32‘, name=‘main_input‘)  

# Embedding 层将输入序列编码为一个稠密向量的序列,
# 每个向量维度为 512。
x = Embedding(output_dim=512, input_dim=10000, input_length=100)(main_input)  

# LSTM 层把向量序列转换成单个向量,
# 它包含整个序列的上下文信息
lstm_out = LSTM(32)(x)  

在这里,我们插入辅助损失,使得即使在模型主损失很高的情况下,LSTM 层和 Embedding 层都能被平稳地训练。

auxiliary_output = Dense(1, activation=‘sigmoid‘, name=‘aux_output‘)(lstm_out)  

此时,我们将辅助输入数据与 LSTM 层的输出连接起来,输入到模型中:

auxiliary_input = Input(shape=(5,), name=‘aux_input‘)
x = keras.layers.concatenate([lstm_out, auxiliary_input])  

# 堆叠多个全连接网络层
x = Dense(64, activation=‘relu‘)(x)
x = Dense(64, activation=‘relu‘)(x)
x = Dense(64, activation=‘relu‘)(x)  

# 最后添加主要的逻辑回归层
main_output = Dense(1, activation=‘sigmoid‘, name=‘main_output‘)(x)  

然后定义一个具有两个输入和两个输出的模型:

model = Model(inputs=[main_input, auxiliary_input], outputs=[main_output, auxiliary_output])  

现在编译模型,并给辅助损失分配一个 0.2 的权重。如果要为不同的输出指定不同的 loss_weights 或 loss,可以使用列表或字典。 在这里,我们给 loss 参数传递单个损失函数,这个损失将用于所有的输出。

model.compile(optimizer=‘rmsprop‘, loss=‘binary_crossentropy‘,
              loss_weights=[1., 0.2])  

我们可以通过传递输入数组和目标数组的列表来训练模型:

model.fit([headline_data, additional_data], [labels, labels],
          epochs=50, batch_size=32)  

由于输入和输出均被命名了(在定义时传递了一个 name 参数),我们也可以通过以下方式编译模型:

model.compile(optimizer=‘rmsprop‘,
              loss={‘main_output‘: ‘binary_crossentropy‘, ‘aux_output‘: ‘binary_crossentropy‘},
              loss_weights={‘main_output‘: 1., ‘aux_output‘: 0.2})  

# 然后使用以下方式训练:
model.fit({‘main_input‘: headline_data, ‘aux_input‘: additional_data},
          {‘main_output‘: labels, ‘aux_output‘: labels},
          epochs=50, batch_size=32)  


共享网络层

函数式 API 的另一个用途是使用共享网络层的模型。我们来看看共享层。

来考虑推特推文数据集。我们想要建立一个模型来分辨两条推文是否来自同一个人(例如,通过推文的相似性来对用户进行比较)。

由于这个问题是对称的,编码第一条推文的机制应该被完全重用来编码第二条推文(权重及其他全部)。这里我们使用一个共享的 LSTM 层来编码推文。

让我们使用函数式 API 来构建它。首先我们将一条推特转换为一个尺寸为 (280, 256) 的矩阵,即每条推特 280 字符,每个字符为 256 维的 one-hot 编码向量 (取 256 个常用字符)。

import keras
from keras.layers import Input, LSTM, Dense
from keras.models import Model  

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

要在不同的输入上共享同一个层,只需实例化该层一次,然后根据需要传入你想要的输入即可:

# 这一层可以输入一个矩阵,并返回一个 64 维的向量
shared_lstm = LSTM(64)  

# 当我们重用相同的图层实例多次,图层的权重也会被重用 (它其实就是同一层)
encoded_a = shared_lstm(tweet_a)
encoded_b = shared_lstm(tweet_b)  

# 然后再连接两个向量:
merged_vector = keras.layers.concatenate([encoded_a, encoded_b], axis=-1)  

# 再在上面添加一个逻辑回归层
predictions = Dense(1, activation=‘sigmoid‘)(merged_vector)  

# 定义一个连接推特输入和预测的可训练的模型
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)  

让我们暂停一会,看看如何读取共享层的输出或输出尺寸。


层「节点」的概念

每当你在某个输入上调用一个层时,都将创建一个新的张量(层的输出),并且为该层添加一个「节点」,将输入张量连接到输出张量。当多次调用同一个图层时,该图层将拥有多个节点索引 (0, 1, 2...)。

在之前版本的 Keras 中,可以通过 layer.get_output() 来获得层实例的输出张量,或者通过 layer.output_shape 来获取其输出形状。现在你依然可以这么做(除了 get_output() 已经被 output 属性替代)。但是如果一个层与多个输入连接呢?

只要一个层仅仅连接到一个输入,就不会有困惑,.output 会返回层的唯一输出:

a = Input(shape=(280, 256))  

lstm = LSTM(32)
encoded_a = lstm(a)  

assert lstm.output == encoded_a  

但是如果该层有多个输入,那就会出现问题:

a = Input(shape=(280, 256))
b = Input(shape=(280, 256))  

lstm = LSTM(32)
encoded_a = lstm(a)
encoded_b = lstm(b)  

lstm.output  

>> AttributeError: Layer lstm_1 has multiple inbound nodes,
hence the notion of "layer output" is ill-defined.
Use `get_output_at(node_index)` instead.  

好吧,通过下面的方法可以解决:

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

够简单,对吧?

input_shape 和 output_shape 这两个属性也是如此:只要该层只有一个节点,或者只要所有节点具有相同的输入/输出尺寸,那么「层输出/输入尺寸」的概念就被很好地定义,并且将由 layer.output_shape / layer.input_shape 返回。但是比如说,如果将一个 Conv2D 层先应用于尺寸为 (32,32,3) 的输入,再应用于尺寸为 (64, 64, 3) 的输入,那么这个层就会有多个输入/输出尺寸,你将不得不通过指定它们所属节点的索引来获取它们:

a = Input(shape=(32, 32, 3))
b = Input(shape=(64, 64, 3))  

conv = Conv2D(16, (3, 3), padding=‘same‘)
conved_a = conv(a)  

# 到目前为止只有一个输入,以下可行:
assert conv.input_shape == (None, 32, 32, 3)  

conved_b = conv(b)
# 现在 `.input_shape` 属性不可行,但是这样可以:
assert conv.get_input_shape_at(0) == (None, 32, 32, 3)
assert conv.get_input_shape_at(1) == (None, 64, 64, 3)  


更多的例子

代码示例仍然是起步的最佳方式,所以这里还有更多的例子。

Inception 模型

有关 Inception 结构的更多信息,请参阅 Going Deeper with Convolutions。

from keras.layers import Conv2D, MaxPooling2D, Input  

input_img = Input(shape=(256, 256, 3))  

tower_1 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(input_img)
tower_1 = Conv2D(64, (3, 3), padding=‘same‘, activation=‘relu‘)(tower_1)  

tower_2 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(input_img)
tower_2 = Conv2D(64, (5, 5), padding=‘same‘, activation=‘relu‘)(tower_2)  

tower_3 = MaxPooling2D((3, 3), strides=(1, 1), padding=‘same‘)(input_img)
tower_3 = Conv2D(64, (1, 1), padding=‘same‘, activation=‘relu‘)(tower_3)  

output = keras.layers.concatenate([tower_1, tower_2, tower_3], axis=1)  

卷积层上的残差连接

有关残差网络 (Residual Network) 的更多信息,请参阅 Deep Residual Learning for Image Recognition。

from keras.layers import Conv2D, Input  

# 输入张量为 3 通道 256x256 图像
x = Input(shape=(256, 256, 3))
# 3 输出通道(与输入通道相同)的 3x3 卷积核
y = Conv2D(3, (3, 3), padding=‘same‘)(x)
# 返回 x + y
z = keras.layers.add([x, y])  

共享视觉模型

该模型在两个输入上重复使用同一个图像处理模块,以判断两个 MNIST 数字是否为相同的数字。

from keras.layers import Conv2D, MaxPooling2D, Input, Dense, Flatten
from keras.models import Model  

# 首先,定义视觉模型
digit_input = Input(shape=(27, 27, 1))
x = Conv2D(64, (3, 3))(digit_input)
x = Conv2D(64, (3, 3))(x)
x = MaxPooling2D((2, 2))(x)
out = Flatten()(x)  

vision_model = Model(digit_input, out)  

# 然后,定义区分数字的模型
digit_a = Input(shape=(27, 27, 1))
digit_b = Input(shape=(27, 27, 1))  

# 视觉模型将被共享,包括权重和其他所有
out_a = vision_model(digit_a)
out_b = vision_model(digit_b)  

concatenated = keras.layers.concatenate([out_a, out_b])
out = Dense(1, activation=‘sigmoid‘)(concatenated)  

classification_model = Model([digit_a, digit_b], out)  

视觉问答模型

当被问及关于图片的自然语言问题时,该模型可以选择正确的单词作答。

它通过将问题和图像编码成向量,然后连接两者,在上面训练一个逻辑回归,来从词汇表中挑选一个可能的单词作答。

from keras.layers import Conv2D, MaxPooling2D, Flatten
from keras.layers import Input, LSTM, Embedding, Dense
from keras.models import Model, Sequential  

# 首先,让我们用 Sequential 来定义一个视觉模型。
# 这个模型会把一张图像编码为向量。
vision_model = Sequential()
vision_model.add(Conv2D(64, (3, 3), activation=‘relu‘, padding=‘same‘, input_shape=(224, 224, 3)))
vision_model.add(Conv2D(64, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Conv2D(128, (3, 3), activation=‘relu‘, padding=‘same‘))
vision_model.add(Conv2D(128, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘, padding=‘same‘))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘))
vision_model.add(Conv2D(256, (3, 3), activation=‘relu‘))
vision_model.add(MaxPooling2D((2, 2)))
vision_model.add(Flatten())  

# 现在让我们用视觉模型来得到一个输出张量:
image_input = Input(shape=(224, 224, 3))
encoded_image = vision_model(image_input)  

# 接下来,定义一个语言模型来将问题编码成一个向量。
# 每个问题最长 100 个词,词的索引从 1 到 9999.
question_input = Input(shape=(100,), dtype=‘int32‘)
embedded_question = Embedding(input_dim=10000, output_dim=256, input_length=100)(question_input)
encoded_question = LSTM(256)(embedded_question)  

# 连接问题向量和图像向量:
merged = keras.layers.concatenate([encoded_question, encoded_image])  

# 然后在上面训练一个 1000 词的逻辑回归模型:
output = Dense(1000, activation=‘softmax‘)(merged)  

# 最终模型:
vqa_model = Model(inputs=[image_input, question_input], outputs=output)  

# 下一步就是在真实数据上训练模型。  

视频问答模型

现在我们已经训练了图像问答模型,我们可以很快地将它转换为视频问答模型。在适当的训练下,你可以给它展示一小段视频(例如 100 帧的人体动作),然后问它一个关于这段视频的问题(例如,「这个人在做什么运动?」 -> 「足球」)。

from keras.layers import TimeDistributed  

video_input = Input(shape=(100, 224, 224, 3))
# 这是基于之前定义的视觉模型(权重被重用)构建的视频编码
encoded_frame_sequence = TimeDistributed(vision_model)(video_input)  # 输出为向量的序列
encoded_video = LSTM(256)(encoded_frame_sequence)  # 输出为一个向量  

# 这是问题编码器的模型级表示,重复使用与之前相同的权重:
question_encoder = Model(inputs=question_input, outputs=encoded_question)  

# 让我们用它来编码这个问题:
video_question_input = Input(shape=(100,), dtype=‘int32‘)
encoded_video_question = question_encoder(video_question_input)  

# 这就是我们的视频问答模式:
merged = keras.layers.concatenate([encoded_video, encoded_video_question])
output = Dense(1000, activation=‘softmax‘)(merged)
video_qa_model = Model(inputs=[video_input, video_question_input], outputs=output)  

Next Previous

原文地址:https://www.cnblogs.com/lihanlin/p/12657718.html

时间: 2024-08-03 15:50:50

Keras学习手册(三),开始使用 Keras 函数式 API的相关文章

Keras学习手册(二),快速开始-Sequential 顺序模型

感谢作者分享-http://bjbsair.com/2020-04-07/tech-info/30660.html 顺序模型是多个网络层的线性堆叠. 你可以通过将网络层实例的列表传递给 Sequential 的构造器,来创建一个 Sequential 模型: from keras.models import Sequential from keras.layers import Dense, Activation model = Sequential([ Dense(32, input_shap

Keras学习手册(一),开篇-使用 Python3 的Deep Learning 库

感谢作者分享-http://bjbsair.com/2020-04-07/tech-info/30656.html === 你恰好发现了 Keras. Keras 是一个用 Python 编写的高级神经网络 API,它能够以 TensorFlow, CNTK, 或者 Theano 作为后端运行.Keras 的开发重点是支持快速的实验.能够以最小的时延把你的想法转换为实验结果,是做好研究的关键. 如果你在以下情况下需要深度学习库,请使用 Keras: 允许简单而快速的原型设计(由于用户友好,高度模

Keras学习手册(五),Keras 模型-Sequential API

感谢作者分享-http://bjbsair.com/2020-04-07/tech-info/30662.html 在 Keras 中有两类主要的模型:Sequential 顺序模型 和 使用函数式 API 的 Model 类模型. 这些模型有许多共同的方法和属性: model.layers 是包含模型网络层的展平列表. model.inputs 是模型输入张量的列表. model.outputs 是模型输出张量的列表. model.summary() 打印出模型概述信息. 它是 utils.p

Keras学习手册(四),FAQ 常见问题解答

感谢作者分享-http://bjbsair.com/2020-04-07/tech-info/30664.html Keras FAQ: 常见问题解答 如何引用 Keras? 如何在 GPU 上运行 Keras? 如何在多 GPU 上运行 Keras 模型? "sample", "batch", "epoch" 分别是什么? 如何保存 Keras 模型? 为什么训练集误差比测试集的误差高很多? 如何获取中间层的输出? 如何用 Keras 处理超过

log4net学习手册三 Apache log4net? 示例

以下内容是Apache log4net官网技术文档通过google翻译而来 http://logging.apache.org/log4net/release/features.html 概述 下面的实施例仅是可以在log4net的源下载,而不是上线.要获得示例下载log4net的源代码版本. 构建示例 构建一个示例 一个简单的例子可以构建从示例目录运行NAnt.例如运行NAnt中的例子\ NET\1.0\教程\ ConsoleApp\ CS目录下会生成C#版本的Microsoft®.NET1.

Zookeeper学习(三) 客户端和原生API

前言 在这篇博客里我会主要总结下两个部分的操作: 在安装ZooKeeper的机器上利用ZKClient连接Zookeeper的集群,然后利用相应的命令做一些简单的操作.相信很多没有接触过Zookeeper的同学对第一篇简介里的哪些ZNode等等一些概念其实不是那么清楚,但是经过实际操作后会深入了解许多. 简单介绍下做的一个小demo,介绍了一下对Zookeeper原生API的使用.我们实际项目中用的是Curator的接口,但是原生API是根本.顺便也会提一下我们利用Zookeeper做的事-简单

Keras 学习之旅(一)

软件环境(Windows): Visual Studio Anaconda CUDA MinGW-w64 conda install -c anaconda mingw libpython CNTK TensorFlow-gpu Keras-gpu Theano MKL CuDNN 参考书籍:谢梁 , 鲁颖 , 劳虹岚.Keras快速上手:基于Python的深度学习实战 Keras 简介 Keras 这个名字来源于希腊古典史诗<奥德赛>的牛角之门(Gate of Horn):Those tha

【深度学习】简单地利用keras做车标识别

一次简简单单的实验课的内容而已. 首先把给出的样本素材放缩的32*32的大小,这部分可以用Python的批处理和opencv中的放缩函数resize()来做,在此我就不列出代码了. 列举出一部分放缩好的图片. 然后在利用keras简历卷积神经网络的模型,在做此实验之前,电脑要配置好Python+Theano+Keras的环境. #生成一个model def __CNN__(testdata,testlabel,traindata,trainlabel): model = Sequential()

【火炉炼AI】深度学习005-简单几行Keras代码解决二分类问题

[火炉炼AI]深度学习005-简单几行Keras代码解决二分类问题 (本文所使用的Python库和版本号: Python 3.6, Numpy 1.14, scikit-learn 0.19, matplotlib 2.2, Keras 2.1.6, Tensorflow 1.9.0) 很多文章和教材都是用MNIST数据集作为深度学习届的"Hello World"程序,但是这个数据集有一个很大的特点:它是一个典型的多分类问题(一共有10个分类),在我们刚刚开始接触深度学习时,我倒是觉得