本文字数:5276字
阅读时间:10分钟
最近学习吴恩达《Machine Learning》课程以及《深度学习入门:基于Python的理论与实现》书,一些东西总结了下。现就后者学习进行笔记总结。本文是本书的学习笔记(四)神经网络的学习的下半部分。
目录
▪梯度
▪学习算法的实现
梯度
像
这样的由全部变量的偏导数汇总而成的向量称为梯度(gradient)。
代码语言:javascript复制# 梯度的代码实现
def numerical_gradient(f, x):
h = 1e-4 #0.001
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
上面的函数的实现看上去有些复杂,但它执行的处理和求单变量的数值微分基本没有区别。
代码语言:javascript复制# 求点(3,4) (0,2) (3,0)处的梯度
numerical_gradient(function_2, np.array([3.0, 4.0]))
代码语言:javascript复制array([6., 8.])
numerical_gradient(function_2, np.array([0.0, 2.0]))
代码语言:javascript复制array([0., 4.])
numerical_gradient(function_2, np.array([3.0, 0.0]))
代码语言:javascript复制array([6., 0.])
import numpy as np
import matplotlib.pylab as plt
from mpl_toolkits.mplot3d import Axes3D
def _numerical_gradient_no_batch(f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x)
for idx in range(x.size):
tmp_val = x[idx]
x[idx] = float(tmp_val) h
fxh1 = f(x) # f(x h)
x[idx] = tmp_val - h
fxh2 = f(x) # f(x-h)
grad[idx] = (fxh1 - fxh2) / (2*h)
x[idx] = tmp_val # 还原值
return grad
def numerical_gradient(f, X):
if X.ndim == 1:
return _numerical_gradient_no_batch(f, X)
else:
grad = np.zeros_like(X)
for idx, x in enumerate(X):
grad[idx] = _numerical_gradient_no_batch(f, x)
return grad
def function_2(x):
if x.ndim == 1:
return np.sum(x**2)
else:
return np.sum(x**2, axis=1)
def tangent_line(f, x):
d = numerical_gradient(f, x)
print(d)
y = f(x) - d*x
return lambda t: d*t y
if __name__ == '__main__':
x0 = np.arange(-2, 2.5, 0.25)
x1 = np.arange(-2, 2.5, 0.25)
X, Y = np.meshgrid(x0, x1)
X = X.flatten()
Y = Y.flatten()
grad = numerical_gradient(function_2, np.array([X, Y]) )
plt.figure()
plt.quiver(X, Y, -grad[0], -grad[1], angles="xy",color="#666666")#,headwidth=10,scale=40,color="#444444")
plt.xlim([-2, 2])
plt.ylim([-2, 2])
plt.xlabel('x0')
plt.ylabel('x1')
plt.grid()
plt.legend()
plt.draw()
plt.show()
我们发现离最低处越远,箭头越大。梯度会指向各点处的函数值降低的方向,更严格的讲,梯度指示的方向是各点处的函数值减小最多的方向。
梯度法
神经网络需在学习时找到最优参数(权重和偏置),这里所说的最优参数是指损失函数取最小值时的参数。通过巧妙地使用梯度来寻找函数最小值(或者尽可能小的值)的方法就是梯度法。
梯度表示的是各点处的函数值减小最多的方向。实际上,在复杂的函数中,梯度指示的方向基本上都不是函数值的最小处。
函数的极小值、最小值以及被称为鞍点(saddle point)的地方,梯度为0。极小值是局部最小值,也就是限定在某个范围内的最小值。鞍点是从某个方向上看是极大值,从另一个方向上看则是极小值的点。 当函数很复杂且呈扁平状时,学习可能会进入一个(几乎)平坦的地区,陷入被称为“学习高原”的无法前进的停滞期。
在寻找函数的最小值(或者尽可能小的值)的位置的任务中,要以梯度的信息为线索,决定前进的方向。
在梯度法中,函数的取值从当前位置沿着梯度方向前进一定距离,然后在新的地方重新求梯度,再沿着新梯度方向前进,如此反复,不断地沿梯度方向前进。像这样,通过不断地沿梯度方向前进,逐渐减小函数值的过程就是梯度法(gradient method)。
严格的讲,寻找最小值的梯度法称为梯度下降法(gradient descent method),寻找最大值的梯度法称为梯度上升法(gradient ascent method)。
用数学式表示梯度法:
上式的
表示更新量,在神经网络的学习中,称为学习率(learning rate)。学习率决定在一次学习中,应该学习多少,以及在多大程度上更新参数。上式表示更新一次的式子,这个步骤会反复执行。
像学习率这样的参数成为超参数。学习率这样的超参数是人工设定的。一般来说,超参数需要尝试多个值,以便找到一种可以使学习顺利进行的设定。
而学习率需要事先确定为某个值,比如0.01或者0.001.一般而言,这个值过大或过小,都无法抵达一个“好的位置”。在神经网络的学习中,一般会一边改变学习率的值,一遍确认学习是否正确进行了。下面用python实现梯度下降法:
代码语言:javascript复制def gradient_descent(f, init_x, lr=0.01, step_num=100):
"""
参数f是要进行最优化的函数,init_x是初始值,lr是学习率learning rate,step_num是梯度法的重复次数。
numerical_gradient(f,x)会求函数的梯度,用该梯度乘以学习率得到的值进行更新操作,由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)
代码语言:javascript复制# 用梯度法求f(x0 x1)=x0^2 x1^2的最小值
def function_2(x):
return x[0]**2 x[1]**2
init_x = np.array([-3.0, 4.0])
x, x_history = gradient_descent(function_2, init_x=init_x, lr=0.1, step_num=100)
代码语言:javascript复制# 用图像表示上面的函数梯度下降法的步骤
plt.plot( [-5, 5], [0,0], '--b')
plt.plot( [0,0], [-5, 5], '--b')
plt.plot(x_history[:,0], x_history[:,1], 'o')
plt.xlim(-3.5, 3.5)
plt.ylim(-4.5, 4.5)
plt.xlabel("X0")
plt.ylabel("X1")
plt.show()
学习率过大或者过小都无法得到好的结果。
代码语言:javascript复制# 学习率过大的例子:lr=10.0
init_x = np.array([-3.0, 4.0])
x, x_history = gradient_descent(function_2, init_x=init_x, lr=10.0, step_num=100)
x
代码语言:javascript复制array([-2.58983747e 13, -1.29524862e 12])
# 学习率过小的例子:lr=1e-10
init_x = np.array([-3.0, 4.0])
x, x_history = gradient_descent(function_2, init_x=init_x, lr=1e-10, step_num=100)
x
array([-2.99999994, 3.99999992])
上面实验可以看出,学习率过大的话,会发散成一个很大的值;反过来,学习率过小的话,基本上没怎么更新就结束了。也就是说,设定合适的学习率是一个很重要的问题。
神经网络的梯度
代码语言:javascript复制# 一个简单的神经网络
# coding: utf-8
import sys, os
sys.path.append('../input/deeplearningfromscratch/deeplearningfromscratch') # 为了导入父目录中的文件而进行的设定
import numpy as np
from common.functions import softmax, cross_entropy_error
from common.gradient import numerical_gradient
class simpleNet:
def __init__(self):
self.W = np.random.randn(2,3)
def predict(self, x):
return np.dot(x, self.W)
def loss(self, x, t):
z = self.predict(x)
y = softmax(z)
loss = cross_entropy_error(y, t)
return loss
代码语言:javascript复制net = simpleNet()
print(net.W)
x = np.array([0.6, 0.9])
p = net.predict(x)
print(p)
print(np.argmax(p))
t = np.array([0, 0, 1]) # 正确解标签
f = lambda w: net.loss(x, t)
dW = numerical_gradient(f, net.W)
print(dW)
[[ 0.61060921 0.39278321 -0.19230499] [-0.57160886 0.59945886 -0.26529107]] [-0.14808245 0.7751829 -0.35414496] 1 [[ 0.13852712 0.34874166 -0.48726878] [ 0.20779067 0.5231125 -0.73090317]]
学习算法的实现
神经网络的学习步骤:
前提:神经网络存在合适的权重和偏置,调整权重和偏置以便拟合训练数据的过程称为“学习”。
- mini-batch:从训练数据中随机选出一部分数据,这部分数据称为mini-batch。我们的目标是减小mini-batch的损失函数的值。
- 计算梯度:为了减小mini-batch的损失函数的值,需要求出各个权重参数的梯度。梯度表示损失函数的值减小最多的方向。
- 更新参数:将权重参数沿梯度方向进行微小更新。
- 重复:重复第1、2、3步。
这里使用的数据是随机选择的mini batch数据,所以又被称为随机梯度下降法(stochastic gradient descent)。这里的随机指的是随机选择的意思。
随机梯度下降法是“对随机选择的数据进行的梯度下降法”。深度学习的很多框架中,随机梯度下降法一般由一个名为SGD的函数来实现。SGD来源于随机梯度下降法的英文名称的首字母。
2层神经网络的类
代码语言:javascript复制from common.functions import *
from common.gradient import numerical_gradient
class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
# 初始化权重
self.params = {}
self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
self.params['b1'] = np.zeros(hidden_size)
self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
self.params['b2'] = np.zeros(output_size)
def predict(self, x):
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
a1 = np.dot(x, W1) b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) b2
y = softmax(a2)
return y
# x:输入数据, t:监督数据
def loss(self, x, t):
y = self.predict(x)
return cross_entropy_error(y, t)
def accuracy(self, x, t):
y = self.predict(x)
y = np.argmax(y, axis=1)
t = np.argmax(t, axis=1)
accuracy = np.sum(y == t) / float(x.shape[0])
return accuracy
# x:输入数据, t:监督数据
def numerical_gradient(self, x, t):
loss_W = lambda W: self.loss(x, t)
grads = {}
grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
return grads
def gradient(self, x, t):
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
grads = {}
batch_num = x.shape[0]
# forward
a1 = np.dot(x, W1) b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) b2
y = softmax(a2)
# backward
dy = (y - t) / batch_num
grads['W2'] = np.dot(z1.T, dy)
grads['b2'] = np.sum(dy, axis=0)
da1 = np.dot(dy, W2.T)
dz1 = sigmoid_grad(a1) * da1
grads['W1'] = np.dot(x.T, dz1)
grads['b1'] = np.sum(dz1, axis=0)
return grads
代码语言:javascript复制# 二层神经网络例子
net = TwoLayerNet(input_size = 784, hidden_size = 100, output_size = 10)
print(net.params['W1'].shape)
print(net.params['b1'].shape)
print(net.params['W2'].shape)
print(net.params['b2'].shape)
代码语言:javascript复制(784, 100) (100,) (100, 10) (10,)
#推理处理的实现如下
x = np.random.rand(100, 784) # 伪输入数据100笔
y = net.predict(x)
t = np.random.rand(100, 10) # 伪正确解标签10笔
# grads = net.numerical_gradient(x, t) # 计算梯度,使用传统的基于数值微分计算参数的梯度
grads = net.gradient(x, t) # 计算梯度,使用误差反向传播算法
print(grads['W1'].shape)
print(grads['b1'].shape)
print(grads['W2'].shape)
print(grads['b2'].shape)
(784, 100) (100,) (100, 10) (10,)
解析TwoLayerNet
的实现:
__init__(self, input_size, hidden_size, output_size)
:类的初始化方法,参数依次表示输入层的神经元数、隐藏层的神经元数、输出层的神经元数。 因为进行手写数字识别时,输入图像的大小是784(28x28),输出为10个类别,所以指定参数input_size = 784, output_size = 10
,将隐藏层的个数, hidden_size
设置为一个合适的值即可(例如这里是100)。predict(self, x)、accuracy(self, x, t)
同上一章神经网络的推理处理基本一致。loss(self, x, t)
是计算损失函数值的方法。这个方法会基于predict()
的结果和正确解标签,计算交叉熵误差。numerical_gradient(self, x, t)
是计算各个参数的梯度,而gradient(self, x, t)
是使用误差反向传播法高效计算梯度的方法。numerical_gradient(self, x, t)
是基于数值微分计算参数的梯度。而gradient(self, x, t)
是使用误差反向传播法高速计算梯度,其求到的梯度和数值微分的结果基本一致,且速度比前者快。
如何设置权重参数的初始值是关系到神经网络是否成功学习的重要问题。权重使用符合高斯分布的随机数进行初始化,偏置使用0进行初始化。
mini-batch的实现
所谓mini-batch的学习,就是从训练数据中随机选择一部分数据,再以这些数据为对象(mini-batch),使用梯度法更新参数的过程。
代码语言:javascript复制import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
# from two_layer_net import TwoLayerNet
# 读入数据
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
# 超参数
iters_num = 10000 # 适当设定循环的次数
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1
train_loss_list = []
train_acc_list = []
test_acc_list = []
# 平均每个epoch的重复次数
iter_per_epoch = max(train_size / batch_size, 1)
for i in range(iters_num):
# 获取mini-batch
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
# 计算梯度
#grad = network.numerical_gradient(x_batch, t_batch)
grad = network.gradient(x_batch, t_batch)
# 更新参数
for key in ('W1', 'b1', 'W2', 'b2'):
network.params[key] -= learning_rate * grad[key]
# 记录学习过程
loss = network.loss(x_batch, t_batch)
train_loss_list.append(loss)
# 计算每个epoch的识别精度
if i % iter_per_epoch == 0:
train_acc = network.accuracy(x_train, t_train)
test_acc = network.accuracy(x_test, t_test)
train_acc_list.append(train_acc)
test_acc_list.append(test_acc)
print("train acc, test acc | " str(train_acc) ", " str(test_acc))
# 绘制图形
markers = {'train': 'o', 'test': 's'}
x = np.arange(len(train_acc_list))
plt.plot(x, train_acc_list, label='train acc')
plt.plot(x, test_acc_list, label='test acc', linestyle='--')
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()
train acc, test acc | 0.10441666666666667, 0.1028 train acc, test acc | 0.7994333333333333, 0.8037 train acc, test acc | 0.8809833333333333, 0.8833 train acc, test acc | 0.9003666666666666, 0.9024 train acc, test acc | 0.9098833333333334, 0.9125 train acc, test acc | 0.9164, 0.9178 train acc, test acc | 0.9214, 0.9244 train acc, test acc | 0.92525, 0.9277 train acc, test acc | 0.92875, 0.9321 train acc, test acc | 0.9321166666666667, 0.9337 train acc, test acc | 0.9348333333333333, 0.9358 train acc, test acc | 0.93755, 0.9384 train acc, test acc | 0.9400833333333334, 0.9407 train acc, test acc | 0.9424166666666667, 0.9422 train acc, test acc | 0.9445666666666667, 0.9431 train acc, test acc | 0.9466666666666667, 0.9459 train acc, test acc | 0.9480833333333333, 0.9479
随着学习的进行,损失函数的值在不断减小。这是学习正常进行的信号,表示神经网络的权重参数在逐渐拟合数据。通过反复地向它浇灌(输入)数据,神经网络整在逐渐向最优参数靠近。
不过损失函数的值,严格的讲是“对训练数据的某个mini-batch的损失函数”的值。训练数据的损失函数值减小的结果是不能说明该神经网络在其他数据集上也一定能有同等程度的表现。
神经网络的学习中,必须确认是否能够知确实别数据意外的其他数据,即虽然训练数据中的内容能够被正确识别,但是不在训练数据的内容却无法被识别,这种现象为过拟合。
epoch是一个单位,一个epoch表示学习中所有训练数据均被使用过一次时的更新次数。