用numpy 实现最简单的前馈神经网络——正向网络建立篇
根据上一篇文章,来构建神经网络吧
- 明确输入和输出
- 选择合适的各种函数
- 用矩阵和激活函数建立起从输入到输出的拟合函数
- 用正向传播或反向传播获得损失函数的偏导数(注意对一定的数据集来说自变量为
W
\bold{W}
W,
A
\bold{A}
A固定)
- 用梯度下降法努力使损失函数最小
mnist分析(输入分析)
下载
在这里下载mnist数据集
关于mnist的详细说明在其他人的文章里有
说明
images 前16个字节包含了数据的说明,之后的所有字节以
784
784
784字节为一组,是一个个
28
×
28
28\times28
28×28像素的图像,像素为一字节的灰度像素
labels 前8个字节包含了数据的说明,之后的所有字节以
1
1
1字节为一组,是一个个对应着images 中图像的数字
加载
以下函数的关键点在于np.fromfile() 以及dtype, offset 参数
import numpy as np
from os import listdir
from typing import Dict, NoReturn
S = 784
C = 28
def load_data(dir_path: str) -> Dict[str, np.ndarray]:
"""
加载图像和标签
load images and labels
:param dir_path: directory that contains training files and test files
"""
resource = {}
file_names = listdir(dir_path)
for file_name in file_names:
full_path = path.join(dir_path, file_name)
name, _ = path.splitext(file_name)
if "images" in name:
images = np.fromfile(full_path, dtype=np.uint8, offset=16)
resource[name] = images.reshape(images.size // S, S)
elif "labels" in name:
labels = np.fromfile(full_path, dtype=np.uint8, offset=8)
resource[name] = labels
return resource
显示
from PIL import Image
def display_images(resource, row: int, column: int,
interval: slice = slice(None, None, None)) -> NoReturn:
"""
将多个图像显示在一张图像上
:param row: 行数
:param column: 列数
:param interval: 图像区间
"""
images = Image.new("L", (column * C, row * C))
resource = resource["t10k-images"][interval]
for i in range(column):
for j in range(row):
index = i * row + j
array = resource[index].reshape(C, C)
img = Image.fromarray(array)
images.paste(img, (i * 28, j * 28))
images.show()
输出分析
显然,输出是10个数字中的一个,也就是预测结果。但是一个结果难以使用损失函数确定拟合程度,所以我们希望输出是一个长度为10的向量,包含了每个数字的预测概率。
通过普通的矩阵函数产生的输出不一定是概率(就是相加为1且没有负数),可以通过softmax() 矫正
s
o
f
t
m
a
x
(
e
i
)
=
e
i
∑
i
e
softmax(e_i)=\frac{e_i}{\sum_i{e}} \\
softmax(ei?)=∑i?eei??
def softmax(x: np.ndarray) -> np.ndarray:
t = np.exp(x - x.max())
y = np.sum(t, axis=1).reshape(t.shape[0], 1)
return t / y
由输出的概率和标签,通过交叉熵损失函数算出损失
def cross_entropy_error(result: np.ndarray, labels: np.ndarray) -> np.ndarray:
"""
交叉熵损失函数
:param result: 长度为10的向量,包含每个数字的预测概率
:param labels: 图像的标签,即图像的真实数字
"""
return -np.sum(np.log(result[np.arange(labels.size), labels] + 1e-7))
拟合函数建立
激活函数
由于计算过程中可能会出现一定程度的上溢或下溢,并且数字过大也会影响计算
可以使用值域在
(
0
,
1
)
(0,1)
(0,1)的sigmoid() ,同时它的导数也很好计算
s
i
g
m
o
i
d
(
x
)
=
1
1
+
e
?
z
.
s
i
g
m
o
i
d
(
x
)
∈
(
0
,
1
)
s
i
g
m
o
i
d
′
(
x
)
=
e
?
z
(
1
+
e
?
z
)
2
=
[
1
?
s
i
g
m
o
i
d
(
x
)
]
?
s
i
g
m
o
i
d
(
x
)
sigmoid(x) = \frac{1}{1+e^{-z}}. \\ sigmoid(x) \in (0,1) \\ sigmoid'(x) = \frac{e^{?z}}{(1+e^{?z})^2} = [1-sigmoid(x)] \cdot sigmoid(x)
sigmoid(x)=1+e?z1?.sigmoid(x)∈(0,1)sigmoid′(x)=(1+e?z)2e?z?=[1?sigmoid(x)]?sigmoid(x)
def sigmoid(x: np.ndarray) -> np.ndarray:
return 1 / (1 + np.exp(-x))
def derivative_sigmoid(x: np.ndarray) -> np.ndarray:
y = sigmoid(x)
return (1 - y) * y
拟合函数
只是演示代码,并不能一定运行
def predict(input_images: np.ndarray, layer_count: int, W: np.ndarray, B: np.ndarray) -> np.ndarray:
"""
获得预测结果的概率
obtain the probabilities of results (namely 0-9)
:param W: 矩阵数组
:param B: 偏置数组
:return: 预测结果概率
"""
layer = input_images
for i in range(layer_count):
layer = sigmoid(layer @ W[i]) + B[i]
layer = soft_max(layer)
return layer
input_images 就是待预测图像(
28
×
28
28 \times 28
28×28字节),layer_count 是神经网络层数,W 是权重也就是矩阵函数,B 是偏置
这样就完成了一次预测,当然最后的代码会复杂很多
正向计算梯度
只是演示代码,并不能一定运行
def numerical_gradient(input_images: np.ndarray,
w_grad: np.ndarray,
b_grad: np.ndarray) -> NoReturn:
"""
正向求梯度
forward gradient
:param step_length: 训练步长,学习率 learning rate
"""
h = 1e-5
for i in range(W.size):
for j in range(W[i].size):
t = W[i, j]
W[i, j] = t - h
f1 = loss(predict())
W[i, j] = t + h
f2 = loss(predict())
w_grad[i, j] = (f2 - f1) / (2 * h)
W[i, j] = t
for i in range(B.size):
for j in range(B[i].size):
t = B[i, j]
B[i, j] = t - h
f1 = loss(predict())
B[i, j] = t + h
f2 = loss(predict())
b_grad[i, j] = (f2 - f1) / (2 * h)
B[i, j] = t
w_grad 是权重的梯度,b_grad 是偏置的梯度,这样就用梯度下降法了
梯度下降
learning_rate = 0.02
W -= w_grad * learning_rate
B -= b_grad * learning_rate
完成一次梯度下降!
多次重复即可提高拟合度
由于正向求梯度,该方法非常缓慢,可能需要3-4天才能有80%-90%的准确率
下一篇会介绍反向求梯度,可以在十几分钟内达到90%的准确率
最终代码
feedforward-mnist
我也是第一次用github,其他不会用,只放了代码
|