前言
目的:控制模型的学习容量,减弱过拟合的风险,降低模型的复杂度。
形式:
1、在损失函数(目标函数)后添加带系数的惩罚项;
2、提前终止训练;
3、dropout
4、训练集增强
L1、L2范数
范数定义:
假设 x 是一个向量,它的 L^p 范数定义:
下面对L1和L2在损失函数上的作用说明:
1式中可以看出,对于范数L1的正则化,会让特征变得稀疏,起到特征选择的作用。因为若 w_1 为正数,则每次更新会减去一个常数;若 w_1 为负数,则每次更新会加上一个常数,所以很容易产生特征的系数为 0 的情况。
2式可以看出,L2正则化会让模型变得更简单,防止过拟合,而不会起到特征选择的作用。从上式可以看出每次更新时,会对特征系数进行一个比例的缩放,而不是减去一个常数项。
代码展示 tf.nn.l2_loss
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['font.sans-serif']=['SimHei']
#⽤来正常显示中⽂标签
plt.rcParams['axes.unicode_minus']=False
#⽤来正常显示负号 #有中⽂出现的情况,需要u'内容'
import tensorflow as tf
# 生成含有噪音的y = 3x^2 + x 的数据集
ran = np.random.RandomState(seed = 1)
x = ran.rand(100)*10 - 5
y = np.array([(3*i**2 + i +ran.rand()*20) for i in x])
# 将ndarray数据集转为tensor
x_ = tf.convert_to_tensor(value = x.reshape((100,1)),dtype = tf.float32)
y_ = tf.convert_to_tensor(value = y.reshape((100,1)),dtype = tf.float32)
data = tf.data.Dataset.from_tensor_slices((x_,y_)).batch(32)
# 构建网络
epoch = 1000
h = 0.004 #学习率
w1 = tf.Variable(tf.random.normal([1,10],mean = 0 ,stddev = 1 ,seed= 123))
b1 = tf.Variable(tf.random.normal([1,10],mean = 0 ,stddev = 1 ,seed= 123))
w2 = tf.Variable(tf.random.normal([10,1],mean = 0 ,stddev = 1 ,seed= 123))
b2 = tf.Variable(tf.random.normal([1,1],mean = 0 ,stddev = 1 ,seed= 123))
for i in range(epoch):
loss_num = 0
for x_train,y_train in data:
with tf.GradientTape() as tape:
y_pred = tf.matmul(x_train,w1) + b1 # x_train (32,1) y_pred (32,10)
y_pred = tf.nn.relu(y_pred) # 激活函数,将所有值为负数的置为0
y_pred = tf.matmul(y_pred,w2) + b2 # (32,1)
loss_mse = tf.reduce_mean(tf.square(y_pred-y_train))
loss_num += loss_mse.numpy()
grads = tape.gradient(loss_mse,[w1,b1,w2,b2]) #基于loss计算梯度,分别求偏导
w1.assign_sub(h*grads[0])
b1.assign_sub(h*grads[1])
w2.assign_sub(h*grads[2])
b2.assign_sub(h*grads[3])
if i % 50 == 0:
print("Epoch:%d,\tloss=%.5f"%(i+1,loss_num/4))
x_test = np.linspace(-5,5,100).reshape((100,1))
x_test = tf.convert_to_tensor(x_test,dtype=tf.float32)
y_test = tf.matmul(x_test,w1) + b1
y_test = tf.nn.relu(y_test)
y_test = tf.matmul(y_test,w2) + b2
y_test = tf.nn.relu(y_test)
plt.scatter(x,y)
plt.plot(x_test,y_test.numpy(),'r')
plt.title("没加正则化")
plt.show()
如果加上正则化
# 构建网络
epoch = 1000
h = 0.004 #学习率
w1 = tf.Variable(tf.random.normal([1,10],mean = 0 ,stddev = 1 ,seed= 123))
b1 = tf.Variable(tf.random.normal([1,10],mean = 0 ,stddev = 1 ,seed= 123))
w2 = tf.Variable(tf.random.normal([10,1],mean = 0 ,stddev = 1 ,seed= 123))
b2 = tf.Variable(tf.random.normal([1,1],mean = 0 ,stddev = 1 ,seed= 123))
for i in range(epoch):
loss_num = 0
for x_train,y_train in data:
with tf.GradientTape() as tape:
y_pred = tf.matmul(x_train,w1) + b1 # x_train (32,1) y_pred (32,10)
y_pred = tf.nn.relu(y_pred) # 激活函数,将所有值为负数的置为0
y_pred = tf.matmul(y_pred,w2) + b2 # (32,1)
loss_mse = tf.reduce_mean(tf.square(y_pred-y_train))
loss_reg = tf.reduce_sum([tf.nn.l2_loss(w1),tf.nn.l2_loss(w2)])
loss_mse += loss_reg
loss_num += loss_mse.numpy()
grads = tape.gradient(loss_mse,[w1,b1,w2,b2]) #基于loss计算梯度,分别求偏导
w1.assign_sub(h*grads[0])
b1.assign_sub(h*grads[1])
w2.assign_sub(h*grads[2])
b2.assign_sub(h*grads[3])
if i % 50 == 0:
print("Epoch:%d,\tloss=%.5f"%(i+1,loss_num/4))
可以发现加上正则化的模型更加简单,如上图在x=0附近的曲线更加平滑。
提前终止训练
当模型采用迭代法进行优化时,如果一直迭代下去,训练集的错误率会缓慢下降,但验证集的错误率会先下降再上升,这表明模型发生了过拟合。所以,我们可以在验证集错误率最低的时候终止训练,利用此时的参数作为模型的最终参数,可以期望获得更好的测试表现。从控制过拟合的观点看,训练次数也是模型的超参数,这个超参数在验证集上具有U形的性能曲线。训练次数只需要对模型训练一次,就可以尝试很多值的超参数。提前终止十分有效,所以是最常用的正则化方法。提前终止法的缺点是需要验证集,而此时验证集的样本就不能用来训练模型,这导致样本的利用率不高。
dropout
dropout 是一种计算方便但功能强大的正则化方法,适用于最近很火的神经网络。他的基本步骤是在每一次的迭代中,随机删除一部分节点,只训练剩下的节点。每次迭代都会随机删除,每次迭代删除的节点也都不一样,相当于每次迭代训练的都是不一样的网络,通过这样的方式降低节点之间的关联性以及模型的复杂度,从而达到正则化的效果。
训练集增强
更大数量的训练集是提升机器学习模型泛化能力最好的方法。对于图片来说,我们可以对他采用小幅旋转,平移,放大,缩小甚至给图片加上波动等方法,来创造出现在数据量的几倍。