前言
本文旨在对于机器语言完全零基础但较有兴趣或对神经网络较浅了解的朋友,通过阐述对神经网络的基础讲解以及Python的基本操作,来利用Python实现简单的神经网络;并以此为基础,在未来方向的几篇文章将以Python为工具,应用几种较为典型的神经网络以及如何对神经网络进行全方位的优化。本文涉及到数列的简单计算、函数以及类的定义、全连结神经网络的运算方式、损失函数、计算图以及随机梯度下降法。
Python基础
本章节主要阐述了Python的安装以及所需库的安装和介绍
Python的安装(Windows)
Python即本文所用的机器语言,在此用的是Python3.8,基于VSCode
Python官方解释器下载地址
打开官网,点击download,选择适合本操作系统及位数的软件,如Windows x86-64位操作系统,点击以下链接进行下载
Download Windows x86-64 executable installer
注:安装程序打开后,需要勾选下方的Add Python3.8 to PATH,然后再点击Install Now
如未勾选,后续文中介绍的库的添加将无法按操作进行。
Visual Studio Code的安装
Visual Studio Code官方网址
选择适合本操作系统及位数的软件进行下载安装
注:打开安装程序,点击下一步到 选择其他任务 页面,其他栏中的选项全部勾选
打开VScode,在右边点击 New File,命名为Demo.py
打开此文件后点击右下角提示框中的Install
所需库的安装及介绍
库即工具包,不像Matlab,在进行编译时,需要的大量数学函数运算,Python自身是不具备通用函数运算的,如exp(x)。我们不可能在神经网络编写时去创造这些函数,所以需要引入前人所编写完成的工具包。在本文中,我们需要引入的库为numpy库,这个库需要进行下载安装。
如果你的安装是按照以上安装过程进行的,那么以下库的安装方式是有效的
1.打开菜单–windows系统–命令提示符(或按住Win+R,输入cmd打开)
2.直接输入 pip install numpy
如果你连接的是大陆服务器,可能会持续出现Collecting numpy,此时可以按Ctrl+C停止。
此时我们通过清华大学的镜像源进行下载
需要输入 pip install numpy -i https://pypi.tuna.tsinghua.edu.cn/simple
安装完成后,在Demo.py中输入 import numpy ,点击右上角三角运行,没有报错视为成功。接下来将介绍本文中用到的numpy中的函数。
numpy.random.normal(loc,scale,size)
从高斯分布中抽取随机样本
loc : 函数分布中心
scale :为标准差
size :输出规模,None表示一个数;1输出一个列表,列表中有一个数;2表示 输出一个列表,列表中有两个数。
若括号中为空,则为默认值 loc=0.0, scale=1.0, size=None
高斯分布的更多内容将在卷积神经网络中进行详细阐述。
numpy.exp(x) 属于ufunc,即通用函数。它的返回值是e^x,本文中用于创建sigmoid函数
numpy.array(object, dtype=None, ·, copy=True, order=‘K’, subok=False, ndmin=0)
参数:
object:一个数组,任何物体露出阵列接口,一个对象,其__array__方法返回一个数组,或任何(嵌套)序列。
dtype:所需的数据类型为阵列。如果没有给出,则类型将被确定为持有的序列中的对象所要求的最低的类型。这种说法只能用来“向上转型”的阵列。对于向下转换,使用.astype(t)的方法。
copy:如果为true(默认值),那么对象被复制。否则,副本将仅当__array__返回副本,如果obj是一个嵌套序列,或者做出是否需要拷贝,以满足任何其他要求(DTYPE,订单等)。
order:指定数组的顺序。如果命令是’C’,那么阵列将在C-连续顺序(上次指数变化最快的)。如果命令是’F’,则返回的数组将是FORTRAN连续顺序(先指数变化最快的)。如果命令是’A’(默认),然后返回数组可以是任意顺序(无论是C-,Fortran的连续的,甚至是不连续的),除非需要一个副本,在这种情况下,这将是C-连续的。
subok:如果为True,则子类将被传递,通过,否则返回数组将被迫成为一个基类数组(默认)。
ndmin:指定结果数组应有尺寸的最小数目。的将根据需要来满足这一要求被预先挂起到的形状。
本文中此函数用来创建数组。
其他所需函数介绍
def函数用于自定义函数的函数,如`
def function_name(): #def+自定义的函数名+(),()中为函数所需参数
expressions
zip函数用于将对象中对应参数打包为元组,由于Python3优化内存,直接输出c会得到内存位置,所以我们需要用list列表形式将结果表现出来。如
a = (1,3)
b = (5,7)
c = zip(a,b)
print(list(c))
#结果:[(1, 5), (3, 7)]
range函数创建一个整数列表
a = range(0,5,2) #从0开始加2一直加到5-1为止的所有整数
print([x for x in a])
#结果:[0, 2, 4]
全连接神经网络基础
本章节从Python的实现角度阐述全连接神经网络的基本框架,以及实现这些框架的Python语句
正如我们的大脑一样,通过皮肤眼睛等感受器对外界刺激的感受,感受以神经递质的方式在神经元之间传递,神经元通过正负电荷来处理感受信息。
受到这种启发,计算机的处理器也是1、0信号,通过有无电流进行处理。所以神经网络必然离不开神经元。一般全连接神经网络分为输入层、隐藏层、输出层三结构。
图中的圆圈就是一个个神经元,又称为节点,他们负责接收并处理外界或上一节点传输的数据。我们要做的便是用Python解释器来编写这样一个网络。因此我们先要引入工具包numpy.
import numpy
激活函数主要使用了sigmoid函数(现在大部分都是用Relu函数,详细原因将在优化提及),所以我们要先对激活函数进行定义
def sigmoid (x):
return 1/(1+numpy.exp(-x))
文字解释:
定义一个函数名字叫sigmoid,需要一个变量(x)
输入数x后返回一个1/(1+e^(-x))的值
每个节点的运算方式都大同小异为sigmoid(WX+b),比如隐藏层第一个的h1便是X0×W1,h1f便是此神经元输出给下一层的数值。
注:关于权重W、偏置b以及激活函数的产生主要与线行分类器基础知识相关,本文是用Python实现全连接神经网络,在此不做过多理论解释,详细可以查看全连接神经网络基础部分。
神经元功能的定义
因此对于每一个节点来时,我们可以定义一个节点函数。
def feedforward(self,x):
h1 = x[0]*self.w1+x[1]*self.w2+self.b1
h1f = sigmoid(h1)
h2 = x[0]*self.w3+x[1]*self.w4+self.b2
h2f = sigmoid(h2)
o1 = h1*self.w5+h2*self.w6+self.b3
of = sigmoid(o1)
文字解释:
定义一个函数叫做 feedforward ,需要一个对象(本文值一个神经网络)和一个二维向量为自变量。(即在使用此函数时,需要给它一个神经网络和一组输入数据)
h1等于( X0 乘 所给网络的权重W1) 加 (X1乘所给网络权重W2 加偏置b1)
以此类推
这样我们就对神经网络的运算过程做了定义。接下来,我们需要解决网络的权重和偏置的问题。神经网络在初始化时会随机分配权重与偏置,我们不妨用高斯分布来取值。
神经网络的类定义
class nerualnetwo():
def __init__(self):
self.w1 = numpy.random.normal()
self.w2 = numpy.random.normal()
self.w3 = numpy.random.normal()
self.w4 = numpy.random.normal()
self.w5 = numpy.random.normal()
self.w6 = numpy.random.normal()
self.b1 = numpy.random.normal()
self.b2 = numpy.random.normal()
self.b3 = numpy.random.normal()
文字解释:
制作一个类叫做nerualnetwo
对这个类自身特有属性进行定义:
它的W1为高斯分布随机数值
以此类推
神经网络的高精准离不开大数据,在神经网络种,我们需要给定输入和输出作为训练集,让其进行训练,然后才能使用。那么训练的标准就是损失函数,要确定真实值与神经网络预测值之间的损失,通过调整权重和偏置以达到损失越来越小,知道符合期望为止。首先就要定义损失函数。
损失函数的定义
def mse_loss(y_tr,y_pre):
return((y_tr - y_pre)**2).mean()
文字解释:
定义一个函数 mse_loss,需要真实值和预测值做为参数
返回真实值减预测值平方的平均值
网络的训练
有了损失函数L,我们就可以对神经网络进行训练了,训练的目的就是为了减小损失。那么减小损失一半会通过增加或减少W和b,改变损失值,如果Wb的增加使得损失值增加,那么我们需要减少W和b。判断一个函数的增减性通常会使用导数来进行,导数为正,即有增加趋势。所以损失函数L(W1,W2,W3,W4,W5,W6,b1,b2,b3),我们需要对这九个值分别求偏导,才得知它的大小与正负。现在流行的训练方法是自适应法,本文中以最简单的梯度下降法来实现,权重和偏置通过一步步调整最终走到损失函数最小的点。涉及到梯度,必然要求导数,那么为了方便我们可以先把sigmoid函数的导数求出。
def der_sigmoid(x):
return sigmoid(x)*(1-sigmoid(x))
超参数: 学习率 迭代次数,需要自己设定,学习率与梯度的乘积为步长。即权重和偏置每向前一步,就不需要走学习率和当前梯度的乘积这么远。学习率太高会导致迈过最优点,学习率太低则走的太慢。
def train(self,data,all_y_tr):
epochs = 1000
learn_rate = 0.1
for i in range(epochs):
for x , y_tr in zip(data,all_y_tr):
valcell = self.feedforward(x)
y_pre = valcell[5]
der_L_y_pre = -2*(y_tr-y_pre)
der_y_pre_h1 = der_sigmoid(valcell[4])*self.w5
der_y_pre_h2 = der_sigmoid(valcell[4])*self.w6
der_h1_w1 = der_sigmoid(valcell[0])*x[0]
der_h1_w2 = der_sigmoid(valcell[0])*x[1]
der_h2_w3 = der_sigmoid(valcell[2])*x[0]
der_h2_w4 = der_sigmoid(valcell[2])*x[1]
der_y_pre_w5 = der_sigmoid(valcell[4])*valcell[1]
der_y_pre_w6 = der_sigmoid(valcell[4])*valcell[3]
der_y_pre_b3 = der_sigmoid(valcell[4])
der_h1_b1 = der_sigmoid(valcell[0])
der_h2_b2 = der_sigmoid(valcell[2])
#重新赋予权值和偏置
self.w1 -= learn_rate * der_L_y_pre * der_y_pre_h1 * der_h1_w1
self.w2 -= learn_rate * der_L_y_pre * der_y_pre_h1 * der_h1_w2
self.w3 -= learn_rate * der_L_y_pre * der_y_pre_h2 * der_h2_w3
self.w4 -= learn_rate * der_L_y_pre * der_y_pre_h2 * der_h2_w4
self.w5 -= learn_rate * der_L_y_pre * der_y_pre_w5
self.w6 -= learn_rate * der_L_y_pre * der_y_pre_w6
self.b1 -= learn_rate * der_L_y_pre * der_y_pre_h1 * der_h1_b1
self.b2 -= learn_rate * der_L_y_pre * der_y_pre_h2 * der_h2_b2
self.b3 -= learn_rate * der_L_y_pre *der_y_pre_b3
#每10步输出一次当前损失值
if i % 10 ==0 :
y_pred = numpy.apply_along_axis(self.simulate,1,data)
loss = mse_loss (all_y_tr , y_pred)
print(i,loss)
文字解释:
定义一个函数叫 train ,需要一个神经网络,输入数据以及对应的输出数据为参数
.设置循环次数为1000
.学习率为0.1
.将以下运算做一个1000次的循环
… …将输入数据与输出数据打包一一对应,然后分别赋值给x和y_tr
… …所有节点值组Valcell等于feedforward函数得出的结果
… …y_pre(y预测值)为valcell的第六个值
… …损失函数对y预测求偏导数
… …y_预测对h1求编导数
… …y_预测对h2求偏导数
… …h1对w1求偏导数
以此类推
最终通过链式求导法则可以得出损失函数L对W1求偏导的值…
训练之后我们就可以用预测函数来预测
def simulate (self,x):
h1 = x[0]*self.w1+x[1]*self.w2+self.b1
h1f = sigmoid(h1)
h2 = x[0]*self.w3+x[1]*self.w4+self.b2
h2f = sigmoid(h2)
o1 = h1*self.w5+h2*self.w6+self.b3
of = sigmoid(o1)
整体代码
由于权重 偏置、运算、训练以及拟合都是神经网络的基本特性,因此我们把这些函数统统放入刚才定义的nerualnetwo中。所有代码如下:
import numpy
def sigmoid (x):
return 1/(1+numpy.exp(-x))
def der_sigmoid(x):
return sigmoid(x)*(1-sigmoid(x))
def mse_loss(y_tr,y_pre):
return((y_tr - y_pre)**2).mean()
class nerualnetwo():
def __init__(self):
self.w1 = numpy.random.normal()
self.w2 = numpy.random.normal()
self.w3 = numpy.random.normal()
self.w4 = numpy.random.normal()
self.w5 = numpy.random.normal()
self.w6 = numpy.random.normal()
self.b1 = numpy.random.normal()
self.b2 = numpy.random.normal()
self.b3 = numpy.random.normal()
def feedforward(self,x):
h1 = x[0]*self.w1+x[1]*self.w2+self.b1
h1f = sigmoid(h1)
h2 = x[0]*self.w3+x[1]*self.w4+self.b2
h2f = sigmoid(h2)
o1 = h1*self.w5+h2*self.w6+self.b3
of = sigmoid(o1)
return h1,h1f,h2,h2f,o1,of
def simulate (self,x):
h1 = x[0]*self.w1+x[1]*self.w2+self.b1
h1f = sigmoid(h1)
h2 = x[0]*self.w3+x[1]*self.w4+self.b2
h2f = sigmoid(h2)
o1 = h1*self.w5+h2*self.w6+self.b3
of = sigmoid(o1)
return of
def train(self,data,all_y_tr):
epochs = 1000
learn_rate = 0.1
for i in range(epochs):
for x , y_tr in zip(data,all_y_tr):
valcell = self.feedforward(x)
y_pre = valcell[5]
der_L_y_pre = -2*(y_tr-y_pre)
der_y_pre_h1 = der_sigmoid(valcell[4])*self.w5
der_y_pre_h2 = der_sigmoid(valcell[4])*self.w6
der_h1_w1 = der_sigmoid(valcell[0])*x[0]
der_h1_w2 = der_sigmoid(valcell[0])*x[1]
der_h2_w3 = der_sigmoid(valcell[2])*x[0]
der_h2_w4 = der_sigmoid(valcell[2])*x[1]
der_y_pre_w5 = der_sigmoid(valcell[4])*valcell[1]
der_y_pre_w6 = der_sigmoid(valcell[4])*valcell[3]
der_y_pre_b3 = der_sigmoid(valcell[4])
der_h1_b1 = der_sigmoid(valcell[0])
der_h2_b2 = der_sigmoid(valcell[2])
self.w1 -= learn_rate * der_L_y_pre * der_y_pre_h1 * der_h1_w1
self.w2 -= learn_rate * der_L_y_pre * der_y_pre_h1 * der_h1_w2
self.w3 -= learn_rate * der_L_y_pre * der_y_pre_h2 * der_h2_w3
self.w4 -= learn_rate * der_L_y_pre * der_y_pre_h2 * der_h2_w4
self.w5 -= learn_rate * der_L_y_pre * der_y_pre_w5
self.w6 -= learn_rate * der_L_y_pre * der_y_pre_w6
self.b1 -= learn_rate * der_L_y_pre * der_y_pre_h1 * der_h1_b1
self.b2 -= learn_rate * der_L_y_pre * der_y_pre_h2 * der_h2_b2
self.b3 -= learn_rate * der_L_y_pre *der_y_pre_b3
if i % 10 ==0 :
y_pred = numpy.apply_along_axis(self.simulate,1,data)
loss = mse_loss (all_y_tr , y_pred)
print(i,loss)
网络定义完成后
进行训练与拟合操作
#训练网络
data = numpy.array([[-2, -1],[25, 6],[17, 4],[-15, -6]])
all_y_trues = numpy.array([1,0,0,1])
ner = nerualnetwo()
ner.train(data,all_y_trues)
#预测:结果为0.99左右
test = numpy.array([-7, -3])
print(ner.simulate(test))