参照鱼书第四章写了一遍前向传播版的梯度下降训练神经网络权重的算法(不用ai就是收获颇丰啊)

以下贴出代码

发现先前的图片好像寄了所以代码直接传代码形式算了

针对二层(输入-单个隐藏层-输出层)网络写的求梯度方法

相当于造了遍轮子,不过造轮子确实学东西啊,而且不用ai了感觉写起来其实好麻烦

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
import sys,os
sys.path.append(os.pardir)
import numpy as numpy
from common.functions import *
from common.gradient import numerical_gradient

"""
二层训练网络所需内容:
Params
1、每一层之间的权重与偏置的存放
Grads
2、每一次之间的权重与偏置的梯度的计算与存储

人为输入:每一层的大小,一个人为设定权重初始化的标准差以减小权重分布
问ai学到,不乘这个标准差,直接获得权重分布的方法叫 朴素随机初始化
如果乘了这个人为固定的标准差,则叫 缩放初始化
除此之外还有较为高级的初始化方法(对weight_init_std进行函数处理)
如针对sigmoid/tanh函数的Xavier/Glorot初始化(理论最优)
或者针对ReLU激活函数的He初始化

类的编写思路:
1、初始化出权重和偏置
2、写正常的预测网络
3、通过预测网络求交叉熵(损失函数)
4、将交叉熵函数传进梯度计算
5、返回梯度
6、额外可加个计算预测的准确率的(不过有损失函数在了也可反应,加上了给人看更好)
"""
class two_layer_net:
def __init__(self,input_size,hide_size,out_size,weight_init_std = 0.01):
self.params = {}
self.params["w1"] = np.random.randn(input_size,hide_size)*weight_init_std
self.params["b1"] = np.zeros(hide_size)
self.params["w2"] = weight_init_std * np.random.randn(hide_size,out_size)
self.params["b2"] = np.zeros(out_size)

#预测网络只需要初始数据,权重,偏置即可,后二者类自带,则只需考虑初始数据,计算出预测值返回即可
def prediction(self,x):
#先取出来权重偏置方便使用
w1,w2 = self.params["w1"], self.params["w2"]
b1,b2 = self.params["b1"], self.params["b2"]
#第一层计算
y1 = np.dot(x,w1) + b1
z1 = softmax(y1)
#第二层计算
y2 = np.dot(z1,w2) + b2
z2 = softmax(y2)
return z2

#计算损失函数,需要初始数据x和监测数据t,返回交叉熵误差
def loss(self,x,t):
y = self.prediction(x)
L = cross_entropy_error(y,t) #计算交叉熵,原理随明白但也要需要抽空细读实现
return L

#梯度计算,需要交叉熵函数,x,t
def gradient_caculation(self,x,t):
#搞个临时函数来方便传入格式与函数要求相同
tmp_f = lambda w: self.loss(x,t)
#梯度计算
grads = {} #不能理解python都如此简化了,这里还需要初始化一下
grads["w1"] = numerical_gradient(tmp_f,self.params["w1"])
grads["w2"] = numerical_gradient(tmp_f,self.params["w2"])
grads["b1"] = numerical_gradient(tmp_f,self.params["b1"])
grads["b2"] = numerical_gradient(tmp_f,self.params["b2"])

return grads

#这里直接粘了教材示例,仅作理解,用到了argmax,取了第1维度,即按列搜索或者跨行搜索,返回每一行最大数的索引
def accuracy(self, x, t):
y = self.prediction(x)
y = np.argmax(y, axis=1) #取了预测值,每行最大值对应的索引即预测的值
t = np.argmax(t, axis=1) #监测值,one-hot数据下应当正确的那个值为1,故一定是那一行最大
accuracy = np.sum(y == t) / float(x.shape[0])
return accuracy

针对上述工具和前向传播+梯度下降思路写的mnist的mini_batch训练函数

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
import numpy as np
import sys,os
from two_layer_net import two_layer_net
import matplotlib.pyplot as plt
sys.path.append(os.pardir)
from dataset.mnist import load_mnist

(x_train,t_train),(x_test,t_test) = load_mnist(normalize=True,one_hot_label=True) #取数据

train_lost_list = [] #监测每轮学习的损失率

# 超参数
iters_num = 10000 #次数
train_size = x_train.shape[0]
batch_size = 100 #批数量,每次训练的数据量
learning_rate = 0.1 #学习率

net = two_layer_net(input_size=784,hide_size=50,out_size=10)

for i_time in range(iters_num):

#mini_batch取出单次循环的数据,batch_mask存的是下标,传给一个数组可以取出下标内容,太高级了
batch_mask = np.random.choice(train_size,batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]

#传给梯度计算函数计算梯度
grad = net.gradient_caculation(x_batch,t_batch)

#应用梯度下降
for key in ["b1","b2","w1","w2"]:
net.params[key] -= grad[key]*learning_rate

#记录损失函数值
error = net.loss(x_batch,t_batch)
train_lost_list.append(error)

print("训练次数:",i_time)



#打印出损失函数变化
xx = np.arange(0,np.size(train_lost_list))
plt.plot(xx,train_lost_list)
plt.show()

评价这代码也就仅供学习用,我真试了跑了一遍训练,结果半天才训练一次,搞得我一度怀疑是自己写的有问题,询问ai后才知道基础的方法就这吊样,算的老多了,它说反向传播的梯度算法能比正向的快40000倍,这下来很期待了bushi,所以说真正训练看效果什么的还是等学完反向传播算法吧

由于没有系统学习python语法,直接来学的鱼书,因而对一些python特性不很熟悉,在实践过程中确实学到了许多有用的小技巧,比如字典这种东西就很舒服,比枚举好用很多,以及batch那里直接用下标取数据属实是把我这种就接触过C和一点点CPP的原始人看呆了,不得不说python真爽(你说能不能以后python写代码然后让ai给转成cpp)

PS:感觉读这种示例代码超多的书适合先把示例读一遍,然后不看示例复现一遍,先拿注释过思路也很舒服。