2023.1.20
在神经网络的学习这一章,学习过了利用 梯度下降法 对参数进行更新,目的是找到是损失函数的值尽量小的参数;像解决这样的问题称为 最优化 。
由于参数空间十分复杂、参数规模十分庞大,导致“最优化”的过程变得困难。
回忆一下随机梯度下降法(stochastic gradient descent),简称SGD
、
将要更新的权重设置为W,把损失函数关于梯度几位 。η 代表学习率;表示右边的值更新左边的值。
Python代码实现SGD:
class SGD: def __init__(self, lr=0.01): self.lr = lr def update(self, params, grads): # 为权重于偏置 w1,w2,b1,b2 这样的参数 for key in params.key(): params[key] -= self.lr * grads[key]
像这样单独实现进行最优化的类,功能的模块化变得简单:
import numpy as np from collections import OrderedDict from dataset.mnist import load_mnist import sys, os sys.path.append(os.pardir) # 数值微分 def numerical_gradient(f, x): h = 1e-4 # 0.0001 grad = np.zeros_like(x) it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite']) # np.nditer() 迭代器处理多维数组 while not it.finished: idx = it.multi_index 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 # 还原值 it.iternext() return grad # 损失函数 def cross_entropy_error(y, t): delta = 1e-7 return -1 * np.sum(t * np.log(y + delta)) # 激活函数 def softmax(x): if x.ndim == 2: x = x.T x = x - np.max(x, axis=0) y = np.exp(x) / np.sum(np.exp(x), axis=0) return y.T x = x - np.max(x) return np.exp(x) / np.sum(np.exp(x)) def sigmoid(x1): return 1 / (1 + np.exp(-x1)) # 加法层、乘法层、激活函数层、Affine层、Softmax层 class Addyer: # 加法节点 def __init__(self): pass def forward(self, x, y): out = x + y return out def backward(self, dout): dx = dout * 1 dy = dout * 1 return dx, dy class Mullyer: # 乘法节点 def __init__(self): # __init__() 中会初始化实例变量 self.x = None self.y = None def forward(self, x, y): self.x = y self.y = x out = x * y return out def backward(self, dout): dx = dout * self.x dy = dout * self.y return dx, dy class ReLU: def __init__(self): self.mask = None def forward(self, x): self.mask = (x <= 0) out = x.copy() out[self.mask] = 0 return out def backward(self, dout): dout[self.mask] = 0 dx = dout return dx class SoftmaxWithLoss: def __init__(self): self.loss = None self.y = None self.t = None def forward(self, x, t): self.t = t self.y = softmax(x) self.loss = cross_entropy_error(self.y, self.t) return self.loss def backward(self, dout=1): batch_size = self.t.shape[0] dx = (self.y - self.t) / batch_size return dx class Affine: def __init__(self, w, b): self.w = w self.b = b self.x = None self.dw = None self.db = None def forward(self, x): self.x = x out = np.dot(x, self.w) + self.b return out def backward(self, dout): dx = np.dot(dout, self.w.T) self.dw = np.dot(self.x.T, dout) self.db = np.sum(dout, axis=0) return dx class TwoLayerNet: def __init__(self, input, hidden, output, weight__init__std=0.01): # 权重的初始化 假设一个权重 self.params = {} self.params['w1'] = weight__init__std * np.random.randn(input, hidden) self.params['b1'] = np.zeros(hidden) self.params['w2'] = weight__init__std * np.random.randn(hidden, output) self.params['b2'] = np.zeros(output) # 生成层 self.layers = OrderedDict() self.layers['Affine1'] = Affine(self.params['w1'], self.params['b1']) self.layers['ReLU1'] = ReLU() self.layers['Affine2'] = Affine(self.params['w2'], self.params['b2']) self.lastlayer = SoftmaxWithLoss() def predict(self, x): for layer in self.layers.values(): x = layer.forward(x) return x def loss(self, x, t): # x:测试数据;t:监督数据 y = self.predict(x) return self.lastlayer.forward(y, t) def accuracy(self, x, t): y = self.predict(x) y = np.argmax(y, axis=1) # 正确解标签 if t.ndim != 1: t = np.argmax(t, axis=1) accuracy = np.sum(y == t) / float(x.shape[0]) return accuracy def numerical_grandient(self, x, t): # 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): # forward self.loss(x, t) # backward dout = 1 dout = self.lastlayer.backward(dout) layers = list(self.layers.values()) layers.reverse() # reserved() 是 Python 内置函数之一,其功能是对于给定的序列(包括列表、元组、字符串以及 range(n) 区间),该函数可以返回一个逆序序列的迭代器(用于遍历该逆序序列) for layer in layers: dout = layer.backward(dout) # setting grads = {} grads['w1'] = self.layers['Affine1'].dw grads['b1'] = self.layers['Affine1'].db grads['w2'] = self.layers['Affine2'].dw grads['b2'] = self.layers['Affine2'].db return grads # 随机梯度下降法 class SGD: def __init__(self, lr=0.01): self.lr = lr def update(self, params, grads): # 为权重于偏置 w1,w2,b1,b2 这样的参数 for key in params.keys(): params[key] -= self.lr * grads[key] # 数据导入 (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True) networks = TwoLayerNet(input=784, hidden=50, output=10) optimizer = SGD() iters_num = 10000 train_size = x_train.shape[0] # 60000 batch_size = 100 # 批处理数量 learning_rate = 0.1 for i in range(iters_num): batch_mask = np.random.choice(train_size, batch_size) # mini——batch 处理 x_batch = x_train[batch_mask] t_batch = t_train[batch_mask] grads = networks.gradient(x_batch, t_batch) # 通过误差反向传播法求梯度 params = networks.params print(params, grads) optimizer.update(params, grads)
SGD的方法简单易于实现,但是也有缺点,现在通过一个函数来讲述:
例如:函数
利用Matlab绘制图像:
x = linspace(-10,10);
y = linspace(-10,10);
[X,Y] = meshgrid(x,y);
Z = (1/20)*X.^2+Y.^2;
Fig = mesh(X,Y,Z);
易得该函数的最低点在(0,0,0)处,从图中的颜色变化我们可以看到梯度特征,y轴方向上颜色变化大,意味着坡度大;x轴方向,颜色变化小,意味值梯度小;而SGD有一个重要的性质就是随机,假设从(x,y)=(-10,-10)开始搜索,SGD只会向一个梯度减少的方向前进,具体y轴方向、还是x轴方向都有可能。也就是说SGD低效的根本原因是,更新路径没有朝着最小值的方向前进。
所以现在学习一些更加高效的方法。
Momentum:
momentum是动量的意思,和物理有关:
表示方法: ; ;
这里有一个新的变量 ,对应“速度”, 表示物体在梯度上受力,在这个力的作用下,物体的“速度”增加。
中的 这一项 在物体不受力时,他承担时物体减速的任务,故的值在[0,1] 这样的值:
代码实现:实例变量v会保存物体的速度。初始化时,self.v=None ,表示什么都不存,当update第一次执行时,v会以dict变量的形式保存于参数结构相关的数据
class Momentum: def __init__(self, lr=0.01, momentum=0.9): self.lr = lr self.momentum = momentum self.v = None def update(self, params, grads): if self.v is None: self.v = {} for key, val in params.items(): self.v[key] = np.zeros_like(val) for key in params.keys(): self.v[key] = self.momentum * self.v[key] - self.lr * grads[key] params[key] += self.v[key]
与SGD相比,Momentum很大程度上减少了“随机”,如果SGD的更新路径是“Z”型随机,那么momentum的更新路是“S”型。因为x轴上的变化梯度很小,而y轴的变化梯度很大,可以看作x轴上受到的是恒力,而y轴上受到的是变力。