TensorFlow基础之模型建立与训练
模型建立与训练:简单的线性回归
书接上文TensorFlow 基础之张量、变量、自动求导、简单的线性回归与梯度下降https://blog.csdn.net/weixin_42764932/article/details/113035338
利用模型类把各种层进行一下组织和连接,重写一下上文的线性回归问题
import tensorflow as tf
x = tf.constant([[1.,2.,3.],[2.,4.,6.]])
y = tf.constant([[10.],[20.]])
class linear(tf.keras.Model):
def __init__(self):
super().__init__()
self.dense = tf.keras.layers.Dense(units = 1, activation=None,
kernel_initializer=tf.zeros_initializer(),
bias_initializer=tf.zeros_initializer())
def call(self, input):
output = self.dense(input)
return output
model = linear()
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)
n_epoch = 200
for i in range(n_epoch):
with tf.GradientTape() as tape:
output = model(x)
loss = tf.reduce_mean(tf.square(output - y))
print(loss.numpy())
grads = tape.gradient(loss, model.variables)
optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))
print(model.variables)
print(output)
-
通过继承 tf.keras.Model 这个 Python 类来定义自己的模型
-
在继承类中, 需要重写 init() (构造函数,初始化)和 call(input) (模型调用)两个方法,同时也可以根据需要增加自定义的方法。
-
在实例化类 model = Model() 后,可以通过 model.variables 这一属性直接获得模型中的所有变量,免去显式指定变量的麻烦。
-
为什么模型类是重载 call() 方法而不是
__call__()
方法?
在 Python 中,对类的实例 myClass 进行形如 myClass() 的调用等价于myClass.__call__()
那么看起来,为了使用 y_pred = model(X) 的形式调用模型类,应该重写__call__()
方法才对呀?
原因是 Keras 在模型调用的前后还需要有一些自己的内部操作,所以暴露出一个专门用于重载的 call() 方法。 tf.keras.Model 这一父类已经包含__call__()
的定义。__call__()
中主要调用了 call() 方法,同时还需要在进行一些 keras 的内部操作。这里,我们通过继承 tf.keras.Model 并重载 call() 方法,即可在保持 keras 结构的同时加入模型调用的代码。 -
全连接层
tf.keras.layers.Dense
- units :输出张量的维度;
- activation :激活函数,对应于 f(AW + b) 中的 f ,默认为无激活函数( a(x) = x )。常用的激活函数包括 tf.nn.relu 、 tf.nn.tanh 和 tf.nn.sigmoid ;
- use_bias :是否加入偏置向量 bias ,即 f(AW + b) 中的 b。默认为 True ;
- kernel_initializer 、 bias_initializer :权重矩阵 kernel 和偏置向量 bias 两个变量的初始化器。默认为 tf.glorot_uniform_initializer 。设置为 tf.zeros_initializer 表示将两个变量均初始化为全 0;
MLP多层感知机
数据获取、预处理
class mnistloder():
def __init__(self):
mnist = tf.kears.datasets.mnist
(self.train_data, self.train_label), (self.test_data, self.test_label) = mnist.load_data()
self.train_data = np.expand_dims(self.train_data.astype(np.float32) / 255.0, axis=-1) # [60000, 28, 28, 1]
self.test_data = np.expand_dims(self.test_data.astype(np.float32) / 255.0, axis=-1) # [10000, 28, 28, 1]
self.train_label = self.train_label.astype(np.int32) # [60000]
self.test_label = self.test_label.astype(np.int32) # [10000]
self.num_train_data, self.num_test_data = self.train_data.shape[0], self.test_data.shape[0]
def get_batch(self, batch_size):
index = np.random.randint(0, self.num_train_data, batch_size)
return self.train_data[index, :], self.train_label[index]
模型搭建
class MLP(tf.keras.Model):
def __init__(self):
super().__init__()
self.flatten = keras.layers.Flatten() # 展成一维向量 batch_size * c
self.dense1 = keras.layers.Dense(units=100, activation=tf.nn.relu)
self.dense2 = keras.layers.Dense(units=10)
def call(self, input):
x = self.flatten(input)
x = self.dense1(x)
x = self.dense2(x)
output = tf.nn.softmax(x)
return output
训练与评估
num_epochs = 5
batch_size = 50
learning_rate = 0.001
model = MLP()
data_loader = mnistloder()
optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)
num_batches = int(data_loader.num_train_data // batch_size * num_epochs)
for i in range(num_epochs):
for batch_index in range(num_batches):
X, y = data_loader.get_batch(batch_size)
with tf.GradientTape() as tape:
y_pred = model(X)
loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred)
loss = tf.reduce_mean(loss)
print("batch %d: loss %f" % (batch_index, loss.numpy()))
grads = tape.gradient(loss, model.variables)
optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))
sparse_categorical_accuracy = tf.keras.metrics.SparseCategoricalAccuracy()
num_batches = int(data_loader.num_test_data // batch_size)
for batch_index in range(num_batches):
start_index, end_index = batch_index * batch_size, (batch_index + 1) * batch_size
y_pred = model.predict(data_loader.test_data[start_index: end_index])
sparse_categorical_accuracy.update_state(y_true=data_loader.test_label[start_index: end_index], y_pred=y_pred)
print("test accuracy: %f" % sparse_categorical_accuracy.result())
- tf.keras.losses.sparse_categorical_crossentropy(交叉熵)函数,将模型的预测值 y_pred 与真实的标签值 y 作为函数参数传入,在分类问题中被广泛应用。
- 模型的评估: tf.keras.metrics.SparseCategoricalAccuracy()
该评估器能够对模型预测的结果与真实结果进行比较,并输出预测正确的样本数占总样本数的比例。
我们迭代测试数据集,每次通过 update_state() 方法向评估器输入两个参数: y_pred 和 y_true ,即模型预测出的结果和真实结果。
评估器具有内部变量来保存当前评估指标相关的参数数值(例如当前已传入的累计样本数和当前预测正确的样本数)。
迭代结束后,我们使用 result() 方法输出最终的评估指标值(预测正确的样本数占总样本数的比例)。
卷积神经网络
class CNN(tf.keras.Model):
def __init__(self):
super().__init__()
self.conv1 = tf.keras.layers.Conv2D(
filters=32, # 卷积层神经元(卷积核)数目
kernel_size=[5, 5], # 感受野大小
padding='same', # padding策略(vaild 或 same)
activation=tf.nn.relu # 激活函数
)
self.pool1 = tf.keras.layers.MaxPool2D(pool_size=[2, 2], strides=2)
self.conv2 = tf.keras.layers.Conv2D(
filters=64,
kernel_size=[5, 5],
padding='same',
activation=tf.nn.relu
)
self.pool2 = tf.keras.layers.MaxPool2D(pool_size=[2, 2], strides=2)
self.flatten = tf.keras.layers.Reshape(target_shape=(7 * 7 * 64,))
self.dense1 = tf.keras.layers.Dense(units=1024, activation=tf.nn.relu)
self.dense2 = tf.keras.layers.Dense(units=10)
def call(self, inputs):
x = self.conv1(inputs) # [batch_size, 28, 28, 32]
x = self.pool1(x) # [batch_size, 14, 14, 32]
x = self.conv2(x) # [batch_size, 14, 14, 64]
x = self.pool2(x) # [batch_size, 7, 7, 64]
x = self.flatten(x) # [batch_size, 7 * 7 * 64]
x = self.dense1(x) # [batch_size, 1024]
x = self.dense2(x) # [batch_size, 10]
output = tf.nn.softmax(x)
return output
和pytorch大同小异
高效建模
Keras Sequential高效建模
将一堆层按特定顺序叠加起来,通过向 tf.keras.models.Sequential() 提供一个层的列表,就能快速地建立一个 tf.keras.Model 模型并返回
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(100, activation=tf.nn.relu),
tf.keras.layers.Dense(10),
tf.keras.layers.Softmax()
])
Functional API建模
这种层叠结构并不能表示任意的神经网络结构。
为此,Keras 提供了 Functional API,帮助我们建立更为复杂的模型,例如多输入 / 输出或存在参数共享的模型。其使用方法是将层作为可调用的对象并返回张量(这点与之前章节的使用方法一致),并将输入向量和输出向量提供给 tf.keras.Model 的 inputs 和 outputs 参数
inputs = tf.keras.Input(shape=(28, 28, 1))
x = tf.keras.layers.Flatten()(inputs)
x = tf.keras.layers.Dense(units=100, activation=tf.nn.relu)(x)
x = tf.keras.layers.Dense(units=10)(x)
outputs = tf.keras.layers.Softmax()(x)
model = tf.keras.Model(inputs=inputs, outputs=outputs)
经典网络调用
tf.keras.applications 中有一些预定义好的经典卷积神经网络结构,如 VGG16 、 VGG19 、 ResNet 、 MobileNet 等。我们可以直接调用这些经典的卷积神经网络结构(甚至载入预训练的参数),而无需手动定义网络结构。
model = tf.keras.applications.MobileNetV2()
model = tf.keras.applications.MobileNetV2(weights=None, classes=10)
参数:
-
input_shape :输入张量的形状(不含第一维的 Batch),大多默认为 224 × 224 × 3 。一般而言,模型对输入张量的大小有下限,长和宽至少为 32 × 32 或 75 × 75 ;
-
include_top :在网络的最后是否包含全连接层,默认为 True ;
-
weights :预训练权值,默认为 ‘imagenet’ ,即为当前模型载入在 ImageNet 数据集上预训练的权值。如需随机初始化变量可设为 None ;
-
classes :分类数,默认为 1000。修改该参数需要 include_top 参数为 True 且 weights 参数为 None 。
高效训练
当模型建立完成后,通过 tf.keras.Model 的 compile 方法配置训练过程:
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
loss=tf.keras.losses.sparse_categorical_crossentropy,
metrics=[tf.keras.metrics.sparse_categorical_accuracy]
)
tf.keras.Model.compile 接受 3 个重要的参数:
-
oplimizer :优化器,可从 tf.keras.optimizers 中选择;
-
loss :损失函数,可从 tf.keras.losses 中选择;
-
metrics :评估指标,可从 tf.keras.metrics 中选择。
使用 tf.keras.Model 的 fit 方法训练模型:
model.fit(data_loader.train_data, data_loader.train_label, epochs=num_epochs, batch_size=batch_size)
tf.keras.Model.fit 接受 5 个重要的参数:
-
x :训练数据;
-
y :目标数据(数据标签);
-
epochs
-
batch_size :批次的大小;
-
validation_data :验证数据,可用于在训练过程中监控模型的性能。
使用 tf.keras.Model.evaluate 评估训练效果,提供测试数据及标签
model.evaluate(data_loader.test_data, data_loader.test_label)
-
x :测试数据;
-
y :测试数据标签 ;
自定义
自定义层
例如, 实现一个全连接层( tf.keras.layers.Dense ),可以按如下方式编写。
此代码在 build 方法中创建两个变量,并在 call 方法中使用创建的变量进行运算:
class LinearLayer(tf.keras.layers.Layer):
def __init__(self, units):
super().__init__()
self.units = units
def build(self, input_shape): # 这里 input_shape 是第一次运行call()时参数inputs的形状
self.w = self.add_variable(name='w',
shape=[input_shape[-1], self.units], initializer=tf.zeros_initializer())
self.b = self.add_variable(name='b',
shape=[self.units], initializer=tf.zeros_initializer())
def call(self, inputs):
y_pred = tf.matmul(inputs, self.w) + self.b
return y_pred
注意:build()的 input_shape 是第一次运行call()时参数inputs的形状,这点挺好的
自定义损失
自定义损失函数需要继承 tf.keras.losses.Loss 类,重写 call 方法即可,输入真实值 y_true 和模型预测值 y_pred ,输出模型预测值和真实值之间通过自定义的损失函数计算出的损失值。
class MeanSquaredError(tf.keras.losses.Loss):
def call(self, y_true, y_pred):
return tf.reduce_mean(tf.square(y_pred - y_true))
自定义评估
自定义评估指标需要继承 tf.keras.metrics.Metric 类,并重写 init 、 update_state 和 result 三个方法。
下面的示例对前面用到的 SparseCategoricalAccuracy 评估指标类做了一个简单的重实现:
class SparseCategoricalAccuracy(tf.keras.metrics.Metric):
def __init__(self):
super().__init__()
self.total = self.add_weight(name='total', dtype=tf.int32, initializer=tf.zeros_initializer())
self.count = self.add_weight(name='count', dtype=tf.int32, initializer=tf.zeros_initializer())
def update_state(self, y_true, y_pred, sample_weight=None):
values = tf.cast(tf.equal(y_true, tf.argmax(y_pred, axis=-1, output_type=tf.int32)), tf.int32)
self.total.assign_add(tf.shape(y_true)[0])
self.count.assign_add(tf.reduce_sum(values))
def result(self):
return self.count / self.total