from tensorflow.examples.tutorials.mnist import input_data
# 导入数据并且进行独热编码
data = input_data.read_data_sets('data/MNIST', one_hot=True)
# 打印训练集、测试集、验证集大小
print("- Training-set:\t\t{}".format(len(data.train.labels)))
print("- Test-set:\t\t{}".format(len(data.test.labels)))
print("- Validation-set:\t{}".format(len(data.validation.labels)))
data.train.labels
是一个列表,元素为各个图像对应的标签。
# 使用One-Hot编码,这意外每个标签是长为10的向量,除了一个元素之外,其他的都为零,于是元素1的索引即为图像的类别
data.test.cls = np.argmax(data.test.labels, axis=1)
data.validation.cls = np.argmax(data.validation.labels, axis=1)
# 在测试集中的图像与对应的类别都是正确的
# 获取测试集中前9个图像与对应的类别
cls_true = data.test.cls[0:9]
将训练集和验证集中的图像和标签合并
# 原始训练集和验证集合并到大的一个数组中
combined_images = np.concatenate([data.train.images, data.validation.images], axis=0)
combined_labels = np.concatenate([data.train.labels, data.validation.labels], axis=0)
Keras 的核心数据结构是 model,一种组织网络层的方式。最简单的模型是 Sequential 顺序模型,它由多个网络层线性堆叠。
创建一个顺序模型:
from keras.models import Sequential
model = Sequential()
# 使用.add()函数来叠加模型的结构
from keras.layers import Dense
model.add(Dense(units=64, activation='relu', input_dim=100))
model.add(Dense(units=10, activation='softmax'))
# 使用.compile()函数来配置学习过程
model.compile(loss='categorical_crossentropy', # 损失函数
optimizer = 'sgd', # 随机下降优化器
metrics = ['accuracy'])# 评价指标
# 批量在训练集上进行迭代训练
model.fit(x_train, y_train, epochs=5, batch_size=32)
"""
你可以手动地将批次的数据提供给模型:
model.train_on_batch(x_batch, y_batch)
"""
# 预测新数据 In one line of code!
classes = model.predict(x_test, batch_size=128)
# 评估模型性能 in one single line of code!
loss_and_metrics = model.evaluate(x_test, y_test, batch_size=128)
Warning:编译模型时必须指明损失函数和优化器,如果你需要的话,也可以自己定制损失函数。
顺序模型是多个网络层的线性堆叠。可以使用网络层实例列表传递给Sequential构造器:
from keras.models import Sequential
from keras.layers import Dense, Activation
model = Sequential([
Dense(32, input_shape=(784,)),
Activation('relu'),
Dense(10),
Activation('softmax'),
])
不过更加方便一点的方法是使用.add()函数将各层添加到模型中:
model = Sequential()
model.add(Dense(32, input_dim=784))
model.add(Activation('relu'))
# Dropout层, Dense全连接层, 激活函数
model = Sequential()
# Dense(64) 是一个具有 64 个隐藏神经元的全连接层。
# 在第一层必须指定所期望的输入数据尺寸:
# 在这里,是一个 20 维的向量。
model.add(Dense(64, activation='relu', input_dim=20))
model.add(Dropout(0.5))
指定输入数据的尺寸
模型需要知道它所期望的输入的尺寸。出于这个原因,顺序模型中的第一层(且只有第一层,因为下面的层可以自动地推断尺寸)
下面的代码片段是等价的:
model = Sequential()
model.add(Dense(32, input_shape=(784,)))
model = Sequential()
model.add(Dense(32, input_dim=784))
# 多分类问题
model.compile(optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['accuracy'])
# 二分类问题
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['accuracy'])
# 均方误差回归问题
model.compile(optimizer='rmsprop',
loss='mse')
# 自定义评估标准函数
import keras.backend as K
def mean_pred(y_true, y_pred):
return K.mean(y_pred)
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['accuracy', mean_pred])
将标签输入转换为分类的 one-hot 编码:
# 将标签转换为分类的 one-hot 编码
one_hot_labels = keras.utils.to_categorical(labels, num_classes=10)
定制优化器参数的实例:
sgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(loss='categorical_crossentropy',
optimizer=sgd,
metrics=['accuracy'])
优化器参数介绍:
from keras import optimizers
# 所有的参数梯度将被限制到最大范数为1
sgd = optimizers.SGD(lr=0.01, clipnorm=1.)
# 所有的参数梯度都将被限制为最大值0.5和最小值-0.5
sgd = optimizers.SGD(lr=0.01, clipvalue=0.5)
"""
momentum: 在相关方向上加速SGD并抑制振荡的参数
decay: 每次更新后学习率的降低率
nesterov: 布尔值,是否使用nesterov动量
"""
sgd = optimizers.SGD(lr=0.01, momentum=0.0, decay=0.0, nesterov=False)
如何在每个 epoch 后记录训练集和验证集的误差和准确率?
model.fit
方法返回一个 History 回调,它具有包含连续误差的列表和其他度量的 history 属性。
hist = model.fit(x, y, validation_split=0.2)
print(hist.history)
# 一个 History 对象。其 History.history 属性是连续 epoch 训练损失和评估值,以及验证集损失和评估值的记录(如果适用)。
一个简单的方法是创建一个新的 Model 来输出你所感兴趣的层:
from keras.models import Model
model = ... # 创建原始模型
layer_name = 'my_layer' # 某一层赋予的名字
intermediate_layer_model = Model(inputs=model.input, outputs=model.get_layer(layer_name).output)
intermediate_output = intermediate_layer_model.predict(data)
或者,你也可以构建一个 Keras 函数,该函数将在给定输入的情况下返回某个层的输出,例如:
from keras import backend as K
# 以 Sequential 模型为例
get_3rd_layer_output = K.function([model.layers[0].input],
[model.layers[3].output])
layer_output = get_3rd_layer_output([x])[0]
使用 model.save(filepath)
将 Keras 模型保存到单个 HDF5 文件中,该文件将包含:
例子:
from keras.models import load_model
model.save('my_model.h5') # 创建 HDF5 文件 'my_model.h5'
del model # 删除现有模型
# 返回一个编译好的模型
# 与之前那个相同
model = load_model('my_model.h5')
只需要保存模型的结构,而非其权重或训练配置项,则可以执行以下操作:
# 保存为 JSON
json_string = model.to_json()
# 保存为 YAML
yaml_string = model.to_yaml()
生成的 JSON/YAML 文件是人类可读的,如果需要还可以手动编辑。
你可以从这些数据建立一个新的模型:
# 从 JSON 重建模型:
from keras.models import model_from_json
model = model_from_json(json_string)
# 从 YAML 重建模型:
from keras.models import model_from_yaml
model = model_from_yaml(yaml_string)
# 先保存当前模型的权重
model.save_weights('my_model_weights.h5')
# 用于实例化模型的代码,则可以将保存的权重加载到具有相同结构的模型中:
model.load_weights('my_model_weights.h5')
如果只需将权重加载到不同的结构(有一些共同层)的模型中,例如微调或迁移学习,则可以按层的名字来加载权重
model.load_weights('my_model_weights.h5', by_name=True)
实例:对不同模型的共同层赋予权重
"""
假设原始模型如下所示:
model = Sequential()
model.add(Dense(2, input_dim=3, name='dense_1'))
model.add(Dense(3, name='dense_2'))
...
model.save_weights(fname)
"""
# 新模型
model = Sequential()
model.add(Dense(2, input_dim=3, name='dense_1')) # 将被加载
model.add(Dense(10, name='new_dense')) # 将不被加载
# 从第一个模型加载权重;只会影响第一层,dense_1
model.load_weights(fname, by_name=True)
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) # 开始训练
由实例可以看出,网络层实例的参数传入格式:
tensor_x1 = Dense(dim=, activation= )(tensor_x)
利用函数式 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)
考虑下面的模型。我们试图预测 Twitter 上的一条新闻标题有多少转发和点赞数。模型的主要输入将是新闻标题本身,即一系列词语,但是为了增添趣味,我们的模型还添加了其他的辅助输入来接收额外的数据,例如新闻标题的发布的时间等。 该模型也将通过两个损失函数进行监督学习。较早地在模型中使用主损失函数,是深度学习模型的一个良好正则方法。
模型结构如下图所示:
主要输入接收新闻标题本身,即一个整数序列(每个整数编码一个词)。 这些整数在 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])
注意: 这里loss_weights
参数列表元素顺序与Model()
中输出的数据列表所对应的顺序要对应。
通过传递输入数组和目标数组的列表来训练模型:
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)
每当你在某个输入上调用一个层时,都将创建一个新的张量(层的输出),并且为该层添加一个「节点」,将输入张量连接到输出张量。当多次调用同一个图层时,该图层将拥有多个节点索引(0, 1, 2...)
在之前版本的 Keras 中,可以通过layer.get_output()
来获得层实例的输出张量,或者通过 layer.output_shape
来获取其输出形状。现在你依然可以这么做(除了 get_output()
已经被 output属性替代)。
Q:问题是如果一个层对应多个输入连接怎么办?
对于一层只连接一个输入,则有.output返回层里的唯一输出:
a = Input(shape=(280, 256))
lstm = LSTM(32) # 返回一个32维向量
encoded_a = lstm(a) # 向lstm输入一个张量
assert lstm.output == encoded_a
而当该层有多个输入时,则会出现属性错误lstm层有多个输入节点:
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
上一个例子中两个输入的维数都相等,此时我们就会想到如果两个输入的维数不一样呢?
例如将一个 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)
考虑推特推文数据集。我们想要建立一个模型来分辨两条推文是否来自同一个人(例如,通过推文的相似性来对用户进行比较)。
实现这个目标的一种方法是建立一个模型,将两条推文编码成两个向量,连接向量,然后添加逻辑回归层;这将输出两条推文来自同一作者的概率。模型将接收一对对正负表示的推特数据。
由于这个问题是对称的,编码第一条推文的机制应该被完全重用来编码第二条推文(权重及其他全部)。这里我们使用一个共享的 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) # axis=-1相当于把向量encoded_b添加到向量encoded_a后面
# 再在上面添加一个逻辑回归层
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)
···
# 模型中共享的 LSTM 用于并行编码两个不同的序列
input_a = keras.Input(shape=(140, 256))
input_b = keras.Input(shape=(140, 256))
shared_lstm = keras.layers.LSTM(64)
# 在一个 GPU 上处理第一个序列
with tf.device_scope('/gpu:0'):
encoded_a = shared_lstm(tweet_a)
# 在另一个 GPU上 处理下一个序列
with tf.device_scope('/gpu:1'):
encoded_b = shared_lstm(tweet_b)
# 在 CPU 上连接结果
with tf.device_scope('/cpu:0'):
merged_vector = keras.layers.concatenate([encoded_a, encoded_b],
axis=-1)
x = keras.layers.concatenate([lay1_out, auxiliary_input])
compile
# compile方法
compile(optimizer, loss=None, metrics=None, loss_weights=None, sample_weight_mode=None, weighted_metrics=None, target_tensors=None)
train_on_batch/test_on_batch/
train_on_batch(x, y, sample_weight=None, class_weight=None)
一批样品的单次梯度更新。
Arguments
evaluate
evaluate(x=None, y=None, batch_size=None, verbose=1, sample_weight=None, steps=None)
在测试模式,返回误差值和评估标准值。计算逐批次进行.
predict/predict_on_batch
predict(x, batch_size=None, verbose=0, steps=None)
为输入样本生成输出预测。计算逐批次进行.
参数
查看网络层信息
get_layer
model.get_layer(self,name=None,index=None)
:依据层名或下标获得层对象⚠️ 注意若要使用下标获取层对象,正确的用法应该是model.get_layer(index=0)
,而不是model.get_layer(0)
,否则会报错。
model.summary
: 输出模型各层的参数状况
“后端”翻译自backend,指的是Keras依赖于完成底层的张量运算的软件包。
支持的后端有:
一般使用tensorflow的后端。
安装依赖(HDF5和h5py)可与将Keras模型保存到磁盘中。模型权重被保存为 HDF5格式。这是一种网格格式,适合存储数字的多维数组。
❓ 在训练过程中数据是否会混洗
是的,如果 model.fit中的 shuffle参数设置为 True(默认值),则训练数据将在每个 epoch 混洗。
⚠️ 验证集永远不会混洗。