本专栏是记录作者学习TensorFlow深度学习的相关内容
通过上一节我们可以通过调整拟合多项式的阶数来限制模型的容量。 实际上,限制特征的数量是缓解过拟合的一种常用技术。 然而,简单地丢弃特征对这项工作来说可能过于生硬
如何控制模型的容量?方法有二:1.减少模型参数 2.参数取值范围减小
本节介绍的权重衰退通过控制取值范围缓解过拟合
本节的 Jupyter 笔记本文件已上传至gitee以供大家学习交流:我的gitee仓库
1 均方范数权重衰减
这里只简单介绍一下结论,具体理论基础可以了解L1和L2范数
我们将原损失函数
L
(
w
,
b
)
L(\mathbf{w}, b)
L(w,b)加上“小尾巴”
L
(
w
,
b
)
+
λ
2
∥
w
∥
2
L(\mathbf{w}, b) + \frac{\lambda}{2} \|\mathbf{w}\|^2
L(w,b)+2λ∥w∥2
加入了“尾巴”后,每一次更新权重都会对权重进行减小再更新,其中通常
η
λ
<
=
1
\eta\lambda<=1
ηλ<=1,
λ
\lambda
λ越大,对
w
w
w的约束越大
w
=
(
1
−
η
λ
)
w
t
−
η
∂
L
(
w
t
,
b
t
)
∂
w
t
\begin{aligned} \mathbf{w} = \left(1- \eta\lambda \right) \mathbf{w_t} - \eta\frac{\partial L(w_t,b_t)}{\partial w_t} \end{aligned}
w=(1−ηλ)wt−η∂wt∂L(wt,bt)
2 权重衰减的实现
%matplotlib inline
import tensorflow as tf
from d2l import tensorflow as d2l
2.1 数据集
标签值是关于输入的线性函数。标签同样加入均值为0,标准差为0.01的高斯噪音。
训练集数量:20
测试集数量:100
输入特征维度:200
y
=
0.05
+
∑
i
=
1
d
0.01
x
i
+
ϵ
where
ϵ
∼
N
(
0
,
0.0
1
2
)
y = 0.05 + \sum_{i = 1}^d 0.01 x_i + \epsilon \text{ where }\epsilon \sim \mathcal{N}(0, 0.01^2)
y=0.05+i=1∑d0.01xi+ϵ where ϵ∼N(0,0.012)
由于训练集非常少,容易出现过拟合的情况
n_train, n_test, num_inputs, batch_size = 20, 100, 200, 5
# 训练集样本数量,测试集样本数量,输入特征数量,批次大小
true_w, true_b = tf.ones((num_inputs, 1)) * 0.01, 0.05
# 真实的模型参数w和b,w是一个(num_inputs, 1)的矩阵,b为标量
train_data = d2l.synthetic_data(true_w, true_b, n_train)
# 生成模拟的训练数据集
train_iter = d2l.load_array(train_data, batch_size)
# 将训练数据集加载到迭代器中,每次返回一个批次大小的样本
test_data = d2l.synthetic_data(true_w, true_b, n_test)
# 生成模拟的测试数据集
test_iter = d2l.load_array(test_data, batch_size, is_train=False)
# 将测试数据集加载到迭代器中,每次返回一个批次大小的样本
2.2 不使用框架实现
#初始化模型的参数
def init_params():
# 初始化模型参数,生成一个形状为(num_inputs, 1)的随机正态分布权重矩阵w
w = tf.Variable(tf.random.normal(mean=1, shape=(num_inputs, 1)))
# 生成一个形状为(1,)的全零向量b
b = tf.Variable(tf.zeros(shape=(1, )))
return [w, b]
def l2_penalty(w):
"""
计算L2正则化惩罚项
:param w: 权重参数
:return: L2正则化惩罚项
"""
return tf.reduce_sum(tf.pow(w, 2)) / 2
#训练
def train(lambd):
w, b = init_params()
#快速定义接受输入 X 的匿名函数,该函数使用 d2l.linreg 函数进行线性回归
#d2l.squared_loss函数定义了损失函数
net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss
# 设置训练超参数
num_epochs, lr = 100, 0.003
animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log',
xlim=[5, num_epochs], legend=['train', 'test'])
for epoch in range(num_epochs):
for X, y in train_iter:
#计算变量对于损失函数的梯度。
with tf.GradientTape() as tape:
# 增加了L2范数惩罚项,
# 广播机制使l2_penalty(w)成为一个长度为batch_size的向量
l = loss(net(X), y) + lambd * l2_penalty(w)
grads = tape.gradient(l, [w, b])#计算l关于[w, b]的梯度
d2l.sgd([w, b], grads, lr, batch_size)
# 每5轮训练打印一次训练和测试的损失函数
if (epoch + 1) % 5 == 0:
animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss),
d2l.evaluate_loss(net, test_iter, loss)))
print('w的L2范数是:', tf.norm(w).numpy())
2.2.1 不使用权重衰减
用lambd = 0不使用权重衰减进行训练,由于训练数据过于简单(20个),测试集上的损失值和训练集的损失值相差很大,发生了严重的过拟合。
train(lambd=0)
结果:
w的L2范数是: 16.87396
2.2.2 使用权重衰减
用lambd = 3,使用权重衰减进行训练,测试集的损失值有效下降
train(lambd=3)
结果:
w的L2范数是: 0.51431155
2.3使用框架实现
深度学习框架为了便于我们使用权重衰减, 将权重衰减集成到优化算法中,以便与任何损失函数结合使用
def train_concise(wd):
# 创建一个顺序模型
net = tf.keras.models.Sequential()
# 添加一个全连接层,其中该层的输出节点数为1。同时,使用L2正则化方法对权重进行约束,其中正则化系数为wd
net.add(tf.keras.layers.Dense(
1, kernel_regularizer=tf.keras.regularizers.l2(wd)))
# 设置模型的输入形状
net.build(input_shape=(1, num_inputs))
# 创建一个均方误差损失函数
loss = tf.keras.losses.MeanSquaredError()
# 设置训练的轮数和学习率
num_epochs, lr = 100, 0.003
# 创建一个随机梯度下降优化器
trainer = tf.keras.optimizers.SGD(learning_rate=lr)
# 创建一个动画演示对象
animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log',
xlim=[5, num_epochs], legend=['train', 'test'])
# 遍历训练轮数
for epoch in range(num_epochs):
# 遍历训练数据的批次
for X, y in train_iter:
# 使用梯度带自动计算损失
with tf.GradientTape() as tape:
# 计算损失
l = loss(net(X), y) + net.losses
# 计算梯度
grads = tape.gradient(l, net.trainable_variables)
# 使用优化器更新模型参数
trainer.apply_gradients(zip(grads, net.trainable_variables))
# 每5轮更新一次动画演示
if (epoch + 1) % 5 == 0:
animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss),
d2l.evaluate_loss(net, test_iter, loss)))
# 打印模型参数w的L2范数
print('w的L2范数:', tf.norm(net.get_weights()[0]).numpy())
其中解释一下计算损失时使用的l = loss(net(X), y) + net.losses
loss(net(X), y)
: 这一项表示模型在当前输入 X 下的预测结果与真实标签 y 之间的损失,这使用了均方误差损失函数。
net.losses
: 这一项表示模型中的额外正则化损失,通过 net.losses 获取。在模型中,我们在第5行定义了正则化项,这些正则化项会添加到模型的 losses 列表中,然后通过 net.losses 获取并加到主要损失上。
train_concise(0)
结果:
w的L2范数: 1.2884972
train_concise(3)
结果:
w的L2范数: 0.033107705