《深度学习的数学》神经网络的python实现
《深度学习的数学》4-4节使用了excel实现了一个小的0-1识别器。自己写了一个python版的实现。
需要指出的是:
1 大部分涉及到的矩阵都是行代表样本(64个),列代表神经元;权重矩阵的话列代表本层神经元,行代表上层神经元。这样权重矩阵可能会根excel表中的正好反着。后面算偏导数的时候通过矩阵乘法直接算出来最后的结果,不用向excel里面一样还得把64个样本计算出来的偏导数加起来。
2 计算z之前我把训练集和隐藏层a在前面多加了一个1(例如训练集里面一个图片是12列,我在最前头又加了一列1),把权重中的b也直接放进权重矩阵里,这样直接权重矩阵和输入矩阵相乘就直接算出来z了。原书是把b单独相加的。
3 我这里尝试使用交叉熵计算误差函数,效果和用最小二乘差不多。当然最小二乘的代码也在里面(只不过注释掉了)
4 所有的数都和excel表中的一样。那个flatten函数只不过是把raw_training_set这个直接从excel表中拷贝的训练集转换成numpy矩阵罢了
import numpy as np
import re
# 获取训练集
raw_training_set = '''
1 1 1 0 1 1 1 1 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 1 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0 0 1 0 1 1 0 0 1 0 0 1 0 0 1 0 1 1 0 1 1 0 1 1 0 0 1 0 0 1 0 1 1 0 1 1 0 0 1 0 0 1 0 0 1 0 1 1 0 1 1 0 0 1 1 1 1 0 1 1 0 1 1 0 1 1 0 0 1 0 1 0 0 1 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 1 0 0 1 0 0 1 0
1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 1 1 0 1 1 1 1 0 1 1 1 1 1 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0 0 1 0 1 1 0 1 1 0 1 0 0 1 1 0 1 1 0 0 1 0 1 1 0 1 1 0 1 0 0 1 1 0 0 1 0 1 1 0 1 1 0 1 0 0 1 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 0 0 1 1 0 1 0 0 1 1 1 1 0 0 1 0 1 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 0 0 0 1 0 0 1 0 0 1 0 0 1 0 0 0 1 1 0 0
1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 1 1 0 1 1 0 1 1 0 0 1 0 1 0 0 1 1 0 1 1 0 1 1 0 1 1 0 0 1 0 1 0 0 1 1 0 1 1 0 1 1 0 0 1 0 1 0 0 1 1 0 1 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 1 0 1 0 1 1 0 1 1 0 0 0 0 0 1 0 0 1 0 0 1 0 0 1 0 0 0 0 0 1 0 0 1 0 0 1 0 0 0 1 1 0 0
1 1 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 0 1 0 0 1 0 0 1 0 1 1 0 0 1 1 1 1 0 0 1 1 1 1 1 0 1 0 0 1 0 0 1 0 0 1 0 1 1 0 1 1 0 1 1 1 0 1 1 0 1 0 0 1 1 0 1 0 0 1 0 1 1 0 0 1 0 1 0 0 0 1 0 0 0 1 1 1 0 1 1 0 1 1 0 0 1 0 0 0 0 0 1 0 0 1 0
'''
# 获取训练集的标签,行代表样本,第一列代表是否为0,第二列代表是否为1
training_label = np.array([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
).T
def flatten(matches): # 把字符串转换成阵列
flag1 = 0 # 行标记
flag2 = 0 # 列标记
done_line = 0
i = 0
return_array = np.zeros((64, 12))
while(done_line < 12/3):
return_array[flag1, flag2] = int(matches[i])
i += 1
flag2 += 1
if (flag2 % 3 == 0):
flag2 = done_line*3
flag1 += 1
if (flag1 % 64 == 0):
done_line += 1
flag1 = 0
flag2 = done_line*3
return return_array
def sigmod(i): # 返回矩阵个元素的sigmod
return 1 / (1 + np.exp(-i))
batch = 1 # 训练次数
eta = 0.2 # η,学习率
C_old = 100 # 上次训练前的误差值
matches = re.findall(r'[10]', raw_training_set, re.MULTILINE)
training_set = flatten(matches)
training_set = np.insert(training_set, 0, np.ones(
64), axis=1) # 多加一列,待会直接乘权重矩阵时就不用单独加偏置了
# 最终获取了每个样本一行,64列的样本矩阵.样本矩阵的第一列是bias
# 隐藏层权重矩阵,i行j列代表上一层i神经元输入到本层j神经元.第一行是b,正好和训练集中添加的那一行相乘,方便计算.
wei_hid = np.array([[-0.185, 0.526, -1.169],
[0.49, 0.442, 0.654],
[0.348, -0.537, -1.389],
[0.073, 1.008, 1.246],
[0.837, 1.072, 0.057],
[-0.071, -0.733, -0.183],
[-3.617, 0.823, -0.743],
[-0.536, -0.453, -0.461],
[-0.023, -0.014, 0.331],
[-1.717, -0.027, 0.449],
[-1.456, -0.427, -1.296],
[-0.556, 1.876, 1.569],
[0.852, -2.305, -0.471]])
# 表示层矩阵,i行j列代表上一层i神经元输入到本层j神经元.第一行是b,正好和训练集中添加的那一行相乘,方便计算.
wei_out = np.array([[-1.438, -1.379],
[0.388, 0.025],
[0.803, -0.790],
[0.029, 1.553]])
# 开始训练
while True:
print("开始第" + str(batch) + "轮训练")
z_hid = training_set @ wei_hid # 获取隐藏层z,z是输入量的加权平均,行代表每个样本,列代表每个神经元
a_hid = sigmod(z_hid) # 获取隐藏层的a,a是sigmod(z),行代表每个样本,列代表每个神经元
a_dri_z_hid = a_hid * (1 - a_hid) # a'(隐藏层的z),行代表每个样本,列代表每个神经元
a_hid = np.insert(a_hid, 0, np.ones(64), axis=1) # 给隐藏层的a添加上bias列
z_out = a_hid @ wei_out
a_out = sigmod(z_out)
a_dri_z_out = a_out * (1 - a_out) # a'(输出层的z),行代表每个样本,列代表每个神经元
#C = a_out - training_label
#C = (C[:, 0] ** 2 + C[:, 1] ** 2) / 2 # 计算每一个样本对应的误差函数C的值
C = -1/training_set.shape[0]*(training_label * np.log(a_out) + (1 - training_label) * np.log(1 - a_out)) # 使用交叉熵计算每一个样本对应的误差函数C的值
print("该轮训练前的误差是"+str(C.sum()))
if abs(C.sum() - C_old) <= 0.00001:
print("训练结束\n")
break
C_old = C.sum()
partial_C_div_partial_a_out = 1 / \
training_set.shape[0]*(a_out - training_label)
delta_out = partial_C_div_partial_a_out * \
a_dri_z_out # 计算在每一个样本下的输出层神经元的δ,行代表样本,列代表神经元
# 计算隐藏层的神经元的δ,行代表样本,列代表神经元.wei_out需要先截取后三行然后转置(第一行用来处理bias)
delta_hid = (delta_out @ wei_out[1:, :].T) * a_dri_z_hid
# 计算反向传播之后误差函数C与输出层权重的偏导数(梯度分量),通过矩阵乘法把所有样本对应的偏导数直接相加了
partial_C_div_partial_w_out = a_hid.T @ delta_out
# 计算反向传播之后误差函数C与隐藏层权重的偏导数(梯度分量),通过矩阵乘法把所有样本对应的偏导数直接相加了
partial_C_div_partial_w_hid = training_set.T @ delta_hid
# 更新权重
wei_hid = wei_hid - eta * partial_C_div_partial_w_hid
wei_out = wei_out - eta * partial_C_div_partial_w_out
print("结束第"+str(batch)+"轮训练")
batch += 1
# 构建测试集
test_set = np.array(
[[0, 1, 1,
1, 0, 1,
1, 0, 1,
1, 0, 1],
[0, 1, 1,
0, 1, 0,
0, 1, 0,
0, 1, 0]])
print(test_set.reshape(2, 4, 3))
print("")
test_set = np.insert(test_set, 0, np.ones(
test_set.shape[0]), axis=1)
# 对测试集进行推理
test_label = np.rint(sigmod(np.insert(sigmod(test_set @ wei_hid),
0, np.ones((test_set @ wei_hid).shape[0]), axis=1) @ wei_out))
# 输出推理结果
print(test_label)