第一章 Python入门
第二章 感知机
感知器是什么
感知机接收多个输入信号,输出一个信号。
感知机的信号只有“流/不流”(1/0)两种取值,0 对应“不传递信号”,1对应“传递信号”
简单逻辑电路
与门
与门仅在 两个输入均为1时输出1,其他时候则输出0。
与非门和或门
与非门就是颠倒了与门的输出
或门是“只要有一个输入信号是1,输 出就为1”的逻辑电路。
感知机的实现
简单实现
与门
def AND(x1, x2):
w1, w2, theta = 0.5, 0.5, 0.7
tmp = x1*w1 + x2*w2
if tmp <= theta:
return 0
else tmp > theta:
return 1
导入权重和偏置
b称为偏置,w1和w2称为权重。
使用权重和偏置的实现
与门
def AND(x1, x2):
x = np.array([x1, x2])
w = np.array([0.5, 0.5])
b = -0.7
tmp = np.sum(W*x) + b
if tmp <= 0:
return 0
else:
return 1
非门
def NAND(x1, x2):
x = np.array([x1, x2])
w = np.array([0.5, -0.5])
b = 0.7
tmp = np.sum(w*X) + b
if tmp <= 0:
return 0
else:
return 1
或门
def OR(x1, x2):
x = np.array([x1, x2])
w = np.array([0.5, 0.5])
b = -0.2
tmp = np.sum(W*X) + b
if tmp <= 0:
return 0
else:
return 1
感知机的局限性
异或门
异或门也被称为逻辑异或电路,仅当x1或x2中的一方为 1时,才会输出1
感知机无法实现异或门
线性和非线性
感知机的局限性就在于它只能表示由一条直线分割的空间
曲线分割而成的空间称为 非线性空间,由直线分割而成的空间称为线性空间
多层感知机
def AND(x1, x2):
w1, w2, theta = 0.5, 0.5, 0.7
tmp = x1*w1 + x2*w2
if tmp <= theta:
return 0
elif tmp > theta:
return 1
def NAND(x1, x2):
x = np.array([x1, x2])
w = np.array([-0.5, -0.5])
b = 0.7
tmp = np.sum(w*x) + b
if tmp <= 0:
return 0
else:
return 1
def OR(x1, x2):
x = np.array([x1, x2])
w = np.array([0.5, 0.5])
b = -0.2
tmp = np.sum(w*x) + b
if tmp <= 0:
return 0
else:
return 1
def XOR(x1, x2):
s1 = NAND(x1, x2)
s2 = OR(x1, x2)
y = AND(s1, s2)
return y
第三章 神经网络
感知机的输出神经元可以分为下面两步表示:
输出单元的两个节点
输出单元里第一步输入信号通过线性运算得到输出值设为a,第二步通过激活函数h()作用生成输出值y=h(a),这里的激活函数就是阶跃函数: ℎ(x)={0(x≤0), 1(x>0)} 。
如果把阶跃函数换成其他的函数,感知机就变成了神经网络。
激活函数与神经网络
激活函数:连接感知机与神经网络的桥梁,将输入信号的综合转换为输出信号。
- 阶跃函数(感知机): ℎ(x)={0(x≤0), 1(x>0)} 。
import numpy as np
from matplotlib import pyplot as pl
def step_function(x): # 定义阶跃函数
return np.where(x <= 0, 0, 1) # where(条件,条件成立的返回值,条件不成立的返回值)
x = np.linspace(-5.0, 5.0, 1000) # 生成一个包含1000个均匀分布的x的值的数组
y = step_function(x) # 计算每个x对应的y
plt.plot(x, y)
plt.xlabel("x")
plt.ylabel("y")
plt.show()
-
sigmoid函数
sigmoid函数是一条s形曲线,单调,连续,到处可微
import numpy as np
import math
from matplotlib import pyplot as plt
def sigmoid_function(z):
fz = [] # 创建空列表fz
for x in z:
fz.append(1/(1 + math.exp(-x))) # math.exp返回x的e次幂
return fz
z = np.arange(-5, 5, 0.01) # np.arange(起点, 终点(不包括), 步长)
fz = sigmoid_function(z)
plt.title('Sigmoid Function')
plt.xlabel('x')
plt.ylabel('y')
plt.plot(z, fz)
plt.show()
阶跃函数和sigmoid函数的比较:
import math
import numpy as np
from matplotlib import pyplot as plt
def step_function(x): # 定义阶跃函数
return np.where(x <= 0, 0, 1)
def sigmoid_function(z): # 定义sigmoid函数
fz = []
for x in z:
fz.append(1/(1 + math.exp(-x)))
return fz
x = np.linspace(-5.0, 5.0, 1000)
y = step_function(x)
z = np.arange(-5, 5, 0.01)
fz = sigmoid_function(z)
plt.plot(x, y, label = 'step_function')
plt.plot(z, fz, linestyle = "--", label = 'sigmoid_function')
plt.xlabel("x")
plt.ylabel("y")
plt.title('Step & Sigmoid ')
plt.legend()
plt.show()
sigmoid函数相较于阶跃函数更平滑,其平滑性对神经网络的学习具有重要意义,二者均为非线性函数(如果线性,叠加层就没用了,比如y=ax,叠两层可能变成了y=abx,这个两次网络仍然相当于一个一层网络,只是稍微改了权重,这将使得神经网络拟合任意函数的定理无法实现)。
-
ReLU函数: ℎ(x)=
当输出为正, 不存在梯度饱和问题,计算速度快
当输入为负数时,完全失效
import numpy as np
from matplotlib import pyplot as plt
def ReLU_function(x): # 定义ReLU函数
return np.maximum(x, 0)
x = np.arange(-5.0, 5.0, 0.01)
y = ReLU_function(x)
plt.plot(x, y)
plt.xlabel("x")
plt.ylabel("y")
plt.show()
2.多维数组的运算
- 一维数组
>>> import numpy as np
>>> A = np.array([1, 2, 3, 4]) # 生成一个一维数组A
>>> print(A)
[1 2 3 4]
>>> np.ndim(A) # ndim返回A的维数
1
>>> A.shape # 得到A的形状,返回一个元组
(4,) # 由于是元组所以4后面加了逗号,否则只有(4)这是数字,不是元组
>>> A.shape[0] # 返回第0个维度的数目
4
- 二维数组(矩阵)
>>> import numpy as np
>>> B = np.array([[1, 2], [3, 4], [5, 6]]) # 生成一个矩阵B
>>> print(B)
[[1 2]
[3 4]
[5 6]]
>>> np.ndim(B) # ndim返回B的维数
2
>>> B.shape # 得到B的形状,返回一个元组
(3, 2) # 3行2列,第0维是行,第1维是列
>>> B.shape[0] # 返回第0个维度的数目
3
- 三维及更高维数组
>>> import numpy as np
>>> A = np.array([[[1, 1], [2, 3], [3, 1]], [[1, 3], [3, 2], [2, 9]], [[1, 3], [3, 2], [2, 9]]])
>>> B = np.array([[[1, 1], [2, 3], [3, 1]], [[1, 3], [3, 2], [2, 9]]])
>>> print(A)
[[[1 1]
[2 3]
[3 1]]
[[1 3]
[3 2]
[2 9]]
[[1 3]
[3 2]
[2 9]]]
>>> print(B)
[[[1 1]
[2 3]
[3 1]]
[[1 3]
[3 2]
[2 9]]]
>>> np.ndim(A)
3
>>> np.ndim(B)
3
>>> A.shape
(3, 3, 2)
>>> A.shape[0]
3
>>> B.shape
(2, 3, 2)
>>> B.shape[2]
2
- 矩阵的运算
A * B:对应位置相乘;A · B或np.dot(A, B)
>>> import numpy as np
>>> A = np.array([[1, 2], [3, 4]])
>>> A.shape
(2, 2)
>>> B = np.array([[5, 6], [7, 8]])
>>> B.shape
(2, 2)
>>> np.dot(A, B) # 矩阵的乘积(点积)
array([[19, 22],
[43, 50]])
- 简单神经网络的实现
利用矩阵乘法实现无偏置和无激活函数的简单神经网络:
>>> import numpy as np
>>> X = np.array([1,2])
>>> W = np.array([[1,3,5],[2,4,6]])
>>> Y = np.dot(X,W)
>>> print(Y)
[ 5 11 17]
# 按照图片
>>> X = np.array([1, 2])
>>> X.shape
(2,)
>>> W = np.array([[1, 3, 5], [2, 4, 6]])
>>> print(W)
[[1 3 5]
[2 4 6]]
>>> W.shape
(2, 3)
>>> Y = np.dot(X, W)
>>> print(Y)
[ 5 11 17]
3.三层神经网络的实现
下面按照书上的运算顺序,即Y=XW+B,实现下图三层网络:
三层神经网络
import numpy as np
import math
# 定义中间层激活函数h()
def sigmoid_function(z): # 选择sigmoid函数
fz = []
for x in z:
fz.append(1/(1 + math.exp(-x)))
return fz
#定义输出层激活函数(恒等函数)
def identity_function(x):
return x
# 确定神经网络参数
X = np.array([1.0, 0.5]) # 输入X:1 x 2的数组
W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]]) # 一层权重W1:2 x 3的矩阵
B1 = np.array([0.1, 0.2, 0.3]) # 一层偏置B1:1 x 3的数组
W2 = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]]) # 二层权重W2:3 x 2的矩阵
B2 = np.array([0.1, 0.2]) # 二层偏置B2:1 x 2的数组
W3 = np.array([[0.1, 0.3], [0.2, 0.4]]) # 三层权重W3:2 x 2的矩阵
B3 = np.array([0.1, 0.2]) # 三层偏置B3:1 x 2的数组
# 各层运算
A1 = np.dot(X, W1) + B1 # 一层输出单元接收值A1:1 x 3的数组
Z1 = sigmoid_function(A1) # 一层输出值Z1:1 x 3的数组
A2 = np.dot(Z1, W2) + B2 # 二层输出单元接收值A2:1 x 2的数组
Z2 = sigmoid_function(A2) # 二层输出值Z2:1 x 2的数组
A3 = np.dot(Z2, W3) + B3 # 三层输出单元接收值A3:1 x 2的数组
Y = identity_function(A3) # 三层输出值Z3:1 x 2的数组
print(Y) # [0.31682708, 0.69627909]
import numpy as np
import math
# 定义神经网络的参数
def init_network():
network = {} # 创建一个字典,用来装神经网络的参数
network['W1'] = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
network['b1'] = np.array([0.1, 0.2, 0.3])
network['W2'] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
network['b2'] = np.array([0.1, 0.2])
network['W3'] = np.array([[0.1, 0.3], [0.2, 0.4]])
network['b3'] = np.array([0.1, 0.2])
return network
# 定义前向传递函数
def forward(network, x):
W1, W2, W3 = network['W1'], network['W2'], network['W3']
b1, b2, b3 = network['b1'], network['b2'], network['b3']
a1 = np.dot(x, W1) + b1
z1 = sigmoid_function(a1)
a2 = np.dot(z1, W2) + b2
z2 = sigmoid_function(a2)
a3 = np.dot(z2, W3) + b3
y = identity_function(a3)
return y
# 定义中间层激活函数h()
def sigmoid_function(z):
fz = []
for x in z:
fz.append(1/(1 + math.exp(-x)))
return fz
#定义输出层激活函数(恒等函数)
def identity_function(x):
return x
# 运算
network = init_network()
x = np.array([1.0, 0.5])
y = forward(network, x)
print(y) # [0.31682708, 0.69627909]
上面的代码把参数封装到一个函数里,把信号的传递方式封装到一个函数里,使得代码看上去更加整洁,同时调用更加方便。
4.输出层
分类问题:判断数据属于哪一个类别的问题,
回归问题:根据输入预测一个连续的数值的问题,
根据输出层的激活函数的选择,神经网络可以解决分类问题或者回归问题:回归问题选择类似上面三层神经网络输出层的恒等函数,分类问题输出层选择softmax函数。
- 恒等函数:对于输入的信息原封不动地输出。
- softmax函数: 假设输出层共有n个神经元,计算第k个神经元的输出。
# softmax函数的实现
import numpy as np
def softmax(a):
c = np.max(a)
exp_a = np.exp(a - c) # 防止溢出所以减掉了c,c是输入信号的最大值
sum_exp_a =np.sum(exp_a)
y = exp_a / sum_exp_a
return y
当 ak很大的时候,这个时候计算机运算不了,因为e的ak次幂太大了,减掉输出信号的最大值可以避免这种情况,这就相当于分子分母同时乘了一个常数,值不变,同时规避了溢出,而且由于指数函数的单调递增,改进softmax函数后并不影响输出的排列,原来是大的还是大,原来是小的还是小。
# softmax函数的特征
>>>import numpy as np
>>>def softmax(a):
c = np.max(a)
exp_a = np.exp(a - c)
sum_exp_a =np.sum(exp_a)
y = exp_a / sum_exp_a
return y
>>>a = np.array([0.3, 2.9, 4.0])
>>>y = softmax(a)
>>>print(y)
[0.01821127 0.24519181 0.73659691]
>>>np.sum(y)
1.0
由以上输出总结softmax:
- softmax的输出都是0到1之间的实数;
- softmax的输出加和是1;
- softmax的输出可以看成是概率,以接下来的手写数字识别为例,假设上面代码的一张图片通过神经网络推理最后得到的信号值是a,用softmax函数激活后得到上面的y值,对于y我们可以把三个神经元看成三类,比如1,2,3,那根据结果就可以说“这张手写数字是1的几率为0.018,是2的几率为0.245,是3的几率为0.737。”通过这样我们就可以实现对数据的分类。
- softmax不改变输出值最大的神经元位置,也就是说输入信号本来就大的,经过softmax还是大,那为什么要加softmax函数,一是为了用概率的方式处理问题,二是为了神经网络的学习。
5.推理手写数字识别
-
导入MINIST数据集
-
神经网络推理MINIST数据集
-
批处理
一次输入100张输出结果也是100×10的
x, t = get_data() # 获得测试集
network = init_network() # 创建神经网络
accuracy_cnt = 0 # 用来计算分类正确的图片个数
batch_size = 100 # 一批一次传100张
for i in range(0, len(x),batch_size): # 从0开始以100为单位取数据,i = 0, 100, 200,..., 9900
x_batch = x[i:i+batch_size] # 取出i到100+i的数据组成x_batch,x_batch的形状是100×784的
y_batch = predict(network, x_batch) # 把一批数据组成的x_batch用神经网络推理得到结果为y_batch,同样是100×10
p= np.argmax(y_batch, axis = 1) # 获取概率最高的元素的索引axis是指沿着每行判断最大值的索引,p是一个100个数的1维数组
accuracy_cnt += np.sum(p == t[i:i+batch_size]) # 每一批看看判断对了几个,然后把所有批次的正确个数加和
print("Accuracy:" + str(float(accuracy_cnt) / len(x))) # 精准度Accuracy是正确个数比总个数
输入数据的集合称为批,以批为单位进行推理处理可以实现高速的运算!
第四章 神经网络的学习
损失函数
均值误差
def mean_aqiared_error(y, t):
return 0.5 * np.sum((y-t)**2)
交叉熵误差
def cross_entropy_error(y, t):
delta = 1e-7
return np.sum(t * np.log(y + delat))
mini-batch
def cross_entropy_error(y, t):
# 检查是否为一维数组,重塑为二维
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
# 若t是one-hot编码的向量,
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
导数
梯度
全部变量的偏导数汇总而成的向量称为梯度
求梯度,相当于对数组x的各个元素求数值微分
def numerical_gradient(f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x) # np.zeros_ like(x)会生成一个形状和x相同、所有元素都为0的数组
for idx in range(x.size): # 如x[3,4],idx=0,1
tmp_val = x[idx] # tmp_val=x[0]=3
# f(x+h)的计算
x[idx] = tmp_val + h # x[0]=3+h,x变为[3+h,4]
fxh1 = f(x) # 算[3+h,4]对应的函数值f
# f(x-h)的计算
x[idx] = tmp_val - h # x[0]=3-h,x为[3-h,4]
fxh2 = f(x) # 算[3-h,4]对应的函数值f
grad[idx] = (fxh1 - fxh2) / (2*h) # 算出梯度
x[idx] = tmp_val # 还原x为[3,4]
return grad
梯度法
神经网络在学习时找到最优参数(权重和偏置)即损失函数取最小值时的参数。但是,损失函数很复杂,参数空间庞大,我们不知道它在何处能取得最小值。而通过巧妙地使用梯度来寻找函数最小值 (或者尽可能小的值)的方法就是梯度法。
严格地讲, 寻找最小值的梯度法称为梯度下降法,寻找最大值的梯度法称为梯度上升法。一般来说,神经网络(深度学习)中,梯度法主要是指梯度下降法。
def gradient_descent(f, init_x, lr=0.01, step_num=100):
x = init_x
for i in range(step_num):
grad = numerical_gradient(f, x)
x -= lr * grad
return x
参数f是要进行最优化的函数,init_x是初始值,lr是学习率learning rate,step_num是梯度法的重复次数。numerical_gradient(f,x)会求函数的梯度,用该梯度乘以学习率得到的值进行更新操作,由step_num指定重复的次数。
学习率过大或者过小都无法得到好的结果。
神经网络的梯度
# 根据书上的思路,可以理解为:
# 1.定义x(输入):1*2,t(标签):1*3,W(权重):2*3 ,y(输出):1*3
# 2.x,W点积求得y
# 3.通过softmax函数处理y
# 4.求损失函数,交叉熵误差
# 5.定义匿名函数,将W变为损失函数的参数
# 6.求损失函数关于W的梯度
# PS:求出来的梯度大小就可以知道权重W如何调整
第五章 误差反向传播法
计算图
-
构建计算图
-
在计算图上,从左向右进行计算(正向传播)
反向传播传递"局部导数"
计算图优点:可以通过正向传播和反向传播高效地计算各个变量的导数值
链式法则
该复合函数的导数可以用构成复 合函数的各个函数的导数的乘积表示
反向传播
乘法的反向传播会将上游的值乘以正向传播时的输入信号的“翻转值” 后传递给下游
简单层的实现
乘法层的实现
层的实现中有两个共通的方法(接口)forward()和backward()。forward() 对应正向传播,backward()对应反向传播。
class MulLayer:
def __init__(self):
self.x = None
self.y = None
def forward(self, x, y):
self.x = x
self.y = y
out = x * y
return out
def backward(self, dout):
dx = dout * self.y # 翻转x和y
dy = dout * self.x
return dx, dy
apple = 100
apple_num = 2
tax = 1.1
# layer
mul_apple_layer = MulLayer()
mul_tax_layer = MulLayer()
#forward
apple_price = mul_apple_layer.forward(apple, apple_num)
price = mul_tax_layer.forward(apple_price, tax)
print(price)
#backward
dprice = 1
dapple_price, dtax = mul_tax_layer.backward(dprice)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)
print(dapple, dapple_num, dtax)
加法层的实现
class AddLayer:
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
apple = 100
orange = 150
apple_num = 2
orange_num = 3
tax = 1.1
class MulLayer:
def __init__(self):
self.x = None
self.y = None
def forward(self, x, y):
self.x = x
self.y = y
out = x * y
return out
def backward(self, dout):
dx = dout * self.y
dy = dout * self.x
return dx, dy
class AddLayer:
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
# layer
mul_apple_layer = MulLayer()
mul_orange_layer = MulLayer()
add_apple_orange_layer = AddLayer()
mul_tax_layer = MulLayer()
#forward
apple_price = mul_apple_layer.forward(apple, apple_num)
orange_price = mul_orange_layer.forward(orange, orange_num)
apple_orange_price = add_apple_orange_layer.forward(apple_price, orange_price)
last_price = mul_tax_layer.forward(apple_orange_price, tax)
#backward
dlast_price = 1
dapple_orange_price, dtax = mul_tax_layer.backward(dlast_price)
dapple_price, dorange_price = add_apple_orange_layer.backward(dapple_orange_price)
dorange, dorange_num = mul_orange_layer.backward(dorange_price)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)
print(apple_price, orange_price, apple_orange_price, last_price)
print(dapple_orange_price, dtax, dapple_price, dorange_price, dorange, dorange_num, dapple, dapple_num)
激活函数层的实现
ReLU层
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
变量mask是由True/False构成的NumPy数组,它会把正向传播时的输入x的元素中小于等于0的地方保存为True,其他地方(大于0的元素)保存为False
Sigmoid层
class Sigmoid:
def __init__(self):
self.out = None
def forward(self, x):
out = 1 / (1 + np.exp(-x))
self.out = out
return out
def backward(self, dout):
dx = dout * (1.0 - self.out) * self.out
return dx
Affine/Softmax层的实现
Affine层
神经网络的正向传播中进行的矩阵的乘积运算在几何学领域被称为“仿射变换”
因此,这里将进行仿射变换的处理实现为“Affine层”
批版本的Affine层
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
Softmax-with-Loss层
神经网络中进行的处理有推理(inference)和学习两个阶段
推理通常不使用 Softmax层
学习阶段则需要 Softmax层
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
误差反向传播法的实现
- 从训练数据中随机选择一部分数据
- 计算损失函数关于各个权重参数的梯度
- 将权重参数沿梯度方向进行微小的更新
- 重复以上步骤
下面是TwoLayerNet的代码实现
import sys, os
sys.path.append(os.pardir)
import numpy as np
from common.layers import *
from common.gradient import numerical_gradient
from collections import OrderedDict
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)
# 生成层
self.layers = OrderedDict()
self.layers['Affinel'] = \
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
# x:输入数据, t:监督数据
def loss(self, x, t):
y = self.predict(x)
return self.lastLayer.forward(y, t)
def accuracy(self, x, t):
y = self.predict(x)
y = np.argmax(t, axis = 1)
if t.ndim ! = 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 = lamba 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()
for layer in layers:
dout = layer.backward(dout)
# 设定
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
误差反向传播法的梯度确认
经常会比较数值微分的结果和 误差反向传播法的结果,以确认误差反向传播法的实现是否正确
确认数值 微分求出的梯度结果和误差反向传播法求出的结果是否一致严格地讲,是非常相近)的操作称为梯度确认(gradient check)
import sys, os
sys.path.append(os.pardir)
import numpy as np
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)
x_batch = x_train[:3]
t_batch = t_train[:3]
grad_numerical = network.numerical_gradient(x_batch, t_batch)
grad_backprop = network.gradient(x_batch, t_batch)
# 求各个权重的绝对误差的平均值
for key in grad_numerical.keys():
diff = np.average( np.abs(grad_backprop[key] - grad_numerical[key]) )
print(key + ":" + str(diff))
使用误差反向传播法的学习
import sys, os
sys.path.append(os.pardir)
import numpy as np
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 = []
iter_per_epoch = max(train_size / batch_size, 1)
for i in range(iters_num):
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
# 通过误差反向传播法求梯度
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)
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)
第六章 与学习相关的技巧
参数的更新
找最优参数的问题,解决这个问题的过程称为最优化
SGD
class SGD:
def __init__(self, lr = 0.01):
self.lr = lr
def update(self, params, grads):
for key in params.key():
params[key] -= self.lr * grads[key]
参数lr表示learning rate(学习率)
参数params和grads是字典型变量,
按params['W1']、grads['W1']的形式,分别保存了权重参数和它们的梯度
SGD的缺点
如果函数的形状非均向(anisotropic),比如呈延伸状,搜索 的路径就会非常低效
SGD低效的根本原因是,梯度的方向并没有指向最小值的方向
Mommentum
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.key():
self.v[key] = self.momentum * self.v[key] - self.lr * grads[key]
params[key] += self.v[key]
AdaGrad
在神经网络的学习中,学习率(数学式中记为η)的值很重要。
学习率过小,会导致学习花费过多时间;学习率过大,则会导致学习发散而不能正确进行
在关于学习率的有效技巧中,有一种被称为学习率衰减(learning rate decay)的方法,即随着学习的进行,使学习率逐渐减小。
class 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)
Adam
Momentum参照小球在碗中滚动的物理规则进行移动
AdaGrad为参 数的每个元素适当地调整更新步伐
Adam将两者融合
Adam会设置 3个超参数。一个是学习率(论文中以α出现),另外两 个是一次momentum系数β1和二次momentum系数β2。根据论文, 标准的设定值是β1为 0.9,β2 为 0.999。设置了这些值后,大多数情 况下都能顺利运行
权重的初始值
为了防止“权重均一化” (严格地讲,是为了瓦解权重的对称结构),必须随机生成初始值
偏向0和1的数据分布会造成反向传播中梯度的值不断变小,最后消失.这个问题称为梯度消失(gradient vanishing)
Xavier初始值
后面的层的分布呈稍微歪斜的形状。如果用tanh 函数(双曲线函数)代替sigmoid函数,这个稍微歪斜的问题就能得到改善。实际上,使用tanh函数后,会呈漂亮的吊钟型分布。tanh 函数和sigmoid函数同是S型曲线函数,但tanh函数是关于原点(0, 0) 对称的S型曲线,而sigmoid函数是关于(x, y)=(0, 0.5)对称的S型曲线。众所周知,用作激活函数的函数最好具有关于原点对称的性质(我!不!知!道!)。
ReLU的权重初始值
Xavier初始值是以激活函数是线性函数为前提而推导出来的。
因为sigmoid函数和tanh函数左右对称,且中央附近可以视作线性函数,所以适合使用Xavier初始值。
但当激活函数使用ReLU时,一般推荐使用ReLU专用的初始值,也就是Kaiming He等人推荐的初始值,也称为“He初始值”。
总结一下:
当激活函数使用ReLU时,权重初始值使用He初始值
当激活函数为sigmoid或tanh等S型曲线函数时,初始值使用Xavier初始值
Batch Normalization
Batch Norm优点
- 可以使学习快速进行(可以增大学习率)
- 不那么依赖初始值(对于初始值不用那么神经质)
- 抑制过拟合(降低Dropout等的必要性)
式(6.7)所做的是将mini-batch的输入数据{x1, x2, ... , xm}变换为均值为0、方差为1的数据 ,非常简单。通过将这个处理插入到 激活函数的前面(或者后面)A,可以减小数据分布的偏向。
接着,Batch Norm层会对正规化后的数据进行缩放和平移的变换
正则化
过拟合指的是只能拟 合训练数据,但不能很好地拟合不包含在训练数据中的其他数据的状态。
过拟合的原因:
- 模型拥有大量参数、表现力强
- 训练数据少
权值衰减
权值衰减是一直以来经常被使用的一种抑制过拟合的方法。
该方法通过在学习的过程中对大的权重进行惩罚,来抑制过拟合。
很多过拟合原本就是因为权重参数取值过大才发生的。
Dropout
Dropout是一种在学习的过程中随机删除神经元的方法。
训练时,随机选出隐藏层的神经元,然后将其删除。被删除的神经元不再进行信号的传递
超参数的验证
这里所说的超参数是指,比如各层的神经元数量、batch大小、参数更新时的学习率或权值衰减等。
调整超参数时,必须使用超参数专用的确认数据。用于调整超参数的数据,一般称为验证数据(validation data)
训练数据用于参数(权重和偏置)的学习,
验证数据用于超参数的性能评估。
超参数的最优化
在超参数的最优化中,减少学习的epoch,缩短一次评估所需的时间
- 设定超参数的范围
- 从设定的超参数范围中随机采样
- 使用步骤1中采样到的超参数的值进行学习,通过验证数据评估识别精 度(但是要将epoch设置得很小)
- 重复步骤1和步骤2(100次等),根据它们的识别精度的结果,缩小超参 数的范围
第七章 卷积神经网络
整体结构
CNN中新出现了卷积层(Convolution层)和池化层(Pooling层)
卷积层
全连接层存在什么问题呢?那就是数据的形状被“忽视”了,将全部的输入数据作为相同的神经元 (同一维度的神经元)处理,所以无法利用与形状相关的信息
而卷积层可以保持形状不变,卷积层的输入数据称为输入特征图(input feature map),输出 数据称为输出特征图(output feature map)
填充
在进行卷积层的处理之前,有时要向输入数据的周围填入固定的数据(比如0等),这称为填充(padding)。幅度为1的填充”是指用幅度为1像素的0填充周围
使用填充主要是为了调整输出的大小。因此,卷积运算就可以在保持空间大小不变 的情况下将数据传给下一层。
步幅
应用滤波器的位置间隔称为步幅(stride)。
增大步幅后,输出大小会变小。而增大填充后,输出大小会变大。
3维数据的卷积运算
通道数只能设定为和输入数据的通道数相同的值
池化层
池化是缩小高、长方向上的空间的运算。
图7-14的例子是按步幅2进行2 × 2的Max池化时的处理顺序。“Max 池化”是获取最大值的运算,“2 × 2”表示目标区域的大小。
一般来说,池化的窗口大小会 和步幅设定成相同的值。
除了Max池化之外,还有Average池化等。相对于Max池化是从 目标区域中取出最大值,Average池化则是计算目标区域的平均值。 在图像识别领域,主要使用Max池化。
池化层的特征
- 没有要学习的参数
- 通道数不变
- 对微小的位置变化具有鲁棒性(健壮)
卷积层和池化层的实现
现在使用im2col来实现卷积层。
class Convolution:
def __init__(self, W, b, stride = 1, pad = 0):
self.W = W
self.b = b
self.stride = stride
self.pad = pad
def forward(self, x):
FN, C, FH, FW = self.W.shape
N, C, H, W = x.shape
out_h = int(1 + (H + 2 * self.pad - FH) / self.stride)
out_w = int(1 + (W + 2 * self.pad - FW) / self.stride)
col = im2col(x, FH, FW, self.stride, self.pad)
col_W = self.W.reshape(FN, -1).T
out = np.dot(col, col_W) + self.b
out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)
return out
在进行卷积层的反向传播时,必须进行im2col 的逆处理。
池化层的forward处理的Python的实现示例
- 展开输入数据
- 求各行的最大值
- 转换为合适的输出大小
class Pooling:
def __init__(self, pool_h, pool_w, stride=1, pad=0):
self.pool_h = pool_h
self.pool_w = pool_w
self.stride = stride
self.pad = pad
def forward(self, x):
N, C, H, W = x.shape
out_h = int(1 + (H - self.pool_h) / self.stride)
out_w = int(1 + (W - self.pool_w) / self.stride)
# 展开(1)
col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
col = col.reshape(-1, self.pool_h*self.pool_w)
# 最大值(2)
out = np.max(col, axis=1)
# 转换(3)
out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)
return out
CNN的实现
class SimpleConvNet:
def __init__(self, input_dim=(1, 28, 28),
conv_param={'filter_num':30, 'filter_size':5,
'pad':0, 'stride':1},
hidden_size=100, output_size=10, weight_init_std=0.01):
filter_num = conv_param['filter_num']
filter_size = conv_param['filter_size']
filter_pad = conv_param['pad']
filter_stride = conv_param['stride']
input_size = input_dim[1]
conv_output_size = (input_size - filter_size + 2*filter_pad) / \
filter_stride + 1
pool_output_size = int(filter_num * (conv_output_size/2) *
(conv_output_size/2))
self.layers = OrderedDict()
self.layers['Conv1'] = Convolution(self.params['W1'],
self.params['b1'],
conv_param['stride'],
conv_param['pad'])
self.layers['Relu1'] = Relu()
self.layers['Pool1'] = Pooling(pool_h=2, pool_w=2, stride=2)
self.layers['Affine1'] = Affine(self.params['W2'],
self.params['b2'])
self.layers['Relu2'] = Relu()
self.layers['Affine2'] = Affine(self.params['W3'],
self.params['b3'])
self.last_layer = softmaxwithloss()
def predict(self, x):
for layer in self.layers.values():
x = layer.forward(x)
return x
def loss(self, x, t):
y = self.predict(x)
return self.lastLayer.forward(y, t)
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()
for layer in layers:
dout = layer.backward(dout)
# 设定
grads = {}
grads['W1'] = self.layers['Conv1'].dW
grads['b1'] = self.layers['Conv1'].db
grads['W2'] = self.layers['Affine1'].dW
grads['b2'] = self.layers['Affine1'].db
grads['W3'] = self.layers['Affine2'].dW
grads['b3'] = self.layers['Affine2'].db
return grads
CNN的可视化
具有代表性的CNN
LeNet
LeNet中使用sigmoid函数,而现在的CNN中主要使用ReLU函数
原始的LeNet中使用子采样(subsampling)缩小中间数据的大小,而 现在的CNN中Max池化是主流
AlexNet
激活函数使用ReLU
使用进行局部正规化的LRN(Local Response Normalization)层
使用Dropout
第八章 深度学习
加深网络
对于大多数的问题,都可以期待通过加深网络来提高性能
深度学习的小历史
VGG、GoogLeNet、ResNet等是几个著名的网络。
深度学习的高速化
基于GPU、分布式学习、位数精度的缩减,可以实现深度学习的高速化
深度学习的应用案例
深度学习(神经网络)不仅可以用于物体识别,还可以用于物体检测、 图像分割