本文内容整理自中国大学MOOC “北京大学-人工智能实践:Tensorflow笔记” 课程,部分内容可能在原笔记内容上进行了修改,转载请注明出处。
授课老师:曹健
中国大学MOOC 人工智能实践:Tensorflow笔记课程链接
本讲目标:学会神经网络优化过程,使用正则化减少过拟合,使用优化器更新网络参数
本节内容:本节将在上一讲的基础上进一步讨论神经网络复杂度、学习率策略、激活函数、损失函数等概念。
神经网络(Neural Network,NN
)的复杂度分为 空间复杂度 和 时间复杂度 。
1.空间复杂度(访存量):
严格来讲包括两部分:总参数量 + 各层输出特征图。
~~ 参数量:模型所有带参数的层的权重参数总量;
~~ 特征图:模型在实时运行过程中每层所计算出的输出特征图大小。
可简单地用NN层数和NN参数的个数衡量,其中:
~~ 层数 = 隐藏层的层数 + 1个输出层 ;
~~ 总参数 = 总w + 总b (即输入层加隐藏层总参数个数)
2.时间复杂度:
即模型的运算次数,可用浮点运算次数(FPLOPs, FLoating-point OPerations)或者乘加运算次数衡
量。
图1 一个简单的神经网络模型 下面简单计算该神经网络模型的复杂度:
空间复杂度(可用NN层数和NN参数的个数衡量):
~~ 层数:1层隐藏层,1层输出层,共2层NN;
~~ 总参数:共有 [3×4+4](第1层) + [4×2+2](第2层) = 26个;
时间复杂度(可用乘加运算次数衡量):
~~ 上图共有 3×4(第1层) + 4×2(第2层) = 20次乘加运算。
在前面的学习中,我们已经知道:
- 神经网络的迭代过程 就是 更新网络参数使得神经网络的预测值和标准答案差距(也即损失函数)更小的过程;
- 梯度下降法(Gradient Descent,GD) 是一种行之有效的更新参数从而寻找损失函数最小值的方法,其基本公式如下: w t + 1 = w t − l r × ∂ l o s s ∂ w t \boxed{w_{t+1}=w_t-lr \times \frac{\partial{loss}}{\partial{w_t}}} wt+1=wt−lr×∂wt∂loss
式中 w t w_t wt为当前参数;
l r lr lr为学习率,是一个重要的超参数;
∂ l o s s ∂ w t \frac{\partial{loss}}{\partial{w_t}} ∂wt∂loss为损失函数的梯度(偏导数);
w t + 1 w_{t+1} wt+1为更新后的参数;- 学习率 l r lr lr 影响梯度下降法迭代过程中参数的更新速度:
学习率 l r lr lr设置较小,参数更新可能会很慢;
学习率 l r lr lr设置过大,参数更新可能会跳过最小值,出现 “梯度爆炸” 现象。
为解决初始化学习率面临的困境,可引入动态调整的学习率:
设置开始训练时学习率较大,以便程序可以快速得到一个比较优的解;
然后随着迭代的次数来逐步减少学习率,让模型在训练后期更加稳定,防止出现 “梯度爆炸” 现象。
以下给出两种动态调整学习率的策略:
指数型学习率衰减法是最常用的衰减方法,在大量模型中广泛使用。
定义
:
指数衰减学习率 = 初始学习率 × 学习率衰减 率 ( 当前轮数 / 多少轮衰减一次 ) d e c a y e d _ l r = l r _ s t a r t e r × d e c a y _ r a t e g l o b a l _ s t e p d e c a y _ s t e p s \begin{gathered}指数衰减学习率= 初始学习率 \times 学习率衰减率 ^ {(当前轮数/多少轮衰减一次)}\\ decayed\_lr= lr\_starter \times decay\_rate ^ {\frac{global\_step}{decay\_steps}} \end{gathered} 指数衰减学习率=初始学习率×学习率衰减率(当前轮数/多少轮衰减一次)decayed_lr=lr_starter×decay_ratedecay_stepsglobal_step
代码直接实现
:
decayed_lr= lr_starter * decay_rate ** (epoch / decay_steps)
TensorFlow API
:
tf.keras.optimizers.schedules.ExponentialDecay
tf.keras.optimizers.schedules.ExponentialDecay(initial_learning_rate, decay_steps, decay_rate, staircase=False, name=None)
功能: 指数衰减学习率策略.
等价API:tf.optimizers.schedules.ExponentialDecay
参数:
·lr_starter
: 初始学习率.
·decay_steps
: 衰减步数, staircase为True时有效.
·decay_rate
: 衰减率.
·staircase
: Bool型变量.如果为True, 学习率呈现阶梯型下降趋势.
返回:tf.keras.optimizers.schedules.ExponentialDecay(step)
~~ 返回计算得到的学习率.
举例如下:
import tensorflow as tf
from matplotlib import pyplot as plt
N = 400
lr_schedule_1 = tf.keras.optimizers.schedules.ExponentialDecay(0.5,decay_steps=10,decay_rate=0.9,staircase=False)
lr_schedule_2 = tf.keras.optimizers.schedules.ExponentialDecay(0.5,decay_steps=10,decay_rate=0.9,staircase=True)
y = []
z = []
for global_step in range(N):
y.append( lr_schedule_1(global_step) )
z.append( lr_schedule_2(global_step) )
plt.figure(figsize=(8,6))
plt.plot(y,'r-',label="$staircase=False$")
plt.plot(z,'b-',label="$staircase=True$")
plt.legend()
plt.ylim([0,max(plt.ylim())])
plt.xlabel('Step')
plt.ylabel('Learning Rate')
plt.title('ExponentialDecay')
plt.show()
图1 指数衰减学习率 ExponentialDecay函数测试
分段常数衰减 可以让调试人员针对不同任务设置不同的学习率,进行精细调参,在任意步长后下降任意数值的 learning rate,要求调试人员对模型和数据集有深刻认识。
TensorFlow API
:
tf.keras.optimizers.schedules.PiecewiseConstantDecay
tf.keras.optimizers.schedules.PiecewiseConstantDecay(boundaries, values, name=None)
功能: 分段常数衰减学习率策略.
等价API:tf.optimizers.schedules.PiecewiseConstantDecay
参数:
·boundaries
: [step_1, step_2, …, step_n] 定义了在第几步进行学习率衰减.
·values
: [val_0, val_1, val_2, …, val_n] 定义了学习率的初始值和后续衰减时的具体取值.
返回:tf.keras.optimizers.schedules.PiecewiseConstantDecay(step)
~~ 返回计算得到的学习率.
举例如下:
import tensorflow as tf
from matplotlib import pyplot as plt
N = 400
lr_schedule = tf.keras.optimizers.schedules.PiecewiseConstantDecay(boundaries=[100, 200, 300],values=[0.1, 0.05, 0.025, 0.001])
y = []
for global_step in range(N):
lr = lr_schedule(global_step)
y.append(lr)
x = range(N)
plt.figure(figsize=(8,6))
plt.plot(x, y, 'r-')
plt.ylim([0,max(plt.ylim())])
plt.xlabel('Step')
plt.ylabel('Learning Rate')
plt.title('PiecewiseConstantDecay')
plt.show()
图2 分段常数衰减学习率 PiecewiseConstantDecay函数测试
激活函数是用来加入非线性因素的,因为线性模型的表达能力不够。引入非线性激活函数,可使深层神经网络的表达能力更加强大。
图3 神经元简化模型
模型缺点:由于其前向传播函数是一个线性函数,即使由多层神经元首尾相接构成深层神经网络,依旧是线性组合,依然可等价为单层神经网络。
而在1943年提出的MP模型中,多了一个非线性函数。这个非线性函数,被称为 激活函数。
图4 神经元MP模型
激活函数的加入加强了模型的表达力,使得深层网络不再是输入 x x x 的线性组合,可以随着层数增加提升表达力。
优秀的激活函数应满足:
· 非线性: 激活函数非线性时,多层神经网络可逼近所有函数;
· 可微性: 优化器大多用梯度下降法更新参数;
· 单调性: 当激活函数是单调的,能保证单层网络的损失函数是凸函数;
· 近似恒等性: 即 f ( x ) ≈ x f(x)\approx x f(x)≈x ,当参数初始化为随机小值时,神经网络更稳定;
激活函数输出值的范围:
· 激活函数输出为有限值时,基于梯度的优化方法更稳定;
· 激活函数输出为无限值时,建议调小学习率;
常见的激活函数有:
sigmoid
, tanh
, ReLU
, Leaky ReLU
, PReLU
, RReLU
, ELU(Exponential Linear Units)
, softplus
, softsign
, softmax
等。
下面介绍几个典型的激活函数及其API:
定义
: f ( x ) = 1 1 + e − x f(x)=\frac{1}{1+e^{-x}} f(x)=1+e−x1
函数图像及其导函数图像
:
图5 sigmoid 函数图像
sigmoid 导函数图像:图6 sigmoid 导函数图像
sigmoid 函数作为激活函数的优缺点
:
优点 :
1.输出映射在(0,1)之间,单调连续,输出范围有限,优化稳定,可用作输出层;
2.求导容易。
缺点 :
1.易造成梯度消失;
2.输出非0均值,收敛慢;
3.幂运算复杂,训练时间长。
sigmoid 函数总结
:
sigmoid 函数可应用在训练过程中。然而,当处理分类问题作出输出时,sigmoid却无能为力。
简单地说,sigmoid函数只能处理两个类,不适用于多分类问题。而softmax可以有效解决这个问题,并且softmax函数大都运用在神经网路中的最后一层网络中,使得值得区间在(0,1)之间,而不是二分类的。
TensorFlow API
:
tf.math.sigmoid
tf.math.sigmoid(x, name=None)
功能: 计算x每一个元素的sigmoid值.
等价API:tf.nn.sigmoid, tf.sigmoid
参数:x
: 张量x.
返回:与 x shape相同的张量.
举例如下:
#输入:
import tensorflow as tf
x = tf.constant([1., 2., 3.], )
print(tf.math.sigmoid(x))
# 等价实现
print(1/(1+tf.math.exp(-x)))
输出:
tf.Tensor([0.7310586 0.8807971 0.95257413], shape=(3,), dtype=float32)
tf.Tensor([0.7310586 0.880797 0.95257413], shape=(3,), dtype=float32)
定义
: f ( x ) = 1 − e − 2 x 1 + e − 2 x f(x)=\frac{1-e^{-2x}}{1+e^{-2x}} f(x)=1+e−2x1−e−2x
函数图像及其导函数图像
:
图7 tanh 函数图像
tanh 导函数图像:
tanh 函数作为激活函数的优缺点
:
优点 :
1.比sigmoid函数收敛速度更快。
2.相比sigmoid函数,其输出以0为中心。
缺点 :
1.易造成梯度消失;
2.幂运算复杂,训练时间长。
TensorFlow API
:
tf.math.tanh
tf.math.tanh(x, name=None)
功能: 计算x每一个元素的双曲正切值.
等价API:tf.nn.tanh, tf.tanh
参数:x
: 张量x.
返回:与 x shape相同的张量.
举例如下:
#输入:
import tensorflow as tf
x = tf.constant([-float("inf"), -5, -0.5, 1, 1.2, 2, 3, float("inf")])
print(tf.math.tanh(x))
# 等价实现
print((tf.math.exp(x)-tf.math.exp(-x))/(tf.math.exp(x)+tf.math.exp(-x)))
输出:
tf.Tensor([-1. -0.99990916 -0.46211717 0.7615942 0.8336547 0.9640276 0.9950547 1.], shape=(8,), dtype=float32)
tf.Tensor([nan -0.9999091 -0.46211714 0.7615942 0.83365464 0.9640275 0.9950547 nan], shape=(8,), dtype=float32)
定义
: f ( x ) = max ( 0 , 1 ) = { 0 if x < 0 x if x ≥ 0 f(x)=\max (0,1)=\begin{cases} 0 &\text{if }x<0 \\ x &\text{if } x\geq0 \end{cases} f(x)=max(0,1)={0xif x<0if x≥0
函数图像及其导函数图像
:
图9 ReLU 函数图像
ReLU 导函数图像:图10 ReLU 导函数图像
ReLU 函数作为激活函数的优缺点
:
优点 :
1.解决了梯度消失问题(在正区间);
2.只需判断输入是否大于0,计算速度快;
3.收敛速度远快于sigmoid和tanh,因为sigmoid和tanh涉及很多expensive的操作;
4.提供了神经网络的稀疏表达能力。
缺点 :
1.输出非0均值,收敛慢;
2.Dead ReLU问题:某些神经元可能永远不会被激活,导致相应的参数永远不能被更新。
Dead ReLU问题说明
:
Dead ReLU问题 : 由于当送入ReLU 激活函数的输入特征是负数时,激活函数输出是0,反向传播得到的梯度是0,导致参数无法更新,造成神经元 “死亡”。
解决思路:造成神经元死亡的根本原因是经过ReLU函数的负数特征过多导致。
· 可以改进随机初始化,避免过多的负数特征送入ReLU函数。
· 可以通过设计更小的学习率,减少参数分布的巨大变化,避免训练中产生过多负数特征进入ReLU函数。
TensorFlow API
:
tf.nn.relu
tf.nn.relu(features, name=None)
功能: 计算修正线性值(rectified linear):max(features, 0).
参数:features
: 张量.
返回:与 features shape相同的张量.
举例如下:
#输入:
import tensorflow as tf
x = tf.constant([-2., 0., -0., 3.], )
print(tf.nn.relu(x))
# 等价实现
print( tf.where(tf.greater(x,0),x,0) )
输出:
tf.Tensor([ 0. 0. -0. 3.], shape=(4,), dtype=float32)
tf.Tensor([0. 0. 0. 3.], shape=(4,), dtype=float32)
定义
: f ( x ) = max ( α x , x ) f(x)=\max(\alpha x,x) f(x)=max(αx,x)
函数图像及其导函数图像
:
图11 Leaky Relu 函数图像
Leaky Relu 导函数图像:图12 Leaky Relu 导函数图像
Leaky Relu 函数说明
:
理论上来讲,Leaky ReLU有ReLU的所有优点,外加不会有Dead ReLU问题,但是在实际操作当中,并没有完全证明Leaky ReLU总是好于ReLU。
TensorFlow API
:
tf.nn.leaky_relu
tf.nn.leaky_relu(features, alpha=0.2, name=None)
功能: == 计算Leaky ReLU值==.
等价API:tf.nn.sigmoid, tf.sigmoid
参数:
·features
: 张量.
·alpha
: x<0时的斜率值.
返回:与 features shape相同的张量.
举例如下:
#输入:
import tensorflow as tf
x = tf.constant([-2., 0., -0., 3.], )
print(tf.nn.leaky_relu(x))
# 等价实现
alpha = 0.2
print( tf.where(tf.greater(x,0),x,alpha*x) )
输出:
tf.Tensor([-0.4 0. -0. 3. ], shape=(4,), dtype=float32)
tf.Tensor([-0.4 0. -0. 3. ], shape=(4,), dtype=float32)
定义
: σ ( z ) j = e z j ∑ k = 1 K e z k for j = 1 , … , K \sigma(\boldsymbol{z})_{j}=\frac{e^{z_{j}}}{\sum_{k=1}^{K} e^{z_{k}}} \quad \text { for } j=1, \ldots, K σ(z)j=∑k=1Kezkezj for j=1,…,K
softmax 函数说明
:
对神经网络全连接层输出进行变换,使其服从概率分布,即每个值都位于 [0,1] 区间且和为1。
TensorFlow API
:
tf.nn.softmax
tf.nn.softmax(logits, axis=None, name=None)
功能: 计算softmax激活值.
等价API:tf.math.softmax
参数:
·logits
: 张量.
·axis
: 计算softmax所在的维度. 默认为-1,即最后一个维度.
返回:与 logits shape相同的张量.
举例如下:
#输入:
import tensorflow as tf
logits = tf.constant([4., 5., 1.])
print(tf.nn.softmax(logits))
# 等价实现
print(tf.exp(logits) / tf.reduce_sum(tf.exp(logits)))
输出:
tf.Tensor([0.26538792 0.7213992 0.01321289], shape=(3,), dtype=float32)
tf.Tensor([0.26538792 0.72139925 0.01321289], shape=(3,), dtype=float32)
对于初学者的建议:
- 首选ReLU激活函数;
- 学习率设置较小值;
- 输入特征标准化,即让输入特征满足以0为均值,1为标准差的正态分布;
- 初始化问题:初始参数中心化,即让随机生成的参数满足以0为均值, 1 当前层输入特征个数 \sqrt{\frac{1}{当前层输入特征个数}} 当前层输入特征个数1为标准差的正态分布。
损失函数(loss):描述了预测值(y)与已知答案(y_)的差距,主流loss的计算方法有如下三种:
NN优化目标: l o s s 最小 ⟹ { 均方误差损失函数 mse (Mean Squared Error) 自定义 交叉熵损失函数 ce (Crose Entropy) \text{NN优化目标:} loss最小\implies\begin{cases} \text{均方误差损失函数 mse (Mean Squared Error)}\\ \text{自定义} \\ \text{交叉熵损失函数 ce (Crose Entropy)} \end{cases} NN优化目标:loss最小⟹⎩ ⎨ ⎧均方误差损失函数 mse (Mean Squared Error)自定义交叉熵损失函数 ce (Crose Entropy)
定义
:
均方误差(Mean Square Error) 是回归问题最常用的损失函数。
回归问题解决的是对具体数值的预测,比如房价预测、销量预测等。这些问题需要预测的不是一个事先定义好的类别,而是一个任意实数。
均方误差定义如下: MSE ( y , y ′ ) = ∑ i = 1 n ( y i − y i ′ ) 2 n \operatorname{MSE}\left(y, y^{\prime}\right)=\frac{\sum_{i=1}^{n}\left(y_{i}-y_{i}^{\prime}\right)^{2}}{n} MSE(y,y′)=n∑i=1n(yi−yi′)2其中 y i y_i yi 为一个batch中第 i 个数据的真实值,而 y i ′ y_i^{\prime} yi′ 为神经网络的预测值。
TensorFlow API
:
tf.keras.losses.MSE
tf.keras.losses.MSE(y_true, y_pred)
功能: 计算 y_true 和 y_pred 的均方误差.
举例如下:
#输入:
import tensorflow as tf
y_true = tf.constant([0.5, 0.8])
y_pred = tf.constant([1.0, 1.0])
print(tf.keras.losses.MSE(y_true, y_pred))
# 等价实现
print(tf.reduce_mean(tf.square(y_true - y_pred)))
输出:
tf.Tensor(0.145, shape=(), dtype=float32)
tf.Tensor(0.145, shape=(), dtype=float32)
根据具体任务和目的,可设计不同的损失函数。
损失函数的定义能极大影响模型预测效果,好的损失函数设计对于模型训练能够起到良好的引导作用。
自定义损失函数往往可以被表示为每一个预测结果 y 与标准答案 y_ 产生的损失累积和,其中损失 f ( y _ , y ) f(y\_,y) f(y_,y) 可被定义为分段函数。
l o s s ( y _ , y ) = ∑ n f ( y _ , y ) loss(y\_,y)=\sum_n f(y\_,y) loss(y_,y)=n∑f(y_,y)式中 y − y_{-} y− 代表数据的真实值, y y y 代表神经网络的预测值。
在实际中,往往需要针对特定的背景、具体的任务设计损失函数。
例如,在目标检测中就存在多种损失函数。
由于目标检测的主要功能是定位和识别,损失函数的功能主要就是让定位更精确,识别准确率更高。
目标检测任务的损失函数由分类损失(Classificition
Loss) 和回归损失(Bounding Box Regeression Loss) 两部分构成。近几年来回归损失主要有Smooth L1 Loss (2015),IoU Loss (2016 ACM),GIoU Loss (2019 CVPR),DIoU Loss & CIoU Loss (2020 AAAI) 等,分类损失有交叉熵、softmax loss、logloss、focal loss等。
定义
:
交叉熵(Cross Entropy)表征两个概率分布之间的距离,交叉熵越小说明二者分布越接近,是分类问题中使用较广泛的损失函数。
交叉熵损失函数定义如下: H ( y − , y ) = − Σ y − ∗ ln y H\left(y_{-}, y\right)=-\Sigma y_{-} * \ln y H(y−,y)=−Σy−∗lny其中 y − y_{-} y− 代表数据的真实值, y y y 代表神经网络的预测值。
说明
:
对于多分类问题,神经网络的输出一般不是概率分布,因此需要引入softmax层,使得输出服从概率分布。
TensorFlow API
:
TensorFlow中可计算交叉熵损失函数的API有:
tf.keras.losses.categorical_crossentropy
;
tf.nn.softmax_cross_entropy_with_logits
;
tf.nn.sparse_softmax_cross_entropy_with_logits
;
分别介绍如下:
tf.keras.losses.categorical_crossentropy
tf.keras.losses.categorical_crossentropy(y_true, y_pred, from_logits=False, label_smoothing=0)
功能: 计算交叉熵.
等价API:tf.losses.categorical_crossentropy
参数:
·y_true
: 真实值.
·y_pred
: 预测值.
·from_logits
: y_pred是否为logits张量.
·label_smoothing
: [0,1]之间的小数.
返回:交叉熵损失值.
举例如下:#输入: import tensorflow as tf y_true = [1, 0, 0] y_pred1 = [0.5, 0.4, 0.1] y_pred2 = [0.8, 0.1, 0.1] print(tf.keras.losses.categorical_crossentropy(y_true, y_pred1)) print(tf.keras.losses.categorical_crossentropy(y_true, y_pred2)) # 等价实现 print(-tf.reduce_sum(y_true * tf.math.log(y_pred1))) print(-tf.reduce_sum(y_true * tf.math.log(y_pred2))) 输出: tf.Tensor(0.6931472, shape=(), dtype=float32) tf.Tensor(0.22314353, shape=(), dtype=float32) tf.Tensor(0.6931472, shape=(), dtype=float32) tf.Tensor(0.22314353, shape=(), dtype=float32)
tf.nn.softmax_cross_entropy_with_logits
tf.nn.softmax_cross_entropy_with_logits(labels, logits, axis=-1, name=None)
功能: logits经过softmax后,与labels进行交叉熵计算.
说明:在机器学习中,对于多分类问题,把未经softmax归一化的向量值称为logits。logits经过softmax层后,输出服从概率分布的向量。(源自 stackoverflow网站回答)
参数:
·labels
: 在类别这一维度上,每个向量应服从有效的概率分布.
例如,在labels的shape为 [batch_size, num_classes] 的情况下,labels[i]应服从概率分布.
·logits
: 每个类别的激活值,通常是线性层的输出. 激活值需要经过softmax归一化.
·axis
: 类别所在维度,默认是-1,即最后一个维度.
返回:softmax交叉熵损失值.
举例如下:#输入: import tensorflow as tf labels = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]] logits = [[4.0, 2.0, 1.0], [0.0, 5.0, 1.0]] print(tf.nn.softmax_cross_entropy_with_logits (labels=labels,logits=logits)) # 等价实现 print(-tf.reduce_sum(labels * tf.math.log(tf.nn.softmax(logits)), axis=1)) 输出: tf.Tensor([0.16984604 0.02474492], shape=(2,), dtype=float32) tf.Tensor([0.16984606 0.02474495], shape=(2,), dtype=float32)
tf.nn.sparse_softmax_cross_entropy_with_logits
tf.nn.sparse_softmax_cross_entropy_with_logits(labels, logits, name=None)
功能: labels经过one-hot编码,logits经过softmax,两者进行交叉熵计算.。
说明:通常labels的shape为 [batch_size] ,logits的shape为 [batch_size, num_classes].
sparse可理解为对labels进行稀疏化处理(即进行one-hot编码).
参数:
·labels
: 标签的索引值.
·logits
: 每个类别的激活值,通常是线性层的输出. 激活值需要经过softmax归一化.
返回:softmax交叉熵损失值.
举例如下:#输入: import tensorflow as tf labels = [0, 1] logits = [[4.0, 2.0, 1.0], [0.0, 5.0, 1.0]] print(tf.nn.sparse_softmax_cross_entropy_with_logits(labels, logits)) # 等价实现 print(-tf.reduce_sum(tf.one_hot(labels, tf.shape(logits)[1]) * tf.math.log(tf.nn.softmax(logits)), axis=1)) 输出: tf.Tensor([0.16984604 0.02474492], shape=(2,), dtype=float32) tf.Tensor([0.16984606 0.02474495], shape=(2,), dtype=float32)
为进一步理解损失函数的作用,体现不同损失函数对神经网络预测结果的影响,以下引入一个例子进行对比,
背景:已知 x1、x2是影响酸奶日销量的两个因素;
目标:预测酸奶日销量 y;
说明:
建模前应预先采集的数据有:每日x1、x2和销量y_。
为简单起见,此处采用计算机程序拟造数据集 X,Y_ ,并取 y_=x1+x2,并加入 -0.05~0.05 的噪声,即满足:y_=x1+x2+噪音(-0.05~0.05)
由于理想情况下 “产量=销量”,而之前定义了 “销量y_=影响因素x1+影响因素x2”,故预测结果应当为 “y=x1+x2”。
使用均方误差损失函数(mse)作为损失函数的程序和结果如下:
程序:
import tensorflow as tf import numpy as np SEED = 23455 rdm = np.random.RandomState(seed=SEED) # 生成[0,1)之间的随机数 x = rdm.rand(32, 2) y_ = [[x1 + x2 + (rdm.rand() / 10.0 - 0.05)] for (x1, x2) in x] # 生成噪声[0,1)/10=[0,0.1); [0,0.1)-0.05=[-0.05,0.05) x = tf.cast(x, dtype=tf.float32) w1 = tf.Variable(tf.random.normal([2, 1], stddev=1, seed=1)) epoch = 20000 lr = 0.002 for epoch in range(epoch): with tf.GradientTape() as tape: y = tf.matmul(x, w1) loss_mse = tf.reduce_mean(tf.square(y_ - y)) grads = tape.gradient(loss_mse, w1) w1.assign_sub(lr * grads) if epoch % 500 == 0: print("After %d training steps,w1 is " % (epoch)) print(w1.numpy(), "\n") print("Final w1 is: ", w1.numpy())
运行结果:
······ Final w1 is: [[1.003844 ] [0.9952425]]
结果分析:迭代20000轮后,程序输出参数 [[1.003844 ][0.9952425]],也即 :
y = 1.003844 × x 1 + 0.9952425 × x 2 y=1.003844\times x1+0.9952425\times x2 y=1.003844×x1+0.9952425×x2,与理论值 y = x 1 + x 2 y=x1+x2 y=x1+x2相近似,说明预测酸奶日销量的公式拟合正确。
使用均方误差作为损失函数的缺点分析:
分析:使用均方误差作为损失函数,实际上默认认为销量预测多了或少了,损失是一样的。
然而,实际情况是:预测多了,损失成本;预测少了,损失利润。
若 “利润≠成本”,则 mse 产生的 loss 无法利益最大化。
以下使用自定义损失函数:
定义如下自定义的损失函数: l o s s ( y _ , y ) = ∑ n f ( y _ , y ) loss(y\_,y)=\sum_n f(y\_,y) loss(y_,y)=n∑f(y_,y)其中: f ( y _ , y ) = { PROFIT × ( y _ − y ) y < y _ COST × ( y − y _ ) y ≥ y _ f(y\_,y)=\begin{cases} \operatorname{PROFIT} \times (y\_-y) ~~~~ y
f(y_,y)={PROFIT×(y_−y) y<y_COST×(y−y_) y≥y_ 以上公式中的PROFIT与COST为两个参数,即:
预测的 y y y 少了,损失利润(PROFIT);
预测的 y y y 多了,损失成本(COST)。
该损失函数可被代码描述为:loss = tf.reduce_sum(tf.where(tf.greater(y, y_), (y - y_) * COST, (y_ - y) * PROFIT))
该自定义的损失函数中包含了两个参数COST
与PROFIT
,分别反映了预测值偏差引起的成本或利润损失。当参数COST
与PROFIT
大小不相等时,会对预测结果产生不同的影响。
若取 PROFIT>COST :
例如取COST=1,PROFIT=99,即意味着
预测少了损失利润99元,大于预测多了损失成本1元(数据差异较大是为了结果明显)。
由于预测少了损失大,我们期望生成的预测函数会往多了预测。
使用的程序如下:import tensorflow as tf import numpy as np # 自定义损失函数 # 酸奶成本1元, 酸奶利润99元 # 成本很低,利润很高,人们希望多预测些,生成模型系数大于1,往多了预测 SEED = 23455 COST = 1 PROFIT = 99 rdm = np.random.RandomState(SEED) x = rdm.rand(32, 2) y_ = [[x1 + x2 + (rdm.rand() / 10.0 - 0.05)] for (x1, x2) in x] # 生成噪声[0,1)/10=[0,0.1); [0,0.1)-0.05=[-0.05,0.05) x = tf.cast(x, dtype=tf.float32) w1 = tf.Variable(tf.random.normal([2, 1], stddev=1, seed=1)) epoch = 20000 lr = 0.002 for epoch in range(epoch): with tf.GradientTape() as tape: y = tf.matmul(x, w1) loss = tf.reduce_sum(tf.where(tf.greater(y, y_), (y - y_) * COST, (y_ - y) * PROFIT)) grads = tape.gradient(loss, w1) w1.assign_sub(lr * grads) if epoch % 500 == 0: print("After %d training steps,w1 is " % (epoch)) print(w1.numpy(), "\n") print("Final w1 is: ", w1.numpy())
运行结果:
······ Final w1 is: [[1.1214936] [1.0841621]]
结果分析:迭代20000轮后,程序输出参数 [[1.1214936 ][1.0841621]],也即 :
y = 1.1214936 × x 1 + 1.0841621 × x 2 y=1.1214936\times x1+1.0841621\times x2 y=1.1214936×x1+1.0841621×x2,明显大于之前接近 y = x 1 + x 2 y=x1+x2 y=x1+x2 的预测,反映出了损失函数中的损失利润远大于损失成本时,预测系数偏向较大值的趋势,符合理论分析。
若取 COST>PROFIT :
例如取COST=99,PROFIT=1,即意味着
预测少了损失利润1元,大于预测多了损失成本99元。
由于预测多了损失大,我们期望生成的预测函数会往少了预测。
使用的程序在上一实验基础上仅修改了COST
与PROFIT
参数值:COST = 99 PROFIT = 1
运行结果:
······ Final w1 is: [[0.7061546] [0.8436774]]
结果分析:迭代20000轮后,程序输出参数 [[0.7061546 ][0.8436774]],也即 :
y = 0.7061546 × x 1 + 0.8436774 × x 2 y=0.7061546\times x1+0.8436774\times x2 y=0.7061546×x1+0.8436774×x2,反映出了损失函数中的损失成本远大于损失利润时,预测系数偏向较小值的趋势,符合理论分析。
上一节将借助Tensorflow2.0库中的底层代码搭建了一个最基础的神经网络并介绍了优化过程中的部分概念。
人工智能实践:Tensorflow2.0笔记 北京大学MOOC(1-3)
下一讲将介绍神经网络优化过程中的 “过拟合与欠拟合” 有关概念,并简述通过正则化缓解过拟合的方法。
人工智能实践:Tensorflow2.0笔记 北京大学MOOC(2-2)<未完工待续。。。>