目录
- Regression Model 回归模型
- Loss Function 损失函数
- Gradient Descent 梯度下降
- 实例
- 问题:
- 解决办法:
- 另一个例子
Regression Model 回归模型
回归模型用于得到输入数据到输出数据之间的一种映射关系,可以用
来简单表示。
其中w表示网络的权重,b表示偏置。x为网络输入,y为网络输出。
Loss Function 损失函数
损失函数用于评估模型预测(拟合)数据能力的好坏,损失函数值越小,表示模型预测越精确。
显然,损失函数应该有如下几个基本要求:
- 与模型的参数,即w和b有关。
- 损失函数值越小,说明模型预测越精确。
- 可求导,且导函数与w和b有关。
常用的损失函数有如下几种:
- 交叉熵
- 均方差
Gradient Descent 梯度下降
假设输入x,在零误差的情况下网络应该输出y0,然而现在网络输出了y1,接着网络需要更新w和b,使得输出更接近与y0.
根据损失函数的定义,只要变化后的w和b使得损失函数更小,则输出就会更接近与y0.
因此,对损失函数求关于w和b的偏导数,只要使得:
即可。其中_w表示损失函数对于w的偏导数。
实例
假若有一个函数,。
def true_function(x):
return -26*x+40
设计一个模型来模拟这个函数,假设该模型设定为
该模型最多可以接近完美地拟合一个一元二次方程。
模型定义如下:
class Model:
def __init__(self):
self.w=1
self.b=1
def cal(self,x):
return self.w*x+self.b
model=Model()
选择均方差作为损失函数,即
其中是准确值,是模型拟合值。
根据链式求导法则,设y1_w是模型对于w的偏导值,则损失函数对于w的偏导也可以如下表示:
def loss(y0,y1):
v=(y1-y0)**2
return v
def loss_w(y0,y1,y1_w1):
v=2*(y1-y0)*y1_w1
return v
def loss_b(y0,y1,y1_b):
v=2*(y1-y0)*y1_b
return v
假设输入为x,则模型根据输入更新一次参数的过程可以如下表示:
from matplotlib import pyplot as plt
ls=[]
dws=[]
dbs=[]
def update(x,lr=0.0001,epoch=1000):
for i in range(epoch):
y1=model.cal(x)
y0=true_function(x)
l=loss(y0,y1)
ls.append(l)
dw=loss_w(y0,y1,x)
db=loss_b(y0,y1,1)
dws.append(dw)
dbs.append(db)
model.w-=dw*lr
model.b-=db*lr
print("loss:{} w1:{:.2f} b:{:.2f}".format(l,model.w,model.b))
准备一系列的数据,执行1000次参数更新,画出loss随着更新次数的变多而变化的图。从图中可以看出,loss的确是随着参数更新越来越小,然而最后得到的结果却与正确的函数相差甚远。
update(5)
plt.subplot(221)
plt.plot(ls)
plt.subplot(222)
plt.plot(dws)
plt.subplot(223)
plt.plot(dbs)
plt.show()
loss:0.27581895497589537 w1:-17.36 b:-2.67
问题:
- 造成这种结果的原因是数据太少,仅使用一个数据,很容易在某种机缘巧合下得到其他恰好与正确函数所得结果一样的另一个函数,因此需要增加用于训练模型的数据,同时update函数也要根据多个输入数据而修改。
- 同时,在实验中注意到,一旦学习速率lr设置得过大,很容易导致参数膨胀,即得到一个非常大的参数,使得后续计算的梯度也非常大,最终导致数据溢出而无法训练。
解决办法:
- 在训练过程中多次修改学习速率
- 修改参数更新的逻辑,使其不会一次变化过大
第一种方法称为优化器,后面的篇章中介绍。
这里简单起见,使用第二种方法,为参数更新添加一个限制,即每次更新的值不得大于bound_value。
修改后的相关函数如下:
def update2(xs,ys,lr=1e-7,epoch=1000):
bound_value=1e7
ws=[]
bs=[]
ls=[]
for i in range(epoch):
l=d_w=d_b=0
for x,y0 in zip(xs,ys):
y1=model.cal(x)
l+=loss(y0,y1)
d_w+=loss_w(y0,y1,x)
d_b+=loss_b(y0,y1,5)
d_w=min(bound_value,max(-bound_value,d_w))*lr
d_b=min(bound_value,max(-bound_value,d_b))*lr
ls.append(l)
ws.append(d_w)
bs.append(d_b)
model.w-=d_w
model.b-=d_b
print("loss:{} w:{:.2f} b:{:.2f}".format(l,model.w,model.b))
plt.subplot(121)
plt.plot(ls)
plt.subplot(122)
plt.scatter(xs,ys,color="b",label="true")
plt.plot(xs,[model.cal(x) for x in xs],color="r",label="ours")
plt.legend()
plt.show()
update=update2
datas_x=[x for x in range(-10,30)]
datas_y=[true_function(x) for x in datas_x]
update(datas_x,datas_y,epoch=10000,lr=1e-7)
loss:28946.69946353684 w:-24.50 b:5.16
从得到的函数与正确函数的图像来看,拟合效果已经比较好,然而若仅仅从各参数的数值来看,拟合效果仍然不是我们想要的结果。
这里有两种解决办法:
- 从数据出发,添加更多的训练数据。
- 从训练次数出发,由于两条曲线没有完全重合,因此可以增加更多的训练次数。
loss:3309.083946276638 w:-26.00 b:27.09
另一个例子
下面使用y=36*x+1000这个函数来进行实验,结果如下。从实验结果来看,拟合过程中b的变化非常缓慢。
从参数更新的过程可以看出,计算b的偏导数的值较w更小,因此设置相同的学习速率不太合理。
loss:4050028.206281854 w:35.32 b:548.32