上一个章节主要讲的是神经网络的前向传播,即在一直参数的条件下,计算出输出值,并且根据输出值的大小进行分类(对于分类问题)。本章主要讲通过梯度下降法,进行参数的更新
神经网络中的学习指的是:模型能够从训练数据中自动获取最优权重参数的过程。
介绍几个其中的概念:
损失函数: 神经网络以某一个指标为线索,寻找最优的权重参数,这个指标叫做损失函数,损失函数越小的参数,即是最优的参数。
分类: 损失函数一般由两种,一种是均方误差,另一种是交叉熵误差。
损失函数的引入原因: 如何判断一个神经网络模型的优劣性?有人可能说用识别精度,但是如果以识别精度为指标,则参数的导数在绝大部分地方都会变为0(仅仅微调参数,不能够改变精度值,而且精度值不是连续变化的)。在寻找最优权重参数值,要选用使得损失函数最小的权重参数。损失函数是连续可导的,通过使用梯度下降法,能够不断地更新权重参数,使得损失函数越小,神经网络的模型也会更加优化。
损失函数一般分为:均方误差和交叉熵误差,用来评估预测的结果与实际结果的差异性。
其中y(k)表示神经网络的输出;t(k)表示监督数据(实际标签数据),k表示数据的维度。
import matplotlib.pyplot as plt
import numpy as np
'''
两种类型的损失函数:均方误差和交叉熵误差
'''
#损失函数:均方误差
def mean_square_error(y,t):
return 0.5*np.sum((y-t)**2)
#损失函数:交叉熵误差(为了是计算能够继续,加了一个delta)
def cross_entropy_error(y,t):
delta=1e-7
return -np.sum(t*np.log(y+delta))
y=[0.1,0.05,0.6,0.0,0.05,0.1,0.0,0.1,0.0,0.0];
#softmax函数的输出,可以表示为概率
t=[0,0,1,0,0,0,0,0,0,0];
#该正确标签的表示法,称为one-hot表示,正确标签置1,其余均为0
y1=mean_square_error(np.array(y),np.array(t))
y2=cross_entropy_error(np.array(y),np.array(t))
print('均方误差=',y1,'y交叉熵误差=',y2)
#输出值越小,越准确。
输出:
均方误差= 0.09750000000000003
交叉熵误差= 0.510825457099338
根据上一节所说,可以对损失函数使用梯度下降法,来进行权重的更新,使得损失函数最小,进而得到最优的权重参数。梯度下降法就需要求出目标函数的梯度(梯度又是由各个未知数的导数所构成的)
求导的方式有两种,一种是数值微分求导,一种是解析性求导。梯度下降法主要用的是数值微分求导,因为目标函数常常很复杂。
数值微分求导: 利用微小的差分求导数的过程称为数值微分。有一定的误差。
解析性求导: 基于数学式的推导求导数的过程,称为解析性求导,没有误差。
#前向差分(不太好)
def numerical_diff0(f,x):
h=1e-4
return (f(x+h)-f(x))/h
#数值微分求导(中心差分) 比较好
def numerical_diff(f,x):
h=1e-4
return (f(x+h)-f(x-h))/(2*h)
#切线的曲线
def function_qiexian(f,x,x0):
k=numerical_diff(f,x0)
return k*(x-x0)+f(x0)
#函数的定义
def function_1(x):
y=0.01*x**2+0.1*x
return y
#数值微分求导(中心差分) 比较好
def numerical_diff(f,x):
h=1e-4
return (f(x+h)-f(x-h))/(2*h)
#库的引用
import numpy as np
#调用函数numerical_diff进行数值微分求导
#在x=5和x=10的导数
d1=numerical_diff(function_1,5)
print('x=5的导数:',d1)
d2=numerical_diff(function_1,10)
print('x=10的导数:',d2)
输出:
x=5的导数: 0.20000099999917254
x=10的导数: 0.3000009999976072
#函数的定义
def function_1(x):
y=0.01*x**2+0.1*x
return y
#数值微分求导(中心差分) 比较好
def numerical_diff(f,x):
h=1e-4
return (f(x+h)-f(x-h))/(2*h)
#切线的曲线
def function_qiexian(f,x,x0):
k=numerical_diff(f,x0)
return k*(x-x0)+f(x0)
#库的引用
import numpy as np
import matplotlib.pylab as plt
#原函数和切线函数的定义
x=np.arange(0,20,0.1)
y=function_1(x)
y1=function_qiexian(function_1,x,5)
#坐标轴的标签
plt.xlabel('x')
plt.ylabel('f(x)')
#绘图与显示
plt.plot(x,y)
plt.plot(x,y1)
plt.show()
输出:
偏导数: 导数一般是对只含有一个变量的函数进行求导;对于含有多个变量的函数,对其中一个变量进行求导,即为偏导数。
梯度: 梯度相当于是偏导数的集合,即对函数中每一个变量进行求偏导,由全部变量的偏导数汇总而成的向量称为梯度。
#求梯度函数的定义
def numerical_gradient(f,x):
h=1e-4 #0.0001
grad=np.zeros_like(x) #生成和x形状相同的数组
for idx in range(x.size):
tmp_val=x[idx] #一个一个进行求导
#f(x+h)的计算
x[idx]=tmp_val+h
fxh1=f(x)
#f(x+h)的计算
x[idx]=tmp_val-h
fxh2=f(x)
grad[idx]=(fxh1-fxh2)/(2*h)
x[idx]=tmp_val #还原值
return grad
#目标函数的两种写法
def function_2(x):
return x[1]**2+x[2]**2
def function_3(x):
if x.ndim == 1:
return np.sum(x**2)
else:
return np.sum(x**2, axis=1) #列方向求和
#主函数,引用库,求梯度函数逇调用
import numpy as np
d3=numerical_gradient(function_3,np.array([3.0 ,4.0]))
print(d3)
输出:
[6. 8.]
其中 η \eta η表示学习率,也可以叫做是步长,以多大的位移向下降速度最快的方向移动。(不能过大,也不能过小,认为选取,是一个超参数)
下面这串代码有点长,主要有一下四部分:
1 .库的申明部分
2 .子函数的定义,其中包括求梯度函数定义,梯度下降法的函数定义和目标函数的定义。
3 .主函数部分,其中包括初始值的确定;调用子函数,获取历史梯度信息;绘制历史梯度信息的迭代变化和等高线的绘制。
'''
库的申明
'''
import numpy as np
#import math
import matplotlib.pylab as plt
'''
子函数的定义
'''
#求梯度
def numerical_gradient(f,x):
h=1e-4 #0.0001
grad=np.zeros_like(x) #生成和x形状相同的数组
for idx in range(x.size):
tmp_val=x[idx] #一个一个进行求导
#f(x+h)的计算
x[idx]=tmp_val+h
fxh1=f(x)
#f(x+h)的计算
x[idx]=tmp_val-h
fxh2=f(x)
grad[idx]=(fxh1-fxh2)/(2*h)
x[idx]=tmp_val #还原值
return grad
#梯度下降法
def gradient_decent(f,init_x,lr,step_num=1000):
x=init_x
for i in range(step_num):
grad=numerical_gradient(f,x)
x -=lr*grad
return x
#梯度下降法(带历史记录)
def gradient_decent1(f,init_x,lr,step_num):
x=init_x
x_history=[]
for i in range(step_num):
x_history.append( x.copy() ) #复制保存每一次迭代的梯度值;最后一次的数据存入不了
grad=numerical_gradient(f,x)
x -=lr*grad
return x, np.array(x_history)
#目标函数
def function_2(x):
return x[0]**2+x[1]**2
'''
主函数
'''
#初始值的定义
step_num=100 #迭代次数
lr=0.2 #学习率
init_x=np.array([-3.0,4.0]) #迭代初始点
#调用函数,得到历史梯度值
#x,x_history =gradient_decent(function_2,init_x,lr=0.4,step_num=100)
x,x_history= gradient_decent1(function_2, init_x, lr, step_num)
#print(x)
#print(x_history)
print('最后的迭代点',x)
#开始绘制图像
#先画两条虚线
plt.plot([-5,5],[0,0],'--b')
plt.plot([0,0],[-5,5],'--b')
x=np.arange(-5,5,0.1)
#绘制历史梯度信息点
plt.plot(x_history[:,0], x_history[:,1], 'ro')
#绘制等高线
x1 = y1 = np.arange(-4, 4, 0.1)
x1, y1 = np.meshgrid(x1,y1) #将原始数据变为网格数据形式
for i in range(6):
plt.contour(x1, y1, x1**2 + y1**2, [3*i],) #x**2 + y**2 = 9 的圆形
#坐标轴的尺度
plt.xlim(-3.5, 3.5)
plt.ylim(-4.5, 4.5)
#坐标轴的标签
plt.xlabel("X0")
plt.ylabel("X1")
#plt.axis('scaled') #使坐标轴是标准的(即x轴与y轴的比例尺一致)
#显示图像
plt.show()
结果:
最后的迭代点 [-2.14405215e-21 6.35274710e-21]
学习率:学习率是一个超参数,决定着迭代点移动方向的步长,过大和过小都不合适。
以上面的为题为例,上面的学习率设置的为0.2,迭代次数为100,较为合适
当固定迭代次数,设置偏大或者偏小的学习率,迭代的效果都不太一样。
测试结果如下:
最后的迭代点 [-6.11110793e-10 8.14814391e-10]
最后的迭代点 [-0.39785867 0.53047822]
参考书籍:
1.《深度学习–基于python的理论与实现》