该图为本篇文章中出现的所有符号的参考,来源:周志华《机器学习》
神经网络的原理不再赘述,下面只讲按照公式怎么使用python实现。
首先考虑正向传播,就是算b和y结点的值。
对于权重的命名规则,如v_1h,表示从1指向h结点的线,w同理,注意,这两个权重位于不同层之间,由于使用矩阵乘法进行计算,需要先确定各个层的尺寸
d:输入层的结点数,q:隐藏层的结点数,l:输出层的结点数
对v:如x1->b1:v11,x1->b2:v12,也就是说,其大小为(d,q)
对w:类似上面的道理,大小为(q,l)
确定了各个层的尺寸后开始考虑如何计算:
输入层到隐藏层:
输入层的尺寸为(1,d),那么计算其结点的和则是,(1,d)与(d,q) ->(1,q),即可得到隐藏层的结点值,代码表示则是:
b=np.dot(x,v)
隐藏层到输出层:
类似的,w:(q,l),b:(1,q),因此(1,q)(q,l),则可得到(1,l),代码表示为:
y=np.dot(b,w)
至此,正向传播的计算结束,下面是反向传播,即更新权重。
书上的推导使用链式法则,计算预测的标签与目标标签的差值,将这个差值与各个权重求偏导得到权重更新的公式,这里不做推导,会先把公式列出,然后对公式进行矩阵乘法可能性的探讨。
首先是对于输出层->隐藏层而言的,一个用于计算的中间变量:
y_j表示数据的标签,y'_j表示预测的标签,从最开始的图推测,j的取值应该是[1,l],即对应输出层的各个结点。
此时对于标签y与预测的y'的结果的尺寸均为(1,l),要实现上面的式子暂时没有想到好的做法,因为你如果尝试转置一下的话就会得到(1,1),在摸索了一段时间后发现貌似python中的*法运算可以直接得到结果,因此代码实现为:
g = (self.y * (1 - self.y)) * (y - self.y)
接下来是对隐藏层->输入层而言的,同样是一个中间变量:
前一部分就如上面的操作一样直接乘就可以了,那么后面的求和,先进行分析,对于w:(q,l),g:(1,l),各位可以在草稿纸上画一下就知道了,是可行的,最后的尺寸则是(1,q),代码表示为:
np.dot(g,w.T)
就是(1,l)(l,q),在草稿纸上试一下就发现就是上面求和式子中的表示,现在就得到了两个均为(1,q)的矩阵,接下来直接使用python的*即可,因此代码表示为:
e = (self.b * (1 - self.b)) * (np.dot(g,self.w.T))
计算完这两个中间变量之后就可以开始更新权重了,具体而言各个公式如下:
其中\eta表示学习率,可以理解成速度,\gamma全文没出现过,我的猜测是隐藏层的阈值,其余字母的定义与上文提到的定义一致。
按照上面的式子一一讲述:
1.dw:w:(q,l), g:(1,l), b:(1,q) -> b.T dot g ,演算后正确,代码表示为:
dw = self.lr * np.dot(self.b.T,g)
2.dtheta:直接乘,略
3.dv: v:(d,q), e:(1,q), x:(1,d) -> x.T dot e,代码表示为:
dv = self.lr * np.dot(x.T,e)
4.dgamma:直接乘,略
然后直接修改就可以了,然而在我兴致勃勃的将上面的实现尝试操作时,发现在运行之后的准确率只有3%,比盲猜还要低,我的数据是识别手写的字母,因此是1/26的概率,可以说还是比盲猜要低,具体原因不知道,代码如下所示,希望有大佬可以指点我:
import numpy as np
import random as rd
import pandas as pd
def sigmoid(x):
return 1 / (1 + np.exp(-x))
class MP:
def __init__(self):
#nodes for each layer
self.input_size = 100
self.hide_size = 50
self.output_size = 26
#weight,v:in->hid, w:hid->out
self.v = np.random.randn(self.input_size, self.hide_size)
self.w = np.random.randn(self.hide_size, self.output_size)
#bias
self.theta_v = np.random.randn(1,self.hide_size)
self.theta_w = np.random.randn(1,self.output_size)
#learning rate
self.lr = 0.1
def predict(self,x):
self.b = sigmoid(np.dot(x,self.v) - self.theta_v)
self.y = sigmoid(np.dot(self.b,self.w) - self.theta_w)
def train(self,x,y):
#get b and y
self.predict(x)
y = np.array(y).reshape(1,self.output_size)
#print the err of the epoch
print(np.sum(y - self.y))
#g,e
g = (self.y * (1 - self.y)) * (y - self.y)
e = (self.b * (1 - self.b)) * (np.dot(g,self.w.T))
g = np.array(g).reshape(1,self.output_size)
self.b = np.array(self.b).reshape(1,self.hide_size)
e = np.array(e).reshape(1,self.hide_size)
x = np.array(x).reshape(1,self.input_size)
#update w and v
dw = self.lr * np.dot(self.b.T,g)
theta_w = -self.lr * g
dv = self.lr * np.dot(x.T,e)
theta_v = -self.lr * e
self.w += dw
self.theta_w += theta_w
self.v += dv
self.theta_v += theta_v
#读取数据
data = pd.read_csv("all_alphas.csv")
data = data[data.iloc[:, -1].isin(["A", "B", "C", "D", "E", "F", "G", "H", "I", "J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X", "Y", "Z"])]
y = data['label']
#将标签转换为one-hot编码
y = pd.get_dummies(y)
x = data.drop(['label'],axis=1)
model = MP()
for i in range(len(x)):
model.train(x.iloc[i,:],y.iloc[i,:])
#查看准确率
count = 0
for i in range(len(x)):
model.predict(x.iloc[i,:])
if np.argmax(model.y) == np.argmax(y.iloc[i,:]):
count += 1
print(count/len(x))