Home 鱼书 —— 《深度学习入门》读书笔记
Post
Cancel

鱼书 —— 《深度学习入门》读书笔记

Chapter 1 —— Python基础

Python有“解释器”和“脚本文件”两种运行模式

Numpy

实际上,Numpy主要的处理也是通过C/C++实现的

导入Numpy:import numpy as np

np.array([...])传入python list构造numpy的array,可以做element-wise(对应元素)运算

np.array([[1, 2], [3, 4]])可以构造多维数组,可以通过.shape查询维度

np.arange(0, 6, 0.1)以0.1为单位,生成从0到6的数据

一维数组->向量,二维数组->矩阵,三维数组->张量

广播

numpy的广播原则:如果两个数组的后缘维度(从末尾开始算起的维度)的轴长度相符或其中的一方的长度为1,则认为它们是广播兼容的。广播会在缺失和(或)长度为1的维度上进行

通过扩展,不同形状的数组也能运算

1
2
3
4
5
>>> A = np.array([[1, 2], [3, 4]])
>>> B = np.array([10, 20])
>>> A*B
array([[10, 40],
	[30, 80]])

访问元素

可以用for row in X遍历X的行元素,也可以直接下标访问

X = X.flatten()可以将多维数组转换为维度(追加)

从np.array中抽取元素:

1
2
3
4
5
6
7
8
>>> print(X)
[51 55 14 19 0 4]
>>> X[np.array([0, 2, 4])]
array([51, 14, 0])
>>> X > 15
array([True, True, False, True, False, False], dtype=bool) 
>>> X[X>15]
array([51, 55, 19])

Matplotlib

绘制图形的库,使用Matplotlib可以轻松地绘制图形和实现数据的可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
import matplotlib.pyplot as plt

x = np.arange(0, 6, 0.1)
y1 = np.sin(x)
y2 = np.cos(x)

plt.plot(x, y1, label="sin")
plt.plot(x, y2, label="cos", linestyle="--")
plt.xlabel("x")
plt.ylabel("y")
plt.title("1.6 sin & cos")
plt.legend()  # 显示label内容
plt.show()

还可以通过imshow()显示图像,位于matplotlib.image模块

1
2
3
4
5
6
import matplotlib.image as img

path = "/Users/mariochan/Desktop/Paper.jpg"
x = img.imread(path)
plt.imshow(x)
plt.show()

Chapter 2 —— 感知机

感知机基础

感知机(perceptron),本节中可以理解为人工神经元或者朴素感知机

如果输入乘以权重的和高于某个界限值$\theta$,这里称为阈值,那么“神经元被激活”,输出为1: \(y=\begin{cases} 1, & \text{$w_1x_1+w_2x_2>\theta$} \\ 0, & \text{otherwise} \end{cases}\) 可以结合简单的逻辑电路实现:AND gate、NAND gate、OR gate等:

1
2
3
4
5
6
7
def AND(x1, x2):
    w1, w2, theta = 0.5, 0.5, 0.7
    tmp = w1*x1 + w2*x2
    if tmp <= theta:
        return 0
    else:
        return 1

特别的,我们可以对感知机的表达式作修改: \(y=\begin{cases} 1, & \text{$w_1x_1+w_2x_2+b>0$} \\ 0, & \text{otherwise} \end{cases}\) 此时,我们将$b$称为偏置( $b=-\theta$ ),将$w1,w2$称为权重,以下为numpy写法

1
2
3
4
5
6
7
8
9
10
import numpy as np
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

权重是控制输入信号的重要性参数,而偏置是神经元被激活的难易程度参数

NAND gate与AND gate只要权重和偏置同时取负就可以实现!

1
2
3
4
5
6
7
8
9
10
import numpy as np
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

朴素感知机局限性

无法实现XOR(结合空间划分p30),这本质上是因为感知机只能表示由一条线性划分的空间(线性空间,反之称为非线形空间)

多层感知机 —— multi-layered perceptron

多层感知机可以通过叠加层表示XOR等

利用已有门电路的组合

XOR = OR + NAND + AND

1
2
3
4
def XOR(x1, x2):
    s1 = NAND(x1, x2)
    s2 = OR(x1, x2)
    return AND(x1, x2)

Chapter 3 —— 神经网络

神经网络:输入层、中间层(隐藏层)、输出层

激活函数 —— Activation Function

\[y=\begin{cases} 1, & \text{$w_1x_1+w_2x_2+b>0$} \\ 0, & \text{otherwise} \end{cases}\]

我们将上述感知机表达式转换为: \(y=h(w_1x_1+w_2x_2+b), \quad h(x) = \begin{cases} 1, & \text{$x>0$} \\ 0, & \text{otherwise} \end{cases}\) 此时$h(x)$即为激活函数(Activation Function),这里可以看看p41的计算过程图

一般而言,“朴素神经网络”指的是单层网络,指的是激活函数使用了阶跃函数(一旦输入超过阈值,就会切换输出)模型

“多层感知机”是指神经网络,即使用sigmoid函数等平滑的激活函数的多层网络

阶跃函数

实现
1
2
3
4
5
6
def step_function(x):
    y = x > 0
    return y.astype(int)
  
def step_function2(x):
	return np.array(x > 0, dtype=int)

example:

1
2
3
4
5
6
7
8
9
10
>>> import numpy as np
>>> x = np.array([-1.0, 1.0, 2.0])
>>> x
array([-1., 1., 2.])
>>> y = x > 0
>>> y
array([False, True, True], dtype=bool)
>>> y = y.astype(np.int)
>>> y
array([0, 1, 1])

astype()可以转换Numpy数组的类型

图形

示例:

1
2
3
4
5
6
7
8
9
10
11
import numpy as np
import matplotlib.pylab as plt

def step_function(x):
    return np.array(x > 0, dtype=int)

x = np.arange(-5.0, 5.0, 0.1)
y = step_function(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1)  # 限定y轴范围
plt.show()

Sigmoid

expression: $h(x) = \frac{1}{1+e^{-x}}$

实现

利用了广播特性

1
2
3
4
5
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

x = np.array([-1.0, 1.0, 2.0])
print(sigmoid(x))

如果我们将上一节的画图函数的阶跃函数替换成Sigmoid函数,differences:

  1. 我们可以得到一条更为平滑的曲线,而不是急剧变化
  2. Sigmoid的可以得到的结果是连续的,而阶跃函数得到的结果是离散的

相同点:

  1. 值域[0, 1],结构均是“小输入接近0(为0),大输入接近1(为1)”
  2. 均为非线形函数

为什么激活函数不能使用线形函数

不管如何加深层数,总是存在与之等效的“无隐藏层的神经网络”。example: 对于$h(x) = cx$,我们尝试叠加3层,即$y = h(h(h(x))) = c^3x$,如果我们令$a=c^3, f(x) = ax$,那么多层网络实际上没有意义。所以为了发挥叠加层所带来的优势,激活函数必须使用非线形函数。

ReLU函数

ReLU(Rectified Linear Unit):$h(x) = \begin{cases} x, & \text{$x>0$} \ 0, & \text{otherwise}
\end{cases}$

1
2
def relu(x):
    return np.maximum(0, x)

多维数组运算

np.ndim()获得当前数组的维度

A.shape获得当前数组各个维度的大小

1
2
3
4
5
6
7
8
9
10
>>> import numpy as np
>>> A = np.array([1, 2, 3, 4])
>>> print(A)
[1 2 3 4]
>>> np.ndim(A)
1
>>> A.shape
(4,)
>>> A.shape[0]
4

矩阵运算

维度为2的数组称为矩阵,np.dot() 为点积

1
2
3
4
5
6
7
8
9
>>> 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]])

输出层的激活函数一般用$\sigma()$ (sigma) 表示,不同于隐藏层的$h()$,对于回归问题的$\sigma()$采用identity_funtion(),二元分类问题使用softmax()

3层神经网络
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import numpy as np


def sigmoid(x):
    return 1 / (1+np.exp(-x))

def identity_function(x):  # 恒等函数
    return x

X = np.array([1.0, 0.5])
W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
B1 = np.array([0.1, 0.2, 0.3])

A1 = np.dot(X, W1) + B1
Z1 = sigmoid(A1)

W2 = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
B2 = np.array([0.1, 0.2])

A2 = np.dot(Z1, W2)
Z2 = sigmoid(A2)

W3 = np.array([[0.1, 0.3], [0.2, 0.4]])
B3 = np.array([0.1, 0.2])


A3 = np.dot(Z2, W3)
Z3 = identity_function(A3)

print(Z3)

更常见的做法,我们使用init_network()将权重和偏置进行初始化,然后存在字典network中,然后再在forward()中编写输入转换为输出的过程。

forward表示的是前向,指的是从输入到输出方向的传递处理

Softmax函数

expression: $y_k = \frac{exp(a_k)}{\sum^n_{i=1}exp(a_k )}$

1
2
3
4
5
def softmax(a):
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)    
    y = exp_a / sum_exp_a
    return y

改进

指数overflow

1
2
3
4
5
6
import numpy as np

a = np.array([1010, 1000, 990])
# overflow
# b = np.exp(a) / np.sum(np.exp(a))
# print(b)

解决方法:给每个数字加上(减去)一个常数,一般选择最大值: \(y_k = \frac{exp(a_k)}{\sum^n_{i=1}exp(a_k )}=\frac{Cexp(a_k)}{C\sum^n_{i=1}exp(a_k )} \\ =\frac{exp(a_k+logC)}{\sum^n_{i=1}exp(a_k+logC)}=\frac{exp(a_k+C')}{\sum^n_{i=1}exp(a_k+C')}\)

1
2
3
4
c = np.max(a)
a = a-c
b = np.exp(a) / np.sum(np.exp(a))
print(b)

特征

softmax的和总是1,我们可以把softmax的输出解释为“概率”

sofrmax值最大对应神经元最大

一般而言,神经网络在推理阶段只将最大神经元对应的类别作为识别结果,这样看的话计算softmax其实是无意义的开销,所以一般会被忽略

前向传播 —— forward propagation

求解机器学习的步骤:学习参数、利用学习到的参数进行推理

手写数字识别

load_mnist函数以“(训练图像,训练标签),(测试图像,测试标签)”的形式返回读入的MNIST数据。此外,还能像load_mnist(normalize=True, flatten=True, one_hot_label=False)这样设置参数,详情看p71

one-hot representation将标签表达为[0, 0, 0, 1, 0, 0, 0],仅正确解标签为1

load_mnist函数内置pickle功能,能将运行中的对象保存为文件,后期可以快速调用

识别精度(accuracy):分类正确率统计

正则化(normalization):将数据限定在某个范围内

预处理(pre-processing):对输入数据进行某种转换

数据白化(whitening):将数据整体的分布形状均匀化

批(batch):打包式输入数据

np.argmax(arr, axis=1)按第1维找最大值对应的下标

Chapter 4 —— 神经网络的学习

特征:从数据中学习

感知机收敛定理:通过有限次数的学习,线性可分问题是可解的,但是非线形可分问题无法通过(自动)学习来解决

特征量:可以从输入数据中准确提取本质数据的转换器

  1. input -> 人想到的算法 -> output
  2. input -> 人想到的特征量(如SIFT、HOG)-> 机器学习(如SVM、KNN)-> output
  3. input -> 神经网络(深度学习)-> output

因此,深度学习也被称为端对端机器学习(end-to-end machine learning)

训练数据与测试数据

使用训练数据寻找最优参数,然后使用测试数据评价模型的实际能力。为的是评价模型的泛化能力。此外训练数据也可以称为监督数据。

过拟合(over fitting)

损失函数

损失函数(loss function)可以使用任意函数,但一般使用均方误差和交叉熵误差。

均方误差

mean squared error:$E=\frac{1}{2}\sum_k(y_k-t_k)^2 $,其中$y_k$表示输出,$t_k$表示监督数据,$k$表示数据维数

只有正确解标签为1,其他标签为0,称为one-hot表示

1
2
3
4
5
6
7
def mean_squared_error(y, t):
    return 0.5 * np.sum((y-t)**2)

y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]

print(mean_squared_error(np.array(y), np.array(t)))

交叉熵误差

cross entropy error:$E=-\sum_kt_k\times logy_k$

我们会加上一个微小偏差$delta$,这样当正确解对应的输出为0的时候,不会出现$-inf$导致后续输出有误

1
2
3
def cross_entropy_error(y, t):
    delta = 1e-7  # 微小偏差
    return -np.sum(t * np.log(y + delta))

mini-batch

对于所有数据的交叉熵误差总和:$E=\frac{1}{N}\sum_n\sum_kt_{nk}\times logy_{nk}$

如果训练代价较大,随机抽样作为训练数据,称为mini-batch学习

np.random.choice()可以在指定范围内抽取数字

1
2
3
4
5
6
7
8
9
10
11
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)

print(x_train.shape)  # (60000, 784)
print(t_train.shape)  # (60000, 10)

train_size = x_train.shape[0]
batch_size = 10
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
print(batch_mask)

mini-batch版交叉熵误差的实现

1
2
3
4
5
6
7
8
def cross_entropy_error(y, t):
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)
    
    batch_size = y.shape[0]
    return -np.sum(t * np.log(y + 1e-7)) / batch_size  # t为one-hot表示时
    # t为标签形式时: return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size 

为什么不能用识别精度来评价

无法更新梯度,无法获得连续性反馈

sigmoid函数的斜率永不为0,very import

数值微分

numerical differentiation,numerical_diff(f, x),还有一个隐式参数$h$表示自变量变化量。

问题与改进:

  1. $h$这个值不能随意取,太大的话误差大,太小的话float精度丢失,可以选取$10^{-4}$
  2. 由于$h$实际上并不接近0,我们计算斜率会存在误差,我们可以通过计算(x-h)到(x+h)的差分来部分抵消这种影响,这种差分叫做中心差分(普通的叫做前向查分
1
2
3
def numerical_diff(f, x):
    h = 1e-4  # 0.001
    return (f(x+h) - f(x-h)) / (2 * h)

example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def numerical_diff(f, x):
    h = 1e-4  # 0.001
    return (f(x+h) - f(x-h)) / (2 * h)

def function_1(x):
    return 0.01*x**2 + 0.1*x

def getTangent(f, x):
    k = numerical_diff(f, x)
    b = f(x) - k * x
    return lambda t: k * t + b

x = np.arange(0.0, 20.0, 0.1)
y = function_1(x)

f2 = getTangent(function_1, 5)
t = f2(x)

plt.xlabel("x")
plt.ylabel("function_1 Val")
plt.xlim(0.0, 20.0)
plt.ylim(0.0, 6.0)
plt.plot(x, y)
plt.plot(x, t)
plt.show()

print(numerical_diff(function_1, 5))
print(numerical_diff(function_1, 10))

对应的,通过数学分析得到的导数称为解析性求导

梯度

gradient:指示的是各点处的函数值减小最多的方向

np.zeros_like(x)会生成一个形状和x相同、所有元素都为0的数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def numerical_gradient(f, x):  # 数值微分法
    h = 1e-4  # 0.0001
    grad = np.zeros_like(x)
    print(x, grad)
    for idx in range(x.size):
        tmp_val = x[idx]
        x[idx] = 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

梯度法 —— gradient method

鞍点(saddle point):梯度为0

寻找最小值:梯度下降法(gradient descent method),寻找最大值:梯度上升法(gradient ascent method)

在深度学习中,主要指的是梯度下降法

学习率(learning rate)$\eta$:表示更新量 \(x = x-\eta\frac{\partial f}{\partial x}\)

1
2
3
4
5
6
7
8
def gradient_decent(f, init_x, lr=0.01, step_num=100):  # lr学习率,step_num梯度法重复次数
    x = init_x
    
    for i in range(step_num):
        grad = numerical_gradient(f, x)
        x -= lr * grad  # 上升的反方向

    return x

Test:

1
2
3
4
5
6
def function_2(x):
    return x[0]**2 + x[1]**2

init_x = np.array([-3.0, 4.0])
final_x = gradient_decent(function_2, init_x, lr=0.1)
print(final_x)

关于学习率

过大:容易发散

过小:基本没更新就结束了,除非step_num同步增大

诸如学习率这样的参数称为超参数(hyper parameter)

神经网络的梯度

np.random.randn():符合高斯分布的初始化

lambda表示法lambda parameters: expression

总结

P109-p119

Step:

  1. mini-batch
  2. 计算梯度
  3. 更新参数
  4. 重复上述步骤

由于mini-batch,所以又称为随机梯度下降法(stockastic gradient descent),常用SGD(三个首字母)标识

我们每隔一个epoch就记录一些衡量指标(如识别精度)

epoch:所有训练数据都被使用过一次的更新次数

如果测试数据和训练数据的识别精度变化重合,可以理解为没有过拟合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 为了导入父目录的文件而进行的设定
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

Chapter 5 —— 误差反向传播网络

Computational Graph, Chain Rule

简单层实现

加法层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

乘法层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

激活函数层的实现

ReLU层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ReLU:
	def __init__(self):
		self.mask = None  # 由True/False组成的Numpy数组,将正向传播时的输入x的数据小于等于0的地方保存为True
		
	def forward(self, x):
		self.mask = (x <= 0)
		out = x.copy()
		out[self.mask] = 0
		
		return x
	
	def backward(self, dout):
		dout[self.mask] = 0
		dx = dout
		
		return dx

Sigmoid层

\[\frac{\partial L}{\partial y}y(1-y)\]

y represents output value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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层”

\[Y = X*W+B\quad -> \quad \frac{\partial L}{\partial X} = \frac{\partial L}{\partial Y} * W^T \\ \frac{\partial L}{\partial W} = X^T * \frac{\partial L}{\partial Y} \\ \frac{\partial L}{\partial B} = \frac{\partial L}{\partial Y}的第一个轴方向上的和\]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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层

这里的Loss采用Cross entropy error

最后推出反向传播结果为$y_i-t_i$,Softmax层反向传播的结果为输出与监督标签的差分

如果误差大,反向传播给前面的层的值大,那么学习内容也大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class SoftmaxWithLoss:
	def __init__(self):
		self.loss = None
    self.y = None
    self.t = None  # one-hot vector
   
  def forward(self, x, t):
    self.t = t
    self.y = softmax(x)
    self.loss = cross_entropy_error(self.y, self.t)
   
  def backward(self, dout=1):
    batch_size = self.t.shape[0]
    dx = (self.y - self.t) / batch_size  # 得到单个数据的误差
    
    return dx

误差反向传播法的实现

OrderedDict有序字典,可以记住插入顺序,在反向传播的时候只需reverse就行了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# coding: utf-8
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['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
        
    # 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(y, 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 = 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()
        for layer in layers:
            dout = layer.backward(dout)

        # 设定
        grads = {}
        grads['W1'], grads['b1'] = self.layers['Affine1'].dW, self.layers['Affine1'].db
        grads['W2'], grads['b2'] = self.layers['Affine2'].dW, self.layers['Affine2'].db

        return grads

梯度确认gradient check

通过数值微分与误差反向传播法求出的结果对比

Chapter 6 —— 与学习相关的技巧

This post is licensed under CC BY 4.0 by the author.

最近点对数学分析Solution

ACM & 2023蓝桥杯省赛CB组讲解

Comments powered by Disqus.