30秒上手
https://keras.io/#getting-started-30-seconds-to-keras
核心数据结构是 model
,它是网络层的容器。
最简单的 model
是线性容器 Sequential
:
from keras.models import Sequential
model = Sequential()
用 .add()
向容器中添加层:
from keras.layers import Dense
# 上层输入100,本层输出64
model.add(Dense(units=64, activation='relu', input_dim=100))
# 上层输入64,本层输出10
model.add(Dense(units=10, activation='softmax'))
用 .compile()
配置训练参数:
model.compile(loss='categorical_crossentropy',
optimizer='sgd',
metrics=['accuracy'])
或更详细地配置优化器参数:
model.compile(loss=keras.losses.categorical_crossentropy,
optimizer=keras.optimizers.SGD(lr=0.01, momentum=0.9, nesterov=True))
用 .fit()
进行训练:
# x_train and y_train are Numpy arrays --just like in the Scikit-Learn API.
model.fit(x_train, y_train, epochs=5, batch_size=32)
或手动传入一个 batch:
model.train_on_batch(x_batch, y_batch)
用 .evaluate()
评估模型:
loss_and_metrics = model.evaluate(x_test, y_test, batch_size=128)
用 .predict()
进行预测:
classes = model.predict(x_test, batch_size=128)
Sequential 模型上手
指定输入维度
Sequential的第一层必须知道输入的维度信息。
有以下几种方式:
- 在第一层中指定
input_shape
参数,input_shape
中不包括batch_size
维度。 - 有些层(如
Dense
)还可以指定input_dim
参数。
例子:
model = Sequential()
model.add(Dense(32, input_shape=(784,)))
model = Sequential()
model.add(Dense(32, input_dim=784))
配置训练参数
.compile()
函数接收三个参数:
- 优化器
- 损失函数
- 评估指标
例子:
# 多分类问题,输出节点数为类别个数
model.compile(optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['accuracy'])
# 二分类问题,输出节点数为1
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])
训练
输入特征和输出label都是 Numpy 数组类型。
用 .fit()
进行训练。
例子:
# 二分类问题,输入特征为一维向量,输出为一个数值
model = Sequential()
model.add(Dense(32, activation='relu', input_dim=100))
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['accuracy'])
# 生成1000个训练样本,label值为0/1
import numpy as np
data = np.random.random((1000, 100))
labels = np.random.randint(2, size=(1000, 1))
# 训练模型
model.fit(data, labels, epochs=10, batch_size=32)
# 10类问题
model = Sequential()
model.add(Dense(32, activation='relu', input_dim=100))
model.add(Dense(10, activation='softmax'))
model.compile(optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['accuracy'])
# 生成1000个训练样本,label值为0~9
import numpy as np
data = np.random.random((1000, 100))
labels = np.random.randint(10, size=(1000, 1))
# 训练模型
one_hot_labels = keras.utils.to_categorical(labels, num_classes=10)
# Train the model, iterating on the data in batches of 32 samples
model.fit(data, one_hot_labels, epochs=10, batch_size=32)
例子
MLP多分类问题:
import keras
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation
from keras.optimizers import SGD
# 生成训练样本和测试样本
import numpy as np
x_train = np.random.random((1000, 20))
y_train = keras.utils.to_categorical(np.random.randint(10, size=(1000, 1)), num_classes=10)
x_test = np.random.random((100, 20))
y_test = keras.utils.to_categorical(np.random.randint(10, size=(100, 1)), num_classes=10)
model = Sequential()
# Dense(64) 表示包含64个节点的隐层
# 第一层必须指定输入特征的维度
# 这里输入特征是一维向量,长度为20
model.add(Dense(64, activation='relu', input_dim=20))
model.add(Dropout(0.5))
model.add(Dense(64, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(10, activation='softmax'))
sgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(loss='categorical_crossentropy',
optimizer=sgd,
metrics=['accuracy'])
model.fit(x_train, y_train,
epochs=20,
batch_size=128)
score = model.evaluate(x_test, y_test, batch_size=128)
用一维CNN对序列分类:
from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.layers import Embedding
from keras.layers import Conv1D, GlobalAveragePooling1D, MaxPooling1D
seq_length = 64
model = Sequential()
model.add(Conv1D(64, 3, activation='relu', input_shape=(seq_length, 100)))
model.add(Conv1D(64, 3, activation='relu'))
model.add(MaxPooling1D(3))
model.add(Conv1D(128, 3, activation='relu'))
model.add(Conv1D(128, 3, activation='relu'))
model.add(GlobalAveragePooling1D())
model.add(Dropout(0.5))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy',
optimizer='rmsprop',
metrics=['accuracy'])
model.fit(x_train, y_train, batch_size=16, epochs=10)
score = model.evaluate(x_test, y_test, batch_size=16)
保存模型
保存网络结构+权值+优化器状态
用 model.save(path)
保存以下内容:
- 模型结构
- 权值
- 训练参数(损失函数、优化器)
- 优化器状态(以便进行继续训练)
用 keras.models.load_model(path)
加载上述内容。
例子:
from keras.models import load_model
model.save('my_model.h5')
del model
# 返回编译好的模型(配置好训练参数)
model = load_model('my_model.h5')
仅保存网络结构
用 model.to_json()
或 model.to_yaml()
:
json_string = model.to_json()
yaml_string = model.to_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()
:
model.save_weights('my_model_weights.h5')
加载网络权值:
model.load_weights('my_model_weights.h5')
回调函数类
keras.callbacks.Callback()
基类:
- 类成员
params
- 字典变量
- 保存了训练参数:如信息显示方法verbosity,batch大小,epoch数
- 类成员
model
:正在训练的模型
用于回调的函数有四个,它们的参数被自动填充相关的训练信息:
-
on_train_batch_begin()
- 参数
batch
:整数,当前 epoch 下已遍历的 batch 数 - 参数
logs
:字典,-
logs["batch"]
:当前 batch 数 -
logs["size"]
:batch大小
-
- 参数
-
on_batch_end()
- 参数
batch
:整数,当前 epoch 下已遍历的 batch 数 - 参数
logs
:字典,保存了训练集的评估指标值
- 参数
-
on_epoch_begin()
- 参数
epoch
:当前 epoch 数
- 参数
-
on_epoch_end()
- 参数
epoch
:当前 epoch 数 - 参数
logs
:字典,保存了训练集和验证集(如果指定了验证集数据)的评估指标值
- 参数
CSVLogger
将每一代模型评估结果保存到csv文件。若提供了验证集,默认结果包含五列:
- epoch
- acc
- loss
- val_acc
- val_loss
例子:
csv_logger = CSVLogger('training.log')
model.fit(X_train, Y_train, callbacks=[csv_logger])
构造参数:
-
filename
:csv文件名 -
separator
:csv分隔符 -
append
:若为 Fasle,则覆盖已有csv文件内容
代码解析
-
构造函数参数:
class CSVLogger(Callback): def __init__(self, filename, separator=',', append=False): self.sep = separator self.filename = filename self.append = append # ...
-
成员变量:
-
writer
:用于将内容写入CSV文件 -
keys
:CSV列名 -
csv_file
:CSV文件 -
append_header
:是否在第一行想写入列名,初始值为 True
def __init__(self, filename, separator=',', append=False): # ... self.writer = None self.keys = None self.csv_file = None self.append_header = True
-
-
处理 append 参数:
- 如果为 True,将文件写入模式设为 'a'
- 如果CSV文件已存在且已有内容,则不必写入第一行列名(将 append_header 设为 False)
- 如果为 Fasle,将文件写入模式设为 'w'
def on_train_begin(self, logs=None): if self.append: mode = 'a' if os.path.exists(self.filename): with open(self.filename, 'r') as f: self.append_header = not bool(len(f.readline())) else: mode = 'w' # ...
- 如果为 True,将文件写入模式设为 'a'
-
打开 CSV 文件:
def on_train_begin(self, logs=None): # ... self.csv_file = io.open(self.filename, mode)
-
写入内容前,先创建 keys 和 writer:
def on_epoch_end(self, epoch, logs=None): if self.keys is None: self.keys = sorted(logs.keys) class CustomDialect(csv.excel): delimiter = self.sep if not self.writer: fieldnames = ['epoch'] + self.keys self.writer = csv.DictWriter(self.csv_file, fieldnames=fieldnames, dialect=CustomDialect) if self.append_header: self.writer.writeheader() # ...
-
更新 CSV:
def on_epoch_end(self, epoch, logs=None): # ... row_dict = OrderedDict({'epoch': epoch}) row_dict.update((key, logs[key]) for key in self.keys) self.writer.writerow(row_dict) self.csv_file.flush()
-
关闭 CSV 文件:
def on_train_end(self, logs=None): self.csv_file.close() self.writer = None
ModelCheckpoint
训练时自动保存模型或模型权值。
构造参数:
-
filepath
:包含格式化字符串的文件名,例如:weights.{epoch:02d}-{val_loss:.2f}.hdf5
-
monitor
:用于评估模型好坏的指标,如val_acc
或val_loss
-
save_best_only
:若为 True,则仅保存根据monitor
指标选出的更好模型 -
save_weights_only
:若为 True,则仅保存模型权值。 -
mode
:值为{'auto', 'min', 'max'}
其中之一,表示评估指标monitor
越小越好还是越大越好。- 对于
val_acc
指标,选max
,即越大越好 - 对于
val_loss
指标,选min
,即越小越好
- 对于
-
period
:两次保存动作相隔的代数。 -
verbose
:是否输出说明文本
代码解析
-
构造函数
class ModelCheckpoint(Callback): def __init__(self, filepath, monitor='val_loss', verbose=0, save_best_only=False, save_weights_only=False, mode='auto', period=1): super(ModelCheckpoint, self).__init__() self.monitor = monitor self.verbose = verbose self.filepath = filepath self.save_best_only = save_best_only self.save_weights_only = save_weights_only self.period = period # ...
-
根据
mode
调整指标比较方法def __init__(self, ...): # ... if mode == 'min': self.monitor_op = np.less self.best = np.Inf elif mode == 'max': self.monitor_op = np.greater self.best = -np.Inf # ...
-
创建一个epoch计数器
def __init__(self, ...): # ... self.epochs_since_last_save = 0
-
每训练一代,计数器加1,到达
period
值时,保存模型def on_epoch_end(self, epoch, logs=None): logs = logs or {} self.epochs_since_last_save += 1 if self.epochs_since_last_save >= self.period: self.epochs_since_last_save = 0 # ...
-
解析文件名
def on_epoch_end(self, epoch, logs=None): if self.epochs_since_last_save >= self.period: # ... filepath = self.filepath.format(epoch=epoch + 1, **logs)
-
保存模型
def on_epoch_end(self, epoch, logs=None): if self.epochs_since_last_save >= self.period: # ... if self.save_best_only: # ... else: if self.verbose > 0: print('\nEpoch %05d: saving model to %s' % (epoch + 1, filepath)) if self.save_weights_only: self.model.save_weights(filepath, overwrite=True) else: self.model.save(filepath, overwrite=True)
-
如果参数要求仅保存更好的模型,则需比较模型评估指标
def on_epoch_end(self, epoch, logs=None): if self.epochs_since_last_save >= self.period: # ... if self.save_best_only: current = logs.get(self.monitor) if self.monitor_op(current, self.best): if self.verbose > 0: print('\nEpoch %05d: %s improved from %0.5f to %0.5f,' ' saving model to %s' % (epoch + 1, self.monitor, self.best, current, filepath)) self.best = current if self.save_weights_only: self.model.save_weights(filepath, overwrite=True) else: self.model.save(filepath, overwrite=True) else: if self.verbose > 0: print('\nEpoch %05d: %s did not improve from %0.5f' % (epoch + 1, self.monitor, self.best)) else: # ...