这节不直接讲解随机梯度法(Stochastic Gradient Descent)SGD,而是做一些铺垫,介绍一些很多相关且很重要的基础知识。
MNIST数据集手写数字识别(一)https://blog.csdn.net/weixin_41896770/article/details/119576575MNIST数据集手写数字识别(二)https://blog.csdn.net/weixin_41896770/article/details/119710429
其中参数(权重和偏置)使用的都是人工设定好了的(sample_weight.pkl),如果这些参数都需要人来设定,那工作量很大,甚至不可能(层数很深的深度学习中,参数的数量可以上亿),所以如何让神经网络进行学习,而找出这些参数就很重要了。主要用到损失函数这个指标,找出能使它的值达到最小的权重参数,那为什么不直接使用精确度做指标呢?因为如果使用精确度做指标,会使得绝大多数地方的导数变为0,这样无论权重参数向哪个方向变化,这个时候损失函数的值都不会改变了,这就导致该权重参数无法更新下去了。
损失函数(loss function)可以使用任意函数,一般用均方误差和交叉熵误差,损失函数表示神经网络性能的“恶劣程度”的指标,即当前的神经网络对监督数据(训练数据)在多大程度上不拟合,或说“使性能的优良程度达到最大”。
均方误差的公式:
import numpy as np
def mse(y,t):
'''
均方误差mean squared error
'''
return np.sum((y-t)**2)/2
t=np.array([0,0,1,0,0,0,0,0,0,0])#正确解标签值为2【独热编码】
y1=np.array([0.1,0.05,0.6,0.0,0.05,0.1,0.0,0.1,0.0,0.0])#输出数据,最大值是2
mse(y1,t)#0.097500000000000031
y2=np.array([0.1,0.05,0.1,0.0,0.05,0.1,0.0,0.6,0.0,0.0])#输出数据,最大值是7(错误)
mse(y2,t)#0.59750000000000003
从上面结果可以看出第一个的误差很小,输出值也确实是2
交叉熵误差的公式:
import matplotlib.pyplot as plt
x=np.linspace(1e-7,1)
y=-np.log(x)
plt.rcParams['font.family']=['STFangsong']
plt.title('交叉熵误差')
plt.xlabel('x输出数据的概率')
plt.ylabel('y误差值')
plt.plot(x,y)
plt.show()
输出的图形可以看出,当输出越接近1的时候,交叉熵的误差越接近0!
def cee(y,t):
'''
交叉熵误差cross entropy error
'''
delta=1e-7#微小值是为了防止log0是负无穷大
return -np.sum(t*np.log(y+delta))
cee(y1,t)#0.51082545709933802 和-np.log(0.6)的结果基本一样
cee(y2,t)#2.3025840929945458和-np.log(0.1)的结果基本一样
从结果我们也知道了,第一个的交叉熵误差很小,输出也是正确解,第二个的正确解对应概率只有0.1,它的交叉熵误差也是很大的。
前面所说的都是针对单个数据的损失函数,如果要求所有训练数据的损失函数的总和,以交叉熵误差为例,公式就变为:
其实很简单,就是把单个数据扩展到了N份数据,记得除以N进行正规化,这样就得到了“平均损失函数”。一般来说训练数据都是非常大的,如果使用全部数据为对象来计算损失函数是不现实的,所以需要随机抽取一批数据来进行学习(mini-batch学习)
我们把前面的交叉熵误差改进下,让其能适合二维数组:
def cee2(y,t):
delta=1e-7
if y.ndim==1:
y=y.reshape(1,y.size)#一维变二维
t=t.reshape(1,t.size)
batch_size=y.shape[0]#批量的数量
return -np.sum(t*np.log(y+delta))/batch_size
另外一种情况的处理,就是监督数据不是独热编码,就是标签形式(如“2”,“7”这样的标签)
(x_train,t_train),(x_test,t_test)=load_mnist(normalize=True,flatten=True,one_hot_label=False)
print(t_train.shape,t_test.shape)
(60000,) (10000,)这样的标签数据就不是矩阵,而是一维数组
参数one_hot_label=True是独热编码,那么print(t_train.shape,t_test.shape)
结果就是(60000, 10) (10000, 10)这样的矩阵
合并之后的交叉熵误差的代码如下:
def cross_entropy_error(y, t):
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
# 监督数据是one-hot-vector的情况下,转换为正确解标签的索引
if t.size == y.size:
t = t.argmax(axis=1)
batch_size = y.shape[0]
return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
y1=np.array([0.1,0.05,0.6,0.0,0.05,0.1,0.0,0.1,0.0,0.0])
y2=np.array([0.1,0.05,0.1,0.0,0.05,0.1,0.0,0.6,0.0,0.0])
t1=np.array([0,8,9,2,3,1,6,7,4,5])
print(t1.reshape(1,t1.size).argmax(axis=1))#[2],索引值为2
cross_entropy_error(y1,t1)#0.51082545709933802
cross_entropy_error(y2,t1)#2.3025840929945458
进一步了解导数(梯度),求导数有两种方法,一种是利用微小的差分来求导的过程称为数值微分(numerical differentiation),另外一种就是基于数学公式的求导叫做解析性求导,解析性求导是没有误差的“真的导数”
def numerical_diff(f,x):
'''
中心差分
'''
h=1e-4#0.0001,如果是太小的值也不可以,计算机舍入之后为0
return (f(x+h)-f(x-h))/(2*h)
def f1(x):
return x**2
numerical_diff(f1,5)和numerical_diff(f1,10)#9.999999999976694和19.99999999995339
结果可以看出在5和10的位置的导数和真实导数10,20的误差极小,基本可以认定为相等
下面使用数值微分来看下x²的导数的画图:
import numpy as np
import matplotlib.pylab as plt
def numerical_diff(f,x):
'''
中心差分
'''
h=1e-4#0.0001,如果是太小的值也不可以,计算机舍入之后为0
return (f(x+h)-f(x-h))/(2*h)
def f1(x):
return x**2
def tangent_line(f,x):
'''
画切线
'''
d=numerical_diff(f,x)
y=f(x)-d*x
'''匿名函数
def g(t):
return d*t+y
'''
return lambda t:d*t+y
x=np.arange(0,20,0.1)
y=f1(x)
plt.xlabel("x")
plt.ylabel("f1(x)")
tf1=tangent_line(f1,5)#5位置的导数(此处为函数)
y1=tf1(x)#x数组对应切线函数之后的y数组值
tf2=tangent_line(f1,10)
y2=tf2(x)
#print(y1.shape,y1)
splt=plt.subplot(1,2,1)
plt.rcParams['font.family']=['STFangsong']
plt.plot(x,y,'b--',label='x²')
plt.plot(x,y1,'b--',label='x=5处的导数的切线')
plt.legend()
plt.subplot(1,2,2)
plt.plot(x,y,'r',label='x²')
plt.plot(x,y2,'r',label='x=10处的导数的切线')
plt.legend()
plt.show()