深度学习入门-与学习相关的技巧
目录
摘要
- 参数更新方法:SGD、Momentum、AdaGrad、Adam 等。
- 权重初始值的赋值方法对进行正确的学习非常重要。
- 作为权重初始值,Xavier 初始值、He 初始值等比较有效。
- 通过使用 Batch Normalization(批归一化),可以加速学习,并且对初始值变得健壮。
- 抑制过拟合的正则化技术有:权值衰减、Dropot 等。
- 逐渐缩小 “好值” 存在的范围是搜索超参数的一个有效方法。
1. 参数的更新
最优化(optimization):
神经网络的学习的目的是找到使损失函数的值尽可能小的参数。这是寻找最优参数的问题,解决这个问题的过程称为最优化(optimization)。
随机梯度下降法(stochastic gradient descent):
使用参数的梯度,沿梯度方向更新参数,并重复这个步骤多次,从而逐渐靠近最优参数,这个过程称为随机梯度下降法(stochastic gradient descent),简称 SGD。
1.1 SGD
SGD 用数学式表示如下式 (5.1)。
:需要更新的权重参数;
:损失函数关于 的梯度;
:学习率(实际上会取 0.01 或 0.001 这些事先决定好的值);
:表示用右边的值更新左边的值。
Python 实现 SGD:
-
class SGD(object):
-
"""随机梯度下降法(Stochastic Gradient Descent)"""
-
def __init__(self, lr=0.01):
-
self.lr = lr
# 学习率
-
-
def update(self, params, grads):
-
for key
in params.keys():
-
params[key] -= self.lr * grads[key]
1.2 SGD 的缺点
如果函数的形状非均向(anisotropic),比如呈延伸状,所有的路径就会非常低效。SGD 低效的根本原因是,梯度的方向并没有指向最小值的方向。
1.3 Momentum(动量)
Momentum 用数学式表示如下式 (5.2)、(5.3)。
:需要更新的权重参数;
:损失函数关于 的梯度;
:学习率(实际上会取 0.01 或 0.001 这些事先决定好的值);
:表示了物体在梯度方向上受力,在这个力的作用下,物体的速度增加这一物理法则;
:在物体不受任何力时,该项承担使物体逐渐减速的任务( 设定为 0.9 之类的值),对应物理上的地面摩擦力或空气阻力;
:表示用右边的值更新左边的值。
Python 实现 Momentum:
-
import numpy
as np
-
-
-
class Momentum(object):
-
"""Momentum SGD"""
-
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]
1.4 AdaGrad
学习率衰减(learning rate decay):
随着学习的进行,使学习率逐渐减小。
AdaGrad 用数学式表示如下式 (5.4)、(5.5)。
:需要更新的权重参数;
:损失函数关于 的梯度;
:学习率(实际上会取 0.01 或 0.001 这些事先决定好的值);
:保存了以前的所有梯度值的平方和;
:表示用右边的值更新左边的值。
使用 RMSProp 方法改善 AdaGrad 无止境学习时更新量变为 0 的情况:
AdaGrad 会记录过去所有梯度的平方和。因此,学习越深入,更新的幅度就越小。实际上,如果无止境地学习,更新量就会变为 0,完全不再更新。
RMSProp 方法并不是将过去所有的梯度一视同仁地相加,而是逐渐地遗忘过去的梯度,在做加法运算时将新梯度的信息更多地反映出来。这种操作从专业上讲,称为 “指数移动平均”,呈指数函数式地减小过去的梯度的尺度。
Python 实现 AdaGrad 和 RMSProp :
-
import numpy
as np
-
-
-
class AdaGrad(object):
-
"""AdaGrad"""
-
def __init__(self, lr=0.01):
-
self.lr = lr
-
self.h =
None
-
-
def update(self, params, grads):
-
if self.h
is
None:
-
self.h = {}
-
for key, val
in params.items():
-
self.h[key] = np.zeros_like(val)
-
-
for key
in params.keys():
-
self.h[key] += grads[key] * grads[key]
-
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) +
1e-7)
-
-
-
class RMSProp(object):
-
"""RMSProp"""
-
def __init__(self, lr=0.01, decay_rate=0.99):
-
self.lr = lr
-
self.decay_rate = decay_rate
-
self.h =
None
-
-
def update(self, params, grads):
-
if self.h
is
None:
-
self.h = {}
-
for key, val
in params.items():
-
self.h[key] = np.zeros_like(val)
-
-
for key
in params.keys():
-
self.h[key] *= self.decay_rate
-
self.h[key] += (
1 - self.decay_rate) * grads[key] * grads[key]
-
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) +
1e-7)
1.5 Adam
Adam:直观理解,就是融合了 Momentum 和 AdaGrad 的方法。可以实现参数空间的高效搜索和进行超参数的 “偏置矫正”。论文地址:http://arxiv.org/abs/1412.6980v8。
Python 实现 Adam:
-
import numpy
as np
-
-
-
class Adam(object):
-
"""Adam (http://arxiv.org/abs/1412.6980v8)"""
-
def __init__(self, lr=0.001, beta1=0.9, beta2=0.999):
-
self.lr = lr
# 学习率
-
self.beta1 = beta1
# 一次momentum系数
-
self.beta2 = beta2
# 二次momentum系数
-
self.iter =
0
-
self.m =
None
-
self.v =
None
-
-
def update(self, params, grads):
-
if self.m
is
None:
-
self.m, self.v = {}, {}
-
for key, val
in params.items():
-
self.m[key] = np.zeros_like(val)
-
self.v[key] = np.zeros_like(val)
-
-
self.iter +=
1
-
lr_t = self.lr * np.sqrt(
1.0 - self.beta2**self.iter) / (
1.0 - self.beta1**self.iter)
-
-
for key
in params.keys():
-
self.m[key] += (
1 - self.beta1) * (grads[key] - self.m[key])
-
self.v[key] += (
1 - self.beta2) * (grads[key]**
2 - self.v[key])
-
-
params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) +
1e-7)
1.6 最优化方法的比较
-
import numpy
as np
-
import matplotlib.pyplot
as plt
-
from collections
import OrderedDict
-
-
-
"""优化器"""
-
-
-
class SGD(object):
-
"""随机梯度下降法(Stochastic Gradient Descent)"""
-
def __init__(self, lr=0.01):
-
self.lr = lr
# 学习率
-
-
def update(self, params, grads):
-
for key
in params.keys():
-
params[key] -= self.lr * grads[key]
-
-
-
class Momentum(object):
-
"""Momentum SGD"""
-
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]
-
-
-
class Nesterov:
-
-
"""Nesterov's Accelerated Gradient (http://arxiv.org/abs/1212.0901)"""
-
-
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.momentum * self.momentum * self.v[key]
-
params[key] -= (
1 + self.momentum) * self.lr * grads[key]
-
-
-
class AdaGrad:
-
-
"""AdaGrad"""
-
def __init__(self, lr=0.01):
-
self.lr = lr
-
self.h =
None
-
-
def update(self, params, grads):
-
if self.h
is
None:
-
self.h = {}
-
for key, val
in params.items():
-
self.h[key] = np.zeros_like(val)
-
-
for key
in params.keys():
-
self.h[key] += grads[key] * grads[key]
-
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) +
1e-7)
-
-
-
class RMSProp(object):
-
"""RMSProp"""
-
def __init__(self, lr=0.01, decay_rate=0.99):
-
self.lr = lr
-
self.decay_rate = decay_rate
-
self.h =
None
-
-
def update(self, params, grads):
-
if self.h
is
None:
-
self.h = {}
-
for key, val
in params.items():
-
self.h[key] = np.zeros_like(val)
-
-
for key
in params.keys():
-
self.h[key] *= self.decay_rate
-
self.h[key] += (
1 - self.decay_rate) * grads[key] * grads[key]
-
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) +
1e-7)
-
-
-
class Adam(object):
-
"""Adam (http://arxiv.org/abs/1412.6980v8)"""
-
def __init__(self, lr=0.001, beta1=0.9, beta2=0.999):
-
self.lr = lr
# 学习率
-
self.beta1 = beta1
# 一次momentum系数
-
self.beta2 = beta2
# 二次momentum系数
-
self.iter =
0
-
self.m =
None
-
self.v =
None
-
-
def update(self, params, grads):
-
if self.m
is
None:
-
self.m, self.v = {}, {}
-
for key, val
in params.items():
-
self.m[key] = np.zeros_like(val)
-
self.v[key] = np.zeros_like(val)
-
-
self.iter +=
1
-
lr_t = self.lr * np.sqrt(
1.0 - self.beta2**self.iter) / (
1.0 - self.beta1**self.iter)
-
-
for key
in params.keys():
-
self.m[key] += (
1 - self.beta1) * (grads[key] - self.m[key])
-
self.v[key] += (
1 - self.beta2) * (grads[key]**
2 - self.v[key])
-
-
params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) +
1e-7)
-
-
-
"""优化器比较"""
-
-
-
def f(x, y):
-
return x**
2 /
20.0 + y**
2
-
-
-
def df(x, y):
-
return x /
10.0,
2.0 * y
-
-
-
init_pos = (
-7.0,
2.0)
-
params = {}
-
params[
"x"], params[
"y"] = init_pos[
0], init_pos[
1]
-
grads = {}
-
grads[
"x"], grads[
"y"] =
0,
0
-
-
optimizers = OrderedDict()
-
optimizers[
"SGD"] = SGD(lr=
0.95)
-
optimizers[
"Momentum"] = Momentum(lr=
0.1)
-
optimizers[
"AdaGrad"] = AdaGrad(lr=
1.5)
-
optimizers[
"Adam"] = Adam(lr=
0.3)
-
-
idx =
1
-
-
for key
in optimizers:
-
optimizer = optimizers[key]
-
x_history = []
-
y_history = []
-
params[
'x'], params[
'y'] = init_pos[
0], init_pos[
1]
-
-
for i
in range(
30):
-
x_history.append(params[
"x"])
-
y_history.append(params[
"y"])
-
-
grads[
"x"], grads[
"y"] = df(params[
"x"], params[
"y"])
-
optimizer.update(params, grads)
-
-
x = np.arange(
-10,
10,
0.01)
-
y = np.arange(
-5,
5,
0.01)
-
-
X, Y = np.meshgrid(x, y)
-
Z = f(X, Y)
-
-
# for simple contour line
-
mask = Z >
7
-
Z[mask] =
0
-
-
# plot
-
plt.subplot(
2,
2, idx)
-
idx +=
1
-
plt.plot(x_history, y_history,
"o-", color=
"red")
-
plt.contour(X, Y, Z)
-
plt.ylim(
-10,
10)
-
plt.xlim(
-10,
10)
-
plt.plot(
0,
0,
"+")
-
# colorbar()
-
# spring()
-
plt.title(key)
-
plt.xlabel(
"x")
-
plt.ylabel(
"y")
-
-
plt.show()
1.7 基于 MNIST 数据集的更新方法的比较
实验:
以一个 5 层神经网络为对象,其中每层有 100 个神经元。激活函数使用 ReLU。
Python 实现:
-
"""基于MNIST数据集的更新方法的比较"""
-
-
import numpy
as np
-
import matplotlib.pyplot
as plt
-
from collections
import OrderedDict
-
-
from dataset.mnist
import load_mnist
-
-
-
def smooth_curve(x):
-
"""用于使损失函数的图形变圆滑
-
-
参考:http://glowingpython.blogspot.jp/2012/02/convolution-with-numpy.html
-
"""
-
window_len =
11
-
s = np.r_[x[window_len
-1:
0:
-1], x, x[
-1:-window_len:
-1]]
-
w = np.kaiser(window_len,
2)
-
y = np.convolve(w/w.sum(), s, mode=
'valid')
-
return y[
5:len(y)
-5]
-
-
-
def sigmoid(x):
-
return
1 / (
1 + np.exp(-x))
-
-
-
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 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
-
-
-
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'])
-
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
-
-
-
class SoftmaxWithLoss(object):
-
-
def __init__(self):
-
self.loss =
None
# 损失
-
self.y =
None
# softmax 的输出
-
self.t =
None
# 监督数据(one-hot vector)
-
-
def forward(self, x, t):
-
"""
-
正向传播
-
:param x: 输入
-
:param t: 监督数据
-
:return:
-
"""
-
self.t = t
-
self.y = softmax(x)
-
self.loss = cross_entropy_error(self.y, self.t)
-
-
return self.loss
-
-
def backward(self, dout=1):
-
"""
-
反向传播
-
:param dout: 上游传来的导数
-
:return:
-
"""
-
batch_size = self.t.shape[
0]
-
if self.t.size == self.y.size:
# 监督数据是one-hot-vector的情况
-
dx = (self.y - self.t) / batch_size
-
else:
-
dx = self.y.copy()
-
dx[np.arange(batch_size), self.t] -=
1
-
dx = dx / batch_size
-
-
return dx
-
-
-
class Sigmoid:
-
def __init__(self):
-
self.out =
None
-
-
def forward(self, x):
-
out = sigmoid(x)
-
self.out = out
-
return out
-
-
def backward(self, dout):
-
dx = dout * (
1.0 - self.out) * self.out
-
-
return dx
-
-
-
class Affine:
-
def __init__(self, W, b):
-
self.W = W
# 权重参数
-
self.b = b
# 偏置参数
-
-
self.x =
None
# 输入
-
self.original_x_shape =
None
# 输入张量的形状
-
self.dW =
None
# 权重参数的导数
-
self.db =
None
# 偏置参数的导数
-
-
def forward(self, x):
-
"""
-
正向传播
-
:param x:
-
:return:
-
"""
-
# 对应张量
-
self.original_x_shape = x.shape
-
x = x.reshape(x.shape[
0],
-1)
-
self.x = x
-
-
out = np.dot(self.x, self.W) + self.b
-
-
return out
-
-
def backward(self, dout):
-
"""
-
反向传播
-
:param dout: 上游传来的导数
-
:return: 输入的导数
-
"""
-
dx = np.dot(dout, self.W.T)
-
self.dW = np.dot(self.x.T, dout)
-
self.db = np.sum(dout, axis=
0)
-
-
dx = dx.reshape(*self.original_x_shape)
# 还原输入数据的形状(对应张量)
-
return dx
-
-
-
class Relu(object):
-
def __init__(self):
-
# 由True/False构成的NumPy数组
-
# 正向传播时的输入x的元素中小于等于0的地方保存为True,其他地方(大于0的元素)保存为False
-
self.mask =
None
-
-
def forward(self, x):
-
"""
-
正向传播
-
:param x: 正向传播时的输入
-
:return:
-
"""
-
# 输入x的元素中小于等于0的地方保存为True,其他地方(大于0的元素)保存为False
-
self.mask = (x <=
0)
-
# 输入x的元素中小于等于0的值变换为0
-
out = x.copy()
-
out[self.mask] =
0
-
-
return out
-
-
def backward(self, dout):
-
"""
-
反向传播
-
:param dout: 上游传来的导数
-
:return:
-
"""
-
# 将从上游传来的dout的mask中的元素为True的地方设为0
-
dout[self.mask] =
0
-
dx = dout
-
-
return dx
-
-
-
class MultiLayerNet:
-
"""全连接的多层神经网络
-
-
Parameters
-
----------
-
input_size : 输入大小(MNIST的情况下为784)
-
hidden_size_list : 隐藏层的神经元数量的列表(e.g. [100, 100, 100])
-
output_size : 输出大小(MNIST的情况下为10)
-
activation : 'relu' or 'sigmoid'
-
weight_init_std : 指定权重的标准差(e.g. 0.01)
-
指定'relu'或'he'的情况下设定“He的初始值”
-
指定'sigmoid'或'xavier'的情况下设定“Xavier的初始值”
-
weight_decay_lambda : Weight Decay(L2范数)的强度
-
"""
-
def __init__(self, input_size, hidden_size_list, output_size,
-
activation='relu', weight_init_std='relu', weight_decay_lambda=0):
-
self.input_size = input_size
-
self.output_size = output_size
-
self.hidden_size_list = hidden_size_list
-
self.hidden_layer_num = len(hidden_size_list)
-
self.weight_decay_lambda = weight_decay_lambda
-
self.params = {}
-
-
# 初始化权重
-
self.__init_weight(weight_init_std)
-
-
# 生成层
-
activation_layer = {
'sigmoid': Sigmoid,
'relu': Relu}
-
self.layers = OrderedDict()
-
for idx
in range(
1, self.hidden_layer_num+
1):
-
self.layers[
'Affine和Softmax层的实现' + str(idx)] = Affine(self.params[
'W' + str(idx)],
-
self.params[
'b' + str(idx)])
-
self.layers[
'Activation_function' + str(idx)] = activation_layer[activation]()
-
-
idx = self.hidden_layer_num +
1
-
self.layers[
'Affine和Softmax层的实现' + str(idx)] = Affine(self.params[
'W' + str(idx)],
-
self.params[
'b' + str(idx)])
-
-
self.last_layer = SoftmaxWithLoss()
-
-
def __init_weight(self, weight_init_std):
-
"""设定权重的初始值
-
-
Parameters
-
----------
-
weight_init_std : 指定权重的标准差(e.g. 0.01)
-
指定'relu'或'he'的情况下设定“He的初始值”
-
指定'sigmoid'或'xavier'的情况下设定“Xavier的初始值”
-
"""
-
all_size_list = [self.input_size] + self.hidden_size_list + [self.output_size]
-
for idx
in range(
1, len(all_size_list)):
-
scale = weight_init_std
-
if str(weight_init_std).lower()
in (
'relu',
'he'):
-
scale = np.sqrt(
2.0 / all_size_list[idx -
1])
# 使用ReLU的情况下推荐的初始值
-
elif str(weight_init_std).lower()
in (
'sigmoid',
'xavier'):
-
scale = np.sqrt(
1.0 / all_size_list[idx -
1])
# 使用sigmoid的情况下推荐的初始值
-
-
self.params[
'W' + str(idx)] = scale * np.random.randn(all_size_list[idx
-1], all_size_list[idx])
-
self.params[
'b' + str(idx)] = np.zeros(all_size_list[idx])
-
-
def predict(self, x):
-
for layer
in self.layers.values():
-
x = layer.forward(x)
-
-
return x
-
-
def loss(self, x, t):
-
"""求损失函数
-
-
Parameters
-
----------
-
x : 输入数据
-
t : 教师标签
-
-
Returns
-
-------
-
损失函数的值
-
"""
-
y = self.predict(x)
-
-
weight_decay =
0
-
for idx
in range(
1, self.hidden_layer_num +
2):
-
W = self.params[
'W' + str(idx)]
-
weight_decay +=
0.5 * self.weight_decay_lambda * np.sum(W **
2)
-
-
return self.last_layer.forward(y, t) + weight_decay
-
-
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_gradient(self, x, t):
-
"""求梯度(数值微分)
-
-
Parameters
-
----------
-
x : 输入数据
-
t : 教师标签
-
-
Returns
-
-------
-
具有各层的梯度的字典变量
-
grads['W1']、grads['W2']、...是各层的权重
-
grads['b1']、grads['b2']、...是各层的偏置
-
"""
-
loss_W =
lambda W: self.loss(x, t)
-
-
grads = {}
-
for idx
in range(
1, self.hidden_layer_num+
2):
-
grads[
'W' + str(idx)] = numerical_gradient(loss_W, self.params[
'W' + str(idx)])
-
grads[
'b' + str(idx)] = numerical_gradient(loss_W, self.params[
'b' + str(idx)])
-
-
return grads
-
-
def gradient(self, x, t):
-
"""求梯度(误差反向传播法)
-
-
Parameters
-
----------
-
x : 输入数据
-
t : 教师标签
-
-
Returns
-
-------
-
具有各层的梯度的字典变量
-
grads['W1']、grads['W2']、...是各层的权重
-
grads['b1']、grads['b2']、...是各层的偏置
-
"""
-
# forward
-
self.loss(x, t)
-
-
# backward
-
dout =
1
-
dout = self.last_layer.backward(dout)
-
-
layers = list(self.layers.values())
-
layers.reverse()
-
for layer
in layers:
-
dout = layer.backward(dout)
-
-
# 设定
-
grads = {}
-
for idx
in range(
1, self.hidden_layer_num+
2):
-
grads[
'W' + str(idx)] = self.layers[
'Affine和Softmax层的实现' + str(idx)].dW + self.weight_decay_lambda * self.layers[
'Affine和Softmax层的实现' + str(idx)].W
-
grads[
'b' + str(idx)] = self.layers[
'Affine和Softmax层的实现' + str(idx)].db
-
-
return grads
-
-
-
class SGD(object):
-
"""随机梯度下降法(Stochastic Gradient Descent)"""
-
-
def __init__(self, lr=0.01):
-
self.lr = lr
# 学习率
-
-
def update(self, params, grads):
-
for key
in params.keys():
-
params[key] -= self.lr * grads[key]
-
-
-
class Momentum(object):
-
"""Momentum SGD"""
-
-
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]
-
-
-
class Nesterov:
-
"""Nesterov's Accelerated Gradient (http://arxiv.org/abs/1212.0901)"""
-
-
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.momentum * self.momentum * self.v[key]
-
params[key] -= (
1 + self.momentum) * self.lr * grads[key]
-
-
-
class AdaGrad:
-
"""AdaGrad"""
-
-
def __init__(self, lr=0.01):
-
self.lr = lr
-
self.h =
None
-
-
def update(self, params, grads):
-
if self.h
is
None:
-
self.h = {}
-
for key, val
in params.items():
-
self.h[key] = np.zeros_like(val)
-
-
for key
in params.keys():
-
self.h[key] += grads[key] * grads[key]
-
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) +
1e-7)
-
-
-
class RMSProp(object):
-
"""RMSProp"""
-
-
def __init__(self, lr=0.01, decay_rate=0.99):
-
self.lr = lr
-
self.decay_rate = decay_rate
-
self.h =
None
-
-
def update(self, params, grads):
-
if self.h
is
None:
-
self.h = {}
-
for key, val
in params.items():
-
self.h[key] = np.zeros_like(val)
-
-
for key
in params.keys():
-
self.h[key] *= self.decay_rate
-
self.h[key] += (
1 - self.decay_rate) * grads[key] * grads[key]
-
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) +
1e-7)
-
-
-
class Adam(object):
-
"""Adam (http://arxiv.org/abs/1412.6980v8)"""
-
-
def __init__(self, lr=0.001, beta1=0.9, beta2=0.999):
-
self.lr = lr
# 学习率
-
self.beta1 = beta1
# 一次momentum系数
-
self.beta2 = beta2
# 二次momentum系数
-
self.iter =
0
-
self.m =
None
-
self.v =
None
-
-
def update(self, params, grads):
-
if self.m
is
None:
-
self.m, self.v = {}, {}
-
for key, val
in params.items():
-
self.m[key] = np.zeros_like(val)
-
self.v[key] = np.zeros_like(val)
-
-
self.iter +=
1
-
lr_t = self.lr * np.sqrt(
1.0 - self.beta2 ** self.iter) / (
1.0 - self.beta1 ** self.iter)
-
-
for key
in params.keys():
-
self.m[key] += (
1 - self.beta1) * (grads[key] - self.m[key])
-
self.v[key] += (
1 - self.beta2) * (grads[key] **
2 - self.v[key])
-
-
params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) +
1e-7)
-
-
-
"""优化器比较"""
-
-
-
# 0: 读入数据
-
(x_train, y_train), (x_test, y_test) = load_mnist(normalize=
True)
-
-
train_size = x_train.shape[
0]
-
batch_size =
128
-
max_iterations =
2000
-
-
# 1: 进行实验的设置
-
optimizers = {}
-
optimizers[
"SGD"] = SGD()
-
optimizers[
"Momentum"] = Momentum()
-
optimizers[
"AdaGrad"] = AdaGrad()
-
optimizers[
"Adam"] = Adam()
-
-
networks = {}
-
train_loss = {}
-
for key
in optimizers.keys():
-
networks[key] = MultiLayerNet(input_size=
784,
-
hidden_size_list=[
100,
100,
100,
100],
-
output_size=
10)
-
train_loss[key] = []
-
-
# 2: 开始训练
-
for i
in range(max_iterations):
-
batch_mask = np.random.choice(train_size, batch_size)
-
x_batch = x_train[batch_mask]
-
y_batch = y_train[batch_mask]
-
-
for key
in optimizers.keys():
-
grads = networks[key].gradient(x_batch, y_batch)
-
optimizers[key].update(networks[key].params, grads)
-
-
loss = networks[key].loss(x_batch, y_batch)
-
train_loss[key].append(loss)
-
-
if i %
100 ==
0:
-
print(
f"============itrration: {i}============")
-
for key
in optimizers.keys():
-
loss = networks[key].loss(x_batch, y_batch)
-
print(
f"{key}: {loss}")
-
-
# 3: 绘制图形
-
markers = {
"SGD":
"o",
"Momentum":
"x",
"AdaGrad":
"s",
"Adam":
"D"}
-
x = np.arange(max_iterations)
-
for key
in optimizers.keys():
-
plt.plot(x, smooth_curve(train_loss[key]), marker=markers[key], markevery=
100, label=key)
-
plt.xlabel(
"iterations")
-
plt.ylabel(
"loss")
-
plt.ylim(
0,
1)
-
plt.legend()
-
plt.show()
2. 权重的初始值
在神经网络的学习中,设定什么样的权重初始值,经常关系到神经网络的学习能否成功。
2.1 可以将权重初始值设为 0 吗
权值衰减(weight decay): 一种以减小权重参数的值为目的进行学习的方法。通过减小权重参数的值来抑制过拟合的发生。
为什么不能将权重初始值设为 0 呢?为什么不能将权重初始值设成一样的值呢?
因为在误差反向传播中,所有的权重值都会进行相同的更新。比如,在 2 层神经网络中,假设第 1 层和第 2 层的权重为0。这样一来,正向传播时,因为输入层的权重为 0,所以第 2 层的神经元全部会被传递相同的值。第 2 层的神经元中全部输入相同的值,这意味着反向传播时第 2 层的权重全部都会进行相同的更新。因此,权重被更新为相同的值,并拥有了对称的值(重复的值)。这使得神经网络拥有许多不同的权重的意义丧失了。
2.2 隐藏层的激活值的分布
实验:
向一个 5 层神经网络(激活函数使用 sigmoid 函数)传入随机生成的输入数据,用直方图绘制各层激活值(激活函数的输出数据)的数据分布。
实验 1:使用标准差为 1 的高斯分布作为权重初始值
各层的激活值呈偏向 0 和 1 的分布。这里使用的 sigmoid 函数是 S 型函数,随着输出不断地靠近 0(或者靠近 1),它的导数的值逐渐接近 0。
梯度消失(gradient vanishing):偏向 0 和 1 的数据分布会造成反向传播中梯度的值不断变小,最后消失。这个问题称为梯度消失(gradient vanishing)。
实验 2:使用标准差为 0.01 的高斯分布作为权重初始值
各层的激活值呈集中在 0.5 附近的分布。因为不像标准差为 1 时的那样偏向 0 和 1,所以不会发生梯度消失的问题。
激活值的分布有所偏向,说明表现力上会有很大问题。为什么这么说呢?因为如果有多个神经元都输出几乎相同的值,那它们就没有存在的意义了。比如,如果 100 个神经元都输出几乎相同的值,那么也可以由 1 个神经元来表达基本相同的事情。因此,激活值在分布上有所偏向会出现 “表现力受限 ” 的问题。
各层的激活值的分布都要求有适当的广度。为什么呢?
因为通过在各层间传递多样性的数据,神经网络可以进行高效的学习。反过来,如果传递的是有所偏向的数据,就会出现梯度消失或者 “表现力受限” 的问题,导致学习可能无法顺利进行。
实验 3:使用 Xavier 初始值作为权重初始值
如果前一层的节点数为 n,则初始值使用标准差为 的分布。
使用 Xavier 初始值后,前一层的节点数越多,要设定为目标节点的初始值的权重尺度就越小。
观察图 5-5 可知,越是后面的层,图像变得越歪斜,但是呈现了比之前更有广度的分布。因为各层间传递的数据有了适当的广度,所以 sigmoid 函数的表现力不受限制,有望进行高效地学习。
实验 4:使用 Xavier 初始值作为权重初始值时使用 tanh 函数代替 sigmoid 函数
tanh 和 sigmoid 函数都是 S型曲线函数。
tanh 函数是关于原点 (0, 0) 对称的 S 型曲线,而 sigmoid 函数是关于 (x, y) = (0, 0.5) 对称的 S 型曲线。
用 tanh 函数(双曲线函数)代替 sigmoid 函数,可以改善 sigmoid 函数作为激活函数时神经网络层的激活值分布呈现出稍微歪斜的形状的问题。
3. Batch Normalization(批归一化)
4. 正则化
5. 超参数的验证
转载:https://blog.csdn.net/weixin_38477351/article/details/116380361