本文仅通过numpy,演示神经网络的计算方法,其实神经网络的计算相当于完成线性方程的求解,
当然,这个求解过程,不是线性方程的求解方法,而是通机器学习的神经网络方法进行求解,
本文的目的仅仅只是讲解神经网络的计算方法与流程,
离完善的代码还有很大的距离,你也可以使用如tensorflow等框架进行使用
# -*- coding:utf-8 -*-
import numpy as np
'''
双曲正切函数(tanh) 与 双曲函数,在神经网络运行过程要使用当,当然,这样的函数组不只这组,还有其他的,
我们这里不研究数理,只是为了把神经网络计算的流程交待清楚,所以就不多做解释数理方面的东西,只要知道这在计算loss是用到就是
'''
def tanh(x):
return np.tanh(x)
def tanh_deriv(x):
return 1.0 - np.tanh(x)*np.tanh(x)
class npNN():
def __init__(self,w,b,layers):
'''
w 为线性方程上各维度的系数,
b为常数
即 y= a0 * x0 + a1 * x1 + a2 * x2 + a3 * x3 + b 其中w=[a0,a1,a2,a3]
layers,是指神经网络的中间层,特别强调,是中间层,如果为空则表示没有中间层
因为第一层即是w传参,最后一层的维度即是y值
下面的第一行就将对层数进行调整,形成完整的神经网络
'''
layers=[len(w)]+layers+[1] #补全神经网络层
w=(w if isinstance(w,np.ndarray) else np.array(w)).astype(np.float32)
#构建一100条数据,做为训练数据
x_data=np.random.random([100,len(w)]).astype(np.float32)
y_data=np.matmul(x_data,w)+b
#print(x_data[0]) #[ 0.00964573 , 0.17939466, 0.73853254 , 0.17505005]
#print(y_data[0]) #0.828423 即实现了线性方程 y = 0.00964573*0.1+ 0.17939466*0.2 + 0.73853254 *0.3+0.17505005*0.4+0.5
#------------------------------------------------开始:神经线(weights)的构建
weights=[]
for i in range(1,len(layers)-1):
#加入每两层间,神经节点连线的数量(神经线),注意在这句layers[i-1]+1,layers[i]+1,都有加1,这个1是在每层的维度上要加上一个biase
weights.append((np.random.random([layers[i-1]+1,layers[i]+1])-0.5)*0.5)
i=len(layers)-2
#加入指向倒数二层与与最后一层神经节点间,神经节点连线的数量(神经线),layers[i]+1,layers[i+1],这里倒数2层有加1,是biase,最后一层即y值,没有biase
weights.append((np.random.random([layers[i]+1,layers[i+1]])-0.5)*0.5)
#weights中的-0.5)*0.5,目的是为了产生的weights值在-0.25至0.25之间,数理问题不多说,这个例子只是为了演示过程
#简单的说,太大的数在双曲函数那算得会有问题
#np.random.random的目的是随机初始化weights,这里的目的只是给个初始值
#这和最终计算的没有任何关系,会在机器学习的过程中将不断的调整这些weights,
print('因为加上头尾共%d层,所以len(weights) = %d层 - 1 = %d'%(len(layers),len(layers),len(weights)))
for weight in enumerate(weights[:-1]):
print('神经第 %d 层的维度为 %s,其中%s为当前层的神经节点数,%s为下一层的神经节点数'%(weight[0]+1,np.array(weight[1]).shape,len(weight[1]),len(weight[1][0])))
print('神经第 %d 层的维度为 %s,其中%s为当前层的神经节点数,%s为最后一层的神经节点数'%(len(weights),np.array(weights[len(weights)-1]).shape,len(weights[len(weights)-1]),1))
'''
以下图示画出某两层之间的计算变化示例,,如某层共4个神经节点[a1,a2,a3,b1] 下一层为2个神经节点[c1,b2]
则这两层之间的weight维度即为[4,2] 用矩阵相乘表示
[w1,w2]
[a1,a2,a3,b1] * [w3,w4] = [a1*w1+a2*w3+a3+w5+b1*w7 , a1*w2+a2*w4+a3+w6+b1*w8] = [c1,b2]
[w5,w6]
[w7,w8]
倒数二层与最后一层也是一样的,只是最一层没有b,固定为一个值,weight维度即为[n,1],n由倒数二层决定
'''
print('-'*20+'完成神经线(weights)的构建'+'-'*20)
#------------------------------------------------结束:神经线(weights)的构建
#------------------------------------------------开始:神经线(weights)的计算
tmp=np.ones([x_data.shape[0],x_data.shape[1]+1])
tmp[:,:-1]=x_data#这几行构建一个用新数组,行数与x_data行数相同,列数比x_data列数加1,加的这个1即为biase 多出来的那列为值初设为1
x=tmp
for runTimes in range(1000): #通过不断的循环,调整weights 主要包含正向的计算,逆向的修正两个步骤来完成
index = np.random.randint(len(x_data))#在x中取出任意一条
a=[x[index]] #放一个空数组中,赋值给a, 注意,在x[index] 外面有一层[],目的在于a后面会不断的append每层的神经计算结果
#------------------------------开始:通过从 第1层 到 最后一层 的 循环,计算出所有神经元的值
for k in range(len(weights)):
a.append(tanh(np.dot(a[k], weights[k]))) #计算的每层的的神经元的值np.dot(a[k], weights[l]) ,进行tanh转换后不断加入a数组
#循环结束后,最后一层就是计算出来的y值
error = y_data[index] - a[-1]
deltas = [error *tanh_deriv(a[-1])] # 这两行计算出最后一层,即y层的误差,注意deltas是一个数组,外面有一层[],在后面会不断的append每层的误差
#------------------------------结束:通过从 第1层 到 最后一层 的 循环,计算出所有神经元的值
#------------------------------开始:逆向计算,调整所有的weights
#逆向计算,从倒数二层开始,
for l in range(len(a) - 2, 0, -1): # 从倒数二层开始
#用下一层的误差 * 当前 神经线的置换矩阵() * tanh_deriv(a[l])
deltas.append(deltas[-1].dot(weights[l].T)*tanh_deriv(a[l]))
deltas.reverse() #倒序deltas数组,使得各元素也是从第一层开始
for i in range(len(weights)):
layer = np.atleast_2d(a[i])
delta = np.atleast_2d(deltas[i])
weights[i] += 0.2 * layer.T.dot(delta) #0.2学习效率,应该是一个小于0.5的数,
#------------------------------结束:逆向计算,调整所有的weights
self.weights=weights
def predict(self,x):
#预测的过程,其实也就是上面的流程中,正向的走一次,计算出y值
x = np.array(x)
temp = np.ones(x.shape[0]+1)
temp[0:-1] = x
for l in range(0, len(self.weights)):
temp = tanh(np.dot(temp, self.weights[l]))
return temp
print('测试一:设置中间三层,每层的神经元为3个,2个,1个,特别注意,第一层及中间层都会加上一个biase')
'''
神经元图案如下
@(a0) @ @ @ @(y)
@(a1) @ @ @
@(a2) @ @
@(a3) @
@(b)
'''
test=npNN([0.01,0.02,0.03,0.04],0.05,[3,2,1])
#print(a.weights)
print(test.predict([1,1,1,1])) #实际值的结果应为0.01*1 +0.02*1 +0.03*1+0.04*1+0.05 =0.15
print('\r\n测试二:设置无中层')
'''
神经元图案如下:计算测试二的目地,因为没有中间层,最后出来的wights共5个元素,也就是等于 [0.01,0.02,0.03,0.04],0.05
@(a0)
@(a1)
@(a2) @(y)
@(a3)
@(b)
'''
test=npNN([0.01,0.02,0.03,0.04],0.05,[])
print(test.weights) #无中间层时,显示weigths,就和传参相当的接近了
print(test.predict([1,1,1,1])) #实际值的结果应为0.01*1 +0.02*1 +0.03*1+0.04*1+0.05 =0.15其实
因为本文的主要目的是说明神经网络的计算流程,因此在这篇文章中的一些定义上并不相当正确,如biase,在这段代码中weights所代表的神经线,并没有定义相对应的biases,本代码中的biases并不是真正意义上的biases
同时,中间的很多变量我也交待的不是很清晰,如学习率(0.2)及weights初始值等问题,这些问题可以看我后面的文章。