问题提出
最近研究神经网络,看到了网上用python实现全连接网络的代码,其代码普遍版本如下:
但其中的( self.weights.append((2*np.random.random((layers[i-1]+1,layers[i]+1))-1)0.25)和self.weights.append((2np.random.random((layers[i]+1,layers[i+1]))-1)*0.25))两条语句我一直不能理解,最后在一步步推导后发现,网上流传的代码似乎是错的,虽然其也能获得较好的实验效果,但是并不严谨(就是错的)!!!下面我会通过我的理解进行证明一下,它的不严谨,以及修改成正确的代码(修改后就很容易理解)。
import numpy as np
#激活函数tanh
def tanh(x):
return np.tanh(x)
#tanh的导函数,为反向传播做准备
def tanh_deriv(x):
return 1-np.tanh(x)*np.tanh(x)
#激活函数逻辑斯底回归函数
def logistic(x):
return 1/(1+np.exp(-x))
#激活函数logistic导函数
def logistic_deriv(x):
return logistic(x)*(1-logistic(x))
#神经网络类
class NeuralNetwork:
def __init__(self,layers,activation='tanh'):
#根据激活函数不同,设置不同的激活函数和其导函数
if activation == 'logistic':
self.activation = logistic
self.activation_deriv = logistic_deriv
elif activation == 'tanh':
self.activation = tanh
self.activation_deriv = tanh_deriv
#初始化权重向量,从第一层开始初始化前一层和后一层的权重向量
self.weights = []
for i in range(1 , len(layers)-1):
#权重的shape,是当前层和前一层的节点数目加1组成的元组
self.weights.append((2*np.random.random((layers[i-1]+1,layers[i]+1))-1)*0.25)
#权重的shape,是当前层加1和后一层组成的元组
self.weights.append((2*np.random.random((layers[i]+1,layers[i+1]))-1)*0.25)
#fit函数对元素进行训练找出合适的权重,X表示输入向量,y表示样本标签,learning_rate表示学习率
#epochs表示循环训练次数
def fit(self , X , y , learning_rate=0.2 , epochs=10000):
X = np.atleast_2d(X)#保证X是二维矩阵
temp = np.ones([X.shape[0],X.shape[1]+1])
temp[:,0:-1] = X
X = temp #以上三步表示给X多加一列值为1
y = np.array(y)#将y转换成np中array的形式
#进行训练
for k in range(epochs):
i = np.random.randint(X.shape[0])#从0-epochs任意挑选一行
a = [X[i]]#将其转换为list
#前向传播
for l in range(len(self.weights)):
a.append(self.activation(np.dot(a[l],self.weights[l])))
#计算误差
error = y[i] - a[-1]
deltas = [error * self.activation_deriv(a[-1])]
#反向传播,不包括输出层
for l in range(len(a)-2,0,-1):
deltas.append(deltas[-1].dot(self.weights[l].T)*self.activation_deriv(a[l]))
deltas.reverse()
#更新权重
for i in range(len(self.weights)):
layer = np.atleast_2d(a[i])
delta = np.atleast_2d(deltas[i])
self.weights[i] += learning_rate*layer.T.dot(delta)
#进行预测
def predict(self,x):
x = np.array(x)
temp = np.ones(x.shape[0]+1)
temp[0:-1] = x
a = temp
for l in range(0,len(self.weights)):
a = self.activation(np.dot(a,self.weights[l]))
return a
if __name__ == '__main__':
nn = NeuralNetwork([2,2,1],'tanh')
X = np.array([[0,0],[0,1],[1,0],[1,1]])
y = np.array([0,1,1,0])
nn.fit(X,y)
for i in [[0,0],[0,1],[1,0],[1,1]]:
print (i,nn.predict(i))
问题解决
在说明问题前我假设你知道了关于神经网络的一些概念,比如权重,偏置,输入层,输出层。。。。以及对上面的代码有了一定的了解(注:我们默认输入层不算到网络层数中)
(1)我们对上面的代码进行中间权重输出,查看权重的shape.
上面是我对代码进行中间输出测试,输出结果如下:
从中可以看到,对于2层网络[2,2,1],其中间权重矩阵又两个,分别是3x3和3x1,从这里看来一切正常,似乎没有什么错误(你可能不太理解为什么是3x3而不是2x2,没关系,后面我们分析权重代码时祥细说明)
(2)我们修改下网络结构,将其改为3层网络为[2,3,2,1],结果显示如下(可以看到报错了,按道理来说是不应该的,下面的错误应该显示的是维度不一致无法进行计算):
3.我们修改下网络结构,将其改为3层网络为[2,2,3,1],结果显示如下:从下面结果可以看到中间的权重矩阵有4个,对于3层网络,权重矩阵只应该有3个,所以我开始怀疑是否代码就是错的,后面我试了4层网络,其权重矩阵有6个,越来越离谱!!!
(4)现在我们来分析下面的代码
#初始化权重向量,从第一层开始初始化前一层和后一层的权重向量
self.weights = []
for i in range(1 , len(layers)-1):
#权重的shape,是当前层和前一层的节点数目加1组成的元组
self.weights.append((2*np.random.random((layers[i-1]+1,layers[i]+1))-1)*0.25)
#权重的shape,是当前层加1和后一层组成的元组
self.weights.append((2*np.random.random((layers[i]+1,layers[i+1]))-1)*0.25)
这一部分是初始话权重,现在来解决刚才说的对于2层网络[2,2,1]为什么输入层与隐藏层的权重矩阵是3x3而不是2x2…如果你有留心的话,你会发现隐藏层的偏置并没有设置,那偏置是怎么来实现和计算的勒,就是你想的那样,3x3就是考虑上了偏置。我们来看下代码中的下面片段:
temp = np.ones([X.shape[0],X.shape[1]+1])
temp[:,0:-1] = X
X = temp #以上三步表示给X多加一列值为1
y = np.array(y)#将y转换成np中array的形式
从中可以看出,其给X多插了一列 并赋值均为1,那么在下面训练中,a[0]即输入层为1x3矩阵,那么经过3x3权重矩阵,就好像给隐藏层加了一个单元(神经单元),这个单元可以理解为偏置!!!由于最后输出层不需要加单元,所以出现self.weights.append((2*np.random.random((layers[i]+1,layers[i+1]))-1)*0.25)中的layers[i+1],这时,你是不是发现了什么?为什么网上的代码会出错?
a.append(self.activation(np.dot(a[l],self.weights[l])))
(5)有了上面的分析,以及采用网络上流传代码进行测试关于网络层数与权重矩阵个数相关的关系可以推出,self.weights.append((2*np.random.random((layers[i]+1,layers[i+1]))-1)*0.25)该语句是用于最后一个隐藏层与输出层之间的权重设置(要对上面代码稍微修改下即可)。因此正确的代码如下:
import numpy as np
def tanh(x):
return np.tanh(x)
def tanh_deriv(x):
return 1.0-np.tanh(x)*np.tanh(x)
def logistic(x):
return 1/(1+np.exp(-x))
def logistic_derivative(x):
return logistic(x)*(1-logistic(x))
class NeuralNetwork:
def __init__(self,layers,activation='tanh'):
#根据类实例化一个函数,_init_代表的是构造函数
#self相当于java中的this
"""
:param layers:一个列表,包含了每层神经网络中有几个神经元,至少有两层,输入层不算作
[, , ,]中每个值代表了每层的神经元个数
:param activation:激活函数可以使用tanh 和 logistics,不指明的情况下就是tanh函数
"""
if activation =='logistic':
self.activation = logistic
self.activation_deriv = logistic_derivative
elif activation =='tanh':
self.activation =tanh
self.activation_deriv=tanh_deriv
#初始化weights,
self.weights =[]
#len(layers)layer是一个list[10,10,3],则len(layer)=3
#除了输出层都要赋予一个随机产生的权重
for i in range(1,len(layers)-1):
#np.random.random为nunpy随机产生的数
#实际是以第二层开始,前后都连线赋予权重,权重位于[-0.25,0.25]之间
self.weights.append((2*np.random.random((layers[i-1]+1,layers[i]+1))-1)*0.25)
self.weights.append((2*np.random.random((layers[len(layers)-2]+1,layers[len(layers)-1]))-1)*0.25)
#定义一个方法,训练神经网络
def fit(self,X,y,learning_rate=0.2,epochs=10000):
#X:数据集,确认是二维,每行是一个实例,每个实例有一些特征值
X=np.atleast_2d(X)
#np.ones初始化一个矩阵,传入两个参数全是1
#X.shape返回的是一个list[行数,列数]
#X.shape[0]返回的是行,X.shape[1]+1:比X多1,对bias进行赋值为1
temp = np.ones([X.shape[0],X.shape[1]+1])
#“ :”取所有的行
#“0:-1”从第一列到倒数第二列,-1代表的是最后一列
temp[:,0:-1]=X
X=temp
#y:classlabel,函数的分类标记
y=np.array(y)
#K代表的是第几轮循环
print(len(self.weights))
#print(self.weights[0])
#print(self.weights[1])
for k in range(epochs):
#从0到X.shape[0]随机抽取一行做实例
i =np.random.randint(X.shape[0])
a=[X[i]]
#正向更新权重 ,len(self.weights)等于神经网络层数
for l in range(len(self.weights)):
#np.dot代表两参数的内积,x.dot(y) 等价于 np.dot(x,y)
#即a与weights内积,之后放入非线性转化function求下一层
#a输入层,append不断增长,完成所有正向的更新
a.append(self.activation(np.dot(a[l],self.weights[l])))
#计算错误率,y[i]真实标记 ,a[-1]预测的classlable
error=y[i]-a[-1]
#计算输出层的误差,根据最后一层当前神经元的值,反向更新
deltas =[error*self.activation_deriv(a[-1])]
#反向更新
#len(a)所有神经元的层数,不能算第一场和最后一层
#从最后一层到第0层,每次-1
for l in range(len(a)-2,0,-1):
#
deltas.append(deltas[-1].dot(self.weights[l].T)*self.activation_deriv(a[l]))
#reverse将deltas的层数跌倒过来
deltas.reverse()
for i in range(len(self.weights)):
#
layer = np.atleast_2d(a[i])
#delta代表的是权重更新
delta = np.atleast_2d(deltas[i])
#layer.T.dot(delta)误差和单元格的内积
self.weights[i]+=learning_rate*layer.T.dot(delta)
def predict(self,x):
x=np.array(x)
temp=np.ones(x.shape[0]+1)
#从0行到倒数第一行
temp[0:-1]=x
a=temp
for l in range(0,len(self.weights)):
a=self.activation(np.dot(a,self.weights[l]))
return a
nn=NeuralNetwork([2,3,3,4,1],'tanh')
x=np.array([[0,0],[0,1],[1,0],[1,1]])
y=np.array([0,1,1,0])
nn.fit(x,y)
for i in [[0,0],[0,1],[1,0],[1,1]]:
print(i,nn.predict(i))
上述我设置了4层网络,输出结果如下(输出的权重矩阵个数为4,正确!!!)
(6)后面的分析就自己理解吧,理解了为什么是3x3,相对后面就比较好分析了
感谢
要敢于质疑,做科研要严谨,当时我在网上查到的几乎都是一个版本,一直找不到合适的解释,希望上面的一些个人见解,能给大家带来点帮助。如果有错可以相互讨论,我也不是什么大牛,我会虚心接受大家的建议,一起成长!!!!