真实应用中想要的是通过训练出来的模型对未知的数据给出判断。
过拟合的概念
过拟合,指的是当一个模型过为复杂之后,它可以很好地“记忆”每一个训练数据中随机噪音的部分而忘记了要去“学习”训练数据中通用的趋势。
举个例子:
如果一个模型中的参数比训练数据的总数还多,那么只要训练数据不冲突,这个模型完全可以记住所有训练数据的结果从而使得损失函数为 0。
直观想象:
一个包含 n 个变量和 n 个等式的方程组,当方程不冲突时,这个方程组是可以通过数学的方法来求解。
结论:
过拟合训练数据中的随机噪音虽然可以得到非常小的损失函数,但是对于未知数据可能无法做出可靠的判断。
模型训练的三种情况
- 第一种情况:模型过于简单,无法刻画问题的趋势
- 第二种情况:模型比较合理,既不会国语关注训练数据中的噪音,又能够比较好地刻画问题的整体趋势。
- 第三种情况:模型过拟合。虽然完美的划分了不同形状的点,但这样的划分并不能很好地对未知数据做出判断。因为它过度拟合了训练数据中的噪音而忽视了问题的整体规律。
避免过拟合的方法
正则化
思想: 在损失函数中加入刻画模型复杂程度的指标。
假设用于刻画模型在训练数据上表现的损失函数为 J ( θ ) J(\theta) J(θ),那么在优化时不是直接优化 J ( θ ) J(\theta) J(θ),而是优化 J ( θ ) + λ R ( w ) J(\theta)+\lambda R(w) J(θ)+λR(w)。
- R ( w ) R(w) R(w) 刻画模型的复杂程度;
- λ \lambda λ 表示模型复杂损失在总损失中的比例。
- θ \theta θ 表示一个神经网络中所有的参数,它包括边上的权重 w w w 和偏置项 b b b。
一般模型的复杂程度由权重 w w w 决定。
模型的复杂度
常见的刻画模型复杂度的函数 R ( w ) R(w) R(w) 有两种:
-
L
1
L1
L1 正则化。计算公式是:
R ( w ) = ∥ w ∥ 1 = ∑ i ∣ w i ∣ R(w) = \parallel w\parallel_1 = \sum_i \mid w_i\mid R(w)=∥w∥1=i∑∣wi∣ -
L
2
L2
L2 正则化。计算公式是:
R ( w ) = ∥ w ∥ 2 2 = ∑ i ∣ w i 2 ∣ R(w) = \parallel w\parallel_2^2 = \sum_i \mid w_i^2\mid R(w)=∥w∥22=i∑∣wi2∣
基本思想
通过限制权重的大小,使得模型不能任意拟合训练数据中的随机噪音。
两种正则化方法的区别
第一个区别:
L
1
L1
L1 正则化会让参数变得更稀疏——会有更多的参数变为 0,这样可以达到类似特征选取的功能;
L
2
L2
L2 正则化不会,因为当参数很小时,比如 0.001,这个参数的平方基本上是可以忽略了,于是模型不会进一步将这个参数调整为 0。
第二个区别:
L
1
L1
L1 正则化的计算公式不可导;
L
2
L2
L2 正则化的计算公式可导。
因为在优化时需要计算损失函数的偏导数,所以对含有 L 2 L2 L2 正则化损失函数的优化要更加简洁。
在实践中,可以将 L 1 L1 L1 正则化和 L 2 L2 L2 正则化同时使用:
R ( w ) = ∑ i α ∣ w i ∣ + ( 1 − α ) w i 2 R(w) = \sum_i \alpha|w_i| + (1- \alpha)w_i^2 R(w)=i∑α∣wi∣+(1−α)wi2
用 TensorFlow 优化带正则化的损失函数
函数 tf.contrib.layers.l1_regularizer
解析: 该函数返回一个函数,这个函数可以计算一个给定参数的 L 1 L1 L1 正则化项的值。
以下代码给出一个简单的带 L 1 L1 L1 正则化的损失函数的定义:
w = tf.Variable(tf.random_normal([2, 1], stddev=1, seed=1))
y = tf.matmul(x, w)
loss = tf.reduce_mean(tf.square(y_ - y)) + tf.contrib.layers.l1_regularizer(lambda)(w)
# loss 为损失函数
# 由两部分组成:
# 第一部分是均方误差损失函数,刻画了模型在训练数据上的表现
# 第二部分是 L1 正则化,防止模型过度模拟训练数据中的随机噪音
# lambda 参数表示了正则化的权重
# w 为需要计算正则化损失的参数
函数 tf.contrib.layers.l2_regularizer
解析: 该函数返回一个函数,这个函数可以计算一个给定参数的 L 2 L2 L2 正则化项的值。
以下代码给出一个简单的带 L 2 L2 L2 正则化的损失函数的定义:
w = tf.Variable(tf.random_normal([2, 1], stddev=1, seed=1))
y = tf.matmul(x, w)
loss = tf.reduce_mean(tf.square(y_ - y)) + tf.contrib.layers.l2_regularizer(lambda)(w)
# loss 为损失函数
# 由两部分组成:
# 第一部分是均方误差损失函数,刻画了模型在训练数据上的表现
# 第二部分是 L2 正则化,防止模型过度模拟训练数据中的随机噪音
# lambda 参数表示了正则化的权重
# w 为需要计算正则化损失的参数
示例
weights = tf.constant([[1.0, -2.0], [-3.0, 4.0]])
with tf.Sesson() as sess:
# 输出为 (|1|+|-2|+|-3|+|4|)*0.5=5。其中 0.5 为正则化项的权重
print sess.run(tf.contrib.layers.l1_regularizer(.5)(weights))
# 输出为 ((|1)^2+(-2)^2+(-3)^2+(4)^2)/2*0.5=7.5。
print sess.run(tf.contrib.layers.l2_regularizer(.5)(weights))
在简单的神经网络中,上面的这种方式就可以很好地计算带正则化的损失函数。
但是,当:
- 神经网络参数增多时 —> 导致损失函数 loss 的定义很长,可读性差而且容易出错;
- 神经网络复杂之后定义网络结构的部分和计算损失函数的部分可能不在同一个函数中 —> 这种通过变量方式计算损失函数不方便。
解决方案:
使用 TensorFlow 中提供的集合(collection),可以在一个计算图(tf.Graph)中保存一组实体(比如张量)。
import tentsorflow as tf
# 获取一层神经网络边上的权重,并将这个权重的 L2 正则化损失加入名称为 'losses' 的集合中
def get_weight(shape, lambda):
# 生成一个变量
var = tf.Variable(tf.random_normal(shape), dtype = tf.float32)
# add_to_collection 函数将这个新生成变量的 L2 正则化损失加入集合。
# 这个函数的第一个参数 'losses' 是集合的名字,第二个参数就是要加入这个集合的内容
tf.add_to_collection('losses',tf.contrib.layers.l2_regularizer(lambda)(var))
# 返回生成的变量
return var
x = tf.placeholder(tf.float32, shape=(None, 2))
y_ = tf.placeholder(tf.float32, shape=(None, 1))
batch_size = 8
# 定义了每一层网络中节点的个数。
layer_dimension = [2, 10, 10, 10, 1]
# 神经网络的层数。
n_layers = len(layer_dimension)
# 这个变量维护前向传播时最深层的节点,开始的时候就是输入层。
cur_layer = x
# 当前层的节点个数。
in_dimension = layer_dimension[0]
# 通过一个循环来生成 5 层全连接的神经网络
for i in range(1, n_layers):
# layer_dimension[i]为下一层的节点个数
out_dimension = layer_dimension[i]
# 生成当前层中权重的变量,并将这个变量的 L2 正则化损失加入计算图上的集合。
weight = get_weight([in_dimension, out_dimension], 0.001)
bias = tf.Variable(tf.constant(0.1, shape=[out_dimension]))
# 使用 ReLU 激活函数。
cur_layer = tf.nn.relu(tf.matmul(cur_layer, weight) + bias)
# 进入下一层之前将下一层的节点个数更新为当前层结点个数。
in_dimension = layer_dimension[i]
# 在定义神经网络前向传播的同时已经将所有的 L2 正则化损失加入了图上的集合,
# 这里只需要计算刻画模型在训练数据上表现的损失函数
mse_loss = tf.reduce_mean(tf.square(y_ - cur_layer))
# 将均方误差损失函数加入损失集合
tf.add_to_collection('losses', mse_loss)
# get_collection 返回一个列表,这个列表是所有这个集合中的元素。
# 在这个样例中,这些元素就是损失函数的不同部分,将它们加起来就可以得到最终的损失函数
loss = tf.add_n(tf.get_collection('losses'))