实验 用numpy搭建全连接神经网络用于手写数字识别

【代码+原理讲解】使用Numpy实现一个简单的四层全连接神经网络(手写数字识别,mnist数据集,正确率98.58%) - 知乎
入门讲解:使用numpy实现简单的神经网络(BP算法)-CSDN博客
结合代码和公式对全连接神经网络的实现进行分析

数据处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 标准化处理  
if normalize:
for _ in ('train_img', 'test_img'):
dataset[_] = dataset[_].astype(np.float32) / 255.0
# one_hot_label处理
if one_hot_label:
for _ in ('train_label', 'test_label'):
t = np.zeros((dataset[_].size, 10))
for idx, row in enumerate(t):
row[dataset[_][idx]] = 1
dataset[_] = t
# 展平处理
if flatten:
for _ in ('train_img', 'test_img'):
dataset[_] = dataset[_].reshape(-1, 784)
# 划分验证集
if val_data:
x_val_data, x_test_data = np.split(dataset['test_img'], 2)
y_val_data, y_test_data = np.split(dataset['test_label'], 2)
return dataset['train_img'], dataset['train_label'], x_val_data, y_val_data, x_test_data, y_test_data
  • 标准化处理:将数据归一化

  • one hot处理:将数据处理成one hot形式,即维度扩充为与数据类别相同,数据为哪个类别,其相应维度上的值为1,否则为0

  • 展平处理:将28*28的图像转换成一个维度上784的大小

  • 划分验证集:原数据集为训练集:测试集60000:10000,把测试集中的5000条作为验证集

全连接层

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
class Net(object):  
def __init__(self, num_input, num_output):
self.num_input = num_input
self.num_output = num_output
self.w = np.random.normal(loc=0.0, scale=0.01, size=(self.num_input, self.num_output)) # 随机初始化参数 假设为(n, m)
self.bias = np.zeros([1, self.num_output]) # 初始化为0 (1, m)
self.input_data = np.zeros(0)
self.output_data = np.zeros(0)
self.grad_w = np.zeros(0)
self.grad_b = np.zeros(0)

def forward(self, input_data):
self.input_data = input_data # 假设input_data = (1, n)
self.output_data = np.matmul(self.input_data, self.w) + self.bias # (1, n) * (n, m) = (1, m) m为下一层的输入维度
return self.output_data

def backward(self, grad):
self.grad_w = np.dot(self.input_data.T, grad) # (n, 1) * (1, m) = (n, m)
self.grad_b = np.sum(grad, axis=0)
next_grad = np.dot(grad, self.w.T) # (1, m) * (m, n) = (1, n)
return next_grad

def backward_with_l2(self, grad, lamb, batch_size):
self.grad_w = np.dot(self.input_data.T, grad) + (lamb / batch_size) * self.w
self.grad_b = np.sum(grad, axis=0)
next_grad = np.dot(grad, self.w.T)
return next_grad

def update(self, lr):
self.w = self.w - lr * self.grad_w
self.bias = self.bias - lr * self.grad_b

定义一个类,在其中实现的功能有:

  • 初始化:在创建一层全连接层时,需要初始化w和b,w为(m,n)其中m为输入数据的维度,n为下一层的输入维度,b为(1,n).公式如下:
    image.png

  • 前馈函数:在前馈函数中实现上面的公式

  • 反向传播:需要根据损失更新参数w和b的值,因此分别对w和b求偏导
    image.png

前一层的梯度可以根据后一层的梯度得到:
image.png
根据上面这个公式,可以发现,前一层可以使用后一层的误差项来得到自己的误差项,而不需要从最后用链式法则进行推导。因此称为反向传播
推导过程如下:
image.png

  • 更新参数:在原来的参数上减去梯度方向得到新的参数,实验中往往需要学习率来控制更新的程度

激活函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ReLU(object):  

def __init__(self):
self.input_data = np.zeros(0)

def forward(self, input_data):
self.input_data = input_data
output_data = np.maximum(0, input_data) # (1, n)
return output_data

def backward(self, grad):
next_grad = grad # (1, n) * (1, n) 逐元素相乘
next_grad[self.input_data < 0] = 0
return next_grad

激活函数同样需要两个,一个实现前向传播,一个实现反向传播

Softmax+交叉熵损失

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Softmax(object):  
def __init__(self):
self.prob = np.zeros(0)
self.batch_size = []
self.label = []

def forward(self, input_data):
input_max = np.max(input_data, axis=1, keepdims=True)
input_exp = np.exp(input_data - input_max)
self.prob = input_exp / np.sum(input_exp, axis=1, keepdims=True)
return self.prob

def get_loss(self, label): # 计算损失
self.label = label
self.batch_size = self.prob.shape[0]
loss = -np.sum(label * np.log(self.prob + 1e-7)) / self.batch_size
return loss

def backward(self):
grad = (self.prob - self.label) / self.batch_size
return grad

在Softmax中除了实现前向和后向传播外,添加了用交叉熵计算损失的函数,这是因为在softmax后加交叉熵,反向传播的公式会更简便。

MSE损失(Mean squared error均方误差)

1
2
3
4
def MSE_loss(self, y_pre, y):  
loss = 0.5 * np.sum((y_pre - y) ** 2) / batch_size
grad = (y_pre - y) / batch_size
return loss, grad

整体的传播

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
def forward(self, input_data):  # 神经网络的前向传播  
h1 = self.fc1.forward(input_data)
h1 = self.relu1.forward(h1)
# h1 = self.sigmoid1.forward(h1)
h2 = self.fc2.forward(h1)
h2 = self.relu2.forward(h2)
# h2 = self.sigmoid2.forward(h2)
h3 = self.fc3.forward(h2)
# prob = self.softmax.forward(h1)
return h3

def backward(self, y_pre, y): # 神经网络的反向传播
_, grad = self.MSE_loss(y_pre, y)
# grad = self.softmax.backward()
dh3 = self.fc3.backward(grad)
dh2 = self.relu2.backward(dh3)
# dh2 = self.sigmoid2.backward(dh3)
dh2 = self.fc2.backward(dh2)
dh1 = self.relu1.backward(dh2)
# dh1 = self.sigmoid1.backward(dh2)
dh1 = self.fc1.backward(dh1)

def update(self, lr):
self.fc1.update(lr)
self.fc2.update(lr)
self.fc3.update(lr)

实验结果

使用mini-batch GD,使用效果较好的模型参数
image.png
得到实验结果如下:
image.png
最终测试集准确率稳定在98%以上。


实验 用numpy搭建全连接神经网络用于手写数字识别
http://example.com/2024/11/27/Notes/课程/大三(上)/神经网络与深度学习/实验 用numpy搭建全连接神经网络用于手写数字识别/
许可协议