【零基础】从零开始学神经网络《python神经网络编程》——手写数字识别实战

文章目录

  • 前言
  • 一、机器学习是什么,深度学习是什么?
  • 二、对NN,CNN,RNN,GNN,GAN的名词解释
  • 三、详细介绍神经网络(NN)
    • 1.认识神经网络
    • 2.神经元
    • 3.激活函数
    • 4.权重——连接的线,连接结构
    • 5.正向传播
    • 6. 反向训练权重
  • 四、csv文件
  • 五、创建一个神经网络
  • 六、训练
  • 七、测试
  • 八、计算正确率


前言

minist手写数据集

随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。


一、机器学习是什么,深度学习是什么?

机器学习分为监督学习,无监督学习和强化学习。

  • 监督学习相当于教小孩做题并告诉答案是什么,久而久之,AI会自己做题了,这种方法被用于各种识别任务,如人脸识别,车牌识别,肿瘤诊断;
  • 无监督学习就只有题目而没有答案了,AI只能根据自己观察到的特征,把认为相似的东西分为一组,虽然不知道谁是猫,谁是狗,但也能把它们区分出来,这种方法很适合从一堆东西中找到隐藏规律,就像对一堆颜色各异的积木,可以按形状分类,也可以按颜色分类,这一点比监督学习的用途要广(自发找特征);
  • 强化学习,这时AI不用做题,而是直接和环境互动,通过环境给出的奖惩来学习,目的是通过一系列动作获得最大的奖励,在互动的过程中,AI会不断调整自己的行为,对环境变化做出最佳的应对,这种方法常被用来训练行为,比如玩游戏,无人驾驶,推送广告等。

这三种方法经常被综合使用,让AI学会各种酷炫技能,比如最近兴起的一种训练方法GAN(生成对抗网络),它将两个AI放在一起,一个负责创作,一个负责找茬,通过来回对抗,最终训练一个创作达人,它可以无中生有,画出一张逼真的人脸,还能以假乱真搞艺术创作,画油画,写诗,作曲,都骗过了不少人,可以看到机器学习让AI越来越强大。
而深度学习就是监督学习。

二、对NN,CNN,RNN,GNN,GAN的名词解释

NN: Neural Network(神经网络)
CNN: Convolutional Neural Networks)(卷积神经网络)
RNN: Recurrent Neural Networks (循环神经网路)
GNN:Graph Neural Network(图神经网络)
GAN:Generative Adversarial Network(生成对抗网络)

CNN使得NN从二维转化到三维,加速了训练
RNN引入了时间的概念,比方说,“我今天去打篮球”,已知“我”,推测后面一个词是“今天”,一般用来NLP(自然语言处理)。
GAN将两个AI放在一起,一个负责创作,一个负责找茬,通过来回对抗,最终训练一个创作达人,它可以无中生有,画出一张逼真的人脸

三、详细介绍神经网络(NN)

1.认识神经网络

首先我们打开下面这个链接
神经网络模型
【零基础】从零开始学神经网络《python神经网络编程》——手写数字识别实战_第1张图片
将num_neurons:1 改成1 后,点击simple data出现
【零基础】从零开始学神经网络《python神经网络编程》——手写数字识别实战_第2张图片
把一个神经元比作切一刀,就相当于用一刀来切割图片的绿色和红色,而都改成2时,就会切两刀,会较好的分出红和绿的区分,如下:
【零基础】从零开始学神经网络《python神经网络编程》——手写数字识别实战_第3张图片

  • 神经网络要做的事就是把红和绿给区分开,如果此时再出现一个点,我们不知道它是什么颜色,当它落入绿色的区域中,判定为绿色,当它落入红色的区域中,则判定为红色。
  • 就像你接触一个人,先看看它的朋友是个怎样的人,我们把这种算法叫做K临近算法。

2.神经元

现在我们把目光转向生物的神经元,神经元是怎样工作的呢,它接受一个电输入,输出一个电信号,看起来与机器一摸一样,这些机器也是接收一个输入,进行一些处理,然后弹出一个输出。
【零基础】从零开始学神经网络《python神经网络编程》——手写数字识别实战_第4张图片
也许它的输出可能采取这样一种形式:输出 = (常数*输入)+(也许另一个常数)。
【零基础】从零开始学神经网络《python神经网络编程》——手写数字识别实战_第5张图片

而另一方面,动物大脑表面上看起来以慢得多的节奏运行,却似乎以并行方式处理信号,模糊性是其计算的一种特征。

3.激活函数

但观察表明,神经元不会立即反应,而是会抑制输入,直到输入增强,就像直到水装满了杯子,才可能溢出——神经元不希望传递微小的噪音信号,而只是传递有意识的明显信号。
【零基础】从零开始学神经网络《python神经网络编程》——手写数字识别实战_第6张图片
【零基础】从零开始学神经网络《python神经网络编程》——手写数字识别实战_第7张图片

s(sigmod)阈值函数:	y = 1/1+e^-x
横坐标相当于是阈值,只有输入超过了阈值,才能产生输出信号。
总和输入值 x = a+b+c;
输出y  s阈值函数 y = y(x)

有趣的是,如果只有其中一个输入大,其他输入小也能激发神经元,更重要的是,如果其中单个一般大,组合足够大,超过阈值,那么也能激发。即这些神经元也可以进行一些相对复杂,在某种意义上有点模糊的计算。

【零基础】从零开始学神经网络《python神经网络编程》——手写数字识别实战_第8张图片

4.权重——连接的线,连接结构

连接是神经元中最重要的东西。每一个连接上都有一个权重。
相当于两个神经元节点间的连接强度。

总和输入值x
x =a1*w1+a2*w2+a3*w3
S阈值输出
y = y(x)  #y = 1/1+e^-x

如果是两个输入节点。我们可以这样看

x = (第一个节点的输出*链接权重)+(第二个节点的输出*链接权重)
于是我们可以将它看作两个矩阵相乘
(w1 w2)*(a1    = w1*a1 + w2*a2
           a2)
 以此类推,如果是两个输出
 (w1,1 w2,1 * (a1  = (a1*w1,1 + a2*w2,1 
 w1,2  w2,2)   a2)    a1*w1,2 + a2*w2,2)
 这样我们无需花费太多精力就可以实现神经网络了。

【零基础】从零开始学神经网络《python神经网络编程》——手写数字识别实战_第9张图片
连接结构,我们把前后层每一个神经元与其他神经元互相连接,我们不采用创造性的方式将神经元连接起来,原来有两个:第一个是这种完全连接形式可以相对容易地编码成计算机指令,第二是神经网络的学习过程将会弱化这些实际上不需要的连接(也就是这些连接的权重将趋近于0)。零权重意味着这个链接实际上是断开了。
【零基础】从零开始学神经网络《python神经网络编程》——手写数字识别实战_第10张图片
我们把中间层称为隐藏层,其实并没有多大高深的含义。

权重是怎么得来的呢,我们先随机初始化这些数字,由于大的初始权重会造成大的信
号传递给激活函数,导致网络饱和,因此我们应该避免大的初始权重值,我们可以
从-1.0~+1.0之间随机均匀的选择。
数学家得到的经验法则是:从均值为0,标准方差等于节点传入链接数量平方根倒数
的正太分布中进行采样。(使累加的权重范围变小)
0权重和相同的权重是要避免的。
(np.random.rand(self.hnodes, self.inodes)-0.5#函数只能生成[0,1]的浮点数,于是 -0.5使范围变成[-0.5,0.5],(self.hnodes, self.inodes)为矩阵行,列
(np.random.normal(0.0,pow(self.hnodes,-0.5),(self.hnodes, self.inodes)#均值为0,标准方差等于节点传入链接数量平方根倒数的正太分布中进行采样
lambda 匿名函数
<函数名> = lambda<参数><表达式>
f = lambda x,y,z :x+y+z

至此我们可以写出的神经网络对象

class neturalNetwork :
    # initialise the neural network
    def __init__(self,inputnodes,hiddennodes,outputnodes,learningrate ):
        # set numbers of nodes in each input ,hidden,output layer
        self.inodes = inputnodes
        self.hnodes = hiddennodes
        self.onodes = outputnodes

        # learning rate
        self.lr =learningrate

        # link weight matrices wih,who
        # weights inside the arrays are w_i_j,where link is from node i to node j in the next layer
        # w12 w21
        # w12 w22 etc

        # self.wih = (np.random.rand(self.hnodes, self.inodes) - 0.5)#随机初始化权重
        # self.who = (np.random.rand(self.onodes, self.hnodes) - 0.5)

        self.wih = (np.random.normal(0.0,pow(self.hnodes,-0.5),(self.hnodes, self.inodes)) )#较复杂的初始化权重
        self.who = (np.random.normal(0.0,pow(self.onodes,-0.5),(self.onodes, self.hnodes)) )

        # activation function is the sigmoid function
        self.activation_function = lambda x: scipy.special.expit(x)#激活函数sigmod #使抑制输入 像生物一样

        pass

5.正向传播

根据以上,我们以3个输入层,3个隐藏层,3个输出层举例,

第一层 到 第二层
hidden_inputs = Wih * inputs#Wih 为input层到hidden层的权重矩阵 
hidden_outputs = sigmod(hidden_inputs) #sigmod函数 加上阈值
第二层 到 第三层
final_inputs = Who * hidden_outputs#Who 为hidden层到output层的权重矩阵 
final_outputs = sigmod(final_inputs)

由于输入的对象是以列的形式,所以我们要对输入的列表进行转置,并且矩阵拥有行和列,所以是2维的

inputs = np.array(input_list,ndmin=2).T#ndmin为定义数组最小维度 .T为转置

第一阶段,就是计算输出,所以我们正向查询的代码为

    def query(self,input_list):
        # convert inputs list to 2d array
        inputs = np.array(input_list,ndmin=2).T#ndmin为定义数组最小维度 .T为转置

        #calculate signals into hidden layer
        hidden_inputs=np.dot(self.wih,inputs)

        #calculate the signals emerging from hidden layer
        hidden_outputs=self.activation_function(hidden_inputs)

        #calculate signals into final layer
        final_inputs =np.dot(self.who,hidden_outputs)

        #calculate signals emerging from final layer
        final_outputs = self.activation_function(final_inputs)

        return final_outputs

        pass

6. 反向训练权重

第二阶段 更新权重
首先我们得到误差,由目标矩阵减去最终矩阵

output_errors = targets -final_outputs

然后为隐藏层的误差构建矩阵,在输出层中,有两条路径对误差做出了“贡献”
分别为w1,1和w2,1,所以我们根据权重比例,对误差进行了分割,用e的一部分来更新w1,1:e*(w1,1/w,1,1+w2,1),类似的调整w2,1的e的部分为e*w2,1/(w1,1+w2,1),
这些分数的分母是一种归一化的因子,我们忽略这个因子,如果是两个输出节点

Error(hidden) = (w1,1  w1,2   *(e1    =(w1,1*e1+w1,2*e2
		  	     w2,1  w2,2)    e2)      w2,1*e1 +w2,2*e2)

这个权重矩阵和我们先前构建的矩阵很像,容易发现是进行了转置,

Error(hidden) =Whidden_output.T *Erroroutput

我们现在获得了最终层的误差和隐藏层的误差,现在我们来更新权重。但是这些节点都不是简单的线性分类,这些稍微复杂的节点,对加权后的信号进行求和,,并应用了S阈值函数,将所得到的输出给下一层节点。但这个表达式太复杂了,我们不能硬碰硬的求解这个表达式。

换一个思路,想象一下,在一个非常复杂,有波峰波谷的地形以及连绵的群山峻岭。在黑暗中伸手不见五指。你知道你是在一个山坡上,你需要坡底。对于整个地形,你没有精确的地图,只有一把手电筒,你只能使用手电筒,做近距离的观察,你可以看到某一块土地看起来是下坡,于是就小步往下走,缓慢下山。

在数学上,我们称之为梯度下降,首先我们画一个简单的函数y = (x-1)^2 +1,
用y表示误差,我们希望找到x,可以最小化y,我们随机在线上取一点,当斜率为正就
向左移,斜率为负就向右移,我们要将步子调小,避免超调,在最小值的地方来回反
弹。

这次我们用复杂的三维空间,同时用高表示函数的值,这次我们的山谷有多个,但并不都是最低的山谷,我们会卡在错误的山谷吗?答案是肯定的。
为了避免这种事发生,我们从山上的不同点开始,多次训练神经网络。

epoch =5#训练的次数

我们可以使用梯度下降法,计算出正确的权重吗?只要我们选择了合适的误差函数,这完全是可以的。
之前我们已得到误差,我们很容易把输出函数变成误差函数。

误差值  = (目标值-实际值)^2
  • 使用误差的平方,我们可以很容易使代数计算出梯度下降的斜率。
  • 误差函数平滑连续,这使得梯度下降法很好地发挥作用——没有间断,也没有突然的跳跃
  • 越接近最小值,梯度越小,这意味着,我们使用这个函数调节步长,超调的风险就会变得较小。

要使用梯度下降,现在我们需要计算出误差函数相对于权重的斜率。也就是说我们想要知道“误差对链接权重的改变有多敏感”,这里我们会用到微分知识。
如果把网络连接权重(wi,j)为横坐标,神经网络误差(Error)为纵坐标,那么斜率就是
【零基础】从零开始学神经网络《python神经网络编程》——手写数字识别实战_第11张图片

我们定义i,j,k,i为输入层,j为隐藏层,k为输出层
输出层输出ok,
节点误差=目标值 - 实际值 ek = tk -ok

我们反向传播,先看后两层,隐藏层和输出层
由微分的链式法则得
【零基础】从零开始学神经网络《python神经网络编程》——手写数字识别实战_第12张图片

我们对上述式子各个击破,E = (tk-ok)^2,得到
【零基础】从零开始学神经网络《python神经网络编程》——手写数字识别实战_第13张图片
ok是节点k的输出,如果你还记得,这是连续输入信号上进行加权求和,在得到的结果上应用函数得到的结果
在这里插入图片描述
由sigmod微分公式
【零基础】从零开始学神经网络《python神经网络编程》——手写数字识别实战_第14张图片
带入,由于sigmod函数内部的表达式也需要对wj,k进行微分,因此我们对S函数微分项再次应用链式法则,这也非常容易,答案为oj,所以为
在这里插入图片描述
我们把2去掉,我们只关心斜率方向,ek = tk-ok
在这里插入图片描述
这就是我们所寻求的神奇表达式,也是训练神经网络的关键
以此类推输入层和隐藏层的误差函数斜率
在这里插入图片描述
更新后的权重wj,k是由刚刚得到的误差斜率取反来调整旧的权重而得到的

a为学习率,这个因子可以调节这些变化的长度,确保不会超调。
还是画出y = (x-1)^2+1,y为误差,x为权重
减号意味着,斜率为正就左移,减小误差
斜率为负就右移,减小误差
有点类似于牛顿迭代法,思想上是相同的

【零基础】从零开始学神经网络《python神经网络编程》——手写数字识别实战_第15张图片
old-new = a *斜率
在这里插入图片描述
所以
在这里插入图片描述
表达式的最后一部分,是单行的水平矩阵,是之前输出的转置(之前输出是竖直的),因此通过这种形式可以让我们通过计算机编程语言高效的实现矩阵运算。
所以我们的反向训练代码为:

    # train the netural network
    def train(self,inputs_list,targets_list):
        #convert inputs list to 2d array
        inputs = np.array(inputs_list,ndmin=2).T
        targets =np.array(targets_list,ndmin=2).T
        # calculate signals into hidden layer
        hidden_inputs = np.dot(self.wih, inputs)

        # calculate the signals emerging from hidden layer
        hidden_outputs = self.activation_function(hidden_inputs)

        # calculate signals into final layer
        final_inputs = np.dot(self.who, hidden_outputs)

        # calculate signals emerging from final layer
        final_outputs = self.activation_function(final_inputs)

        # output layer error is the (target -actual)
        output_errors = targets -final_outputs

        # hidden layer error is the output_error,split by weights,recombined at hidden nodes
        hidden_errors = np.dot(self.who.T,output_errors)

        #update the weights for the links between the hidden and output layers
        self.who +=self.lr * np.dot((output_errors * final_outputs *(1.0- final_outputs)),np.transpose(hidden_outputs))

        #updata the weights for the links between the input and hidden layers
        self.wih +=self.lr * np.dot((hidden_errors * hidden_outputs *(1.0-hidden_outputs)),np.transpose(inputs))
        pass

四、csv文件

minist手写数据集
这意味着纯文本中的每一个值都是由逗号分隔的
第一个值是标签,随后的值是手写数字的像素值,28*28 为784个

#load the minist training data csv file into a list
training_data_file = open ("minist_dataset/mnist_train.csv","r")
training_data_list = training_data_file.readlines()
training_data_file.close()

五、创建一个神经网络

# number of input,hidden and output nodes
input_nodes = 784 #28*28
hidden_node = 200 #100#隐层在200以后不再变化
output_node = 10 #10分类

# learning rate is 0.5
learning_rate = 0.1#学习率在0.1左右表现最佳

# create instance of neural network
n =neturalNetwork(input_nodes,hidden_node,output_node,learning_rate)

六、训练

#training the neural network

#epochs is the number of times the training data set is used for training
epochs =5#就是训练两遍

for e in range(epochs):#5 或 7 达到一个最佳点
# go through all records in the traing data set
    for record in training_data_list:
        #split the record by the ',' commas(逗号)
        all_values = record.split(',')
        #scale and shift(变换) the inputs
        inputs = (np.asfarray(all_values[1:]) / 255.0 * 0.99)+0.01 #np.asfarray 将文本字符串转换为实数,并创建这些数字的数组
                                                                   # 使当前范围为0.01到0.99
        #create the target output values (all 0.01,except the desired label which is 0.99)
        targets = np.zeros(output_node) + 0.01#创建一个10分类的目标 ,除正确的是0.99其他都是0.01
        #all_value[0] is the target label for this record
        targets[int(all_values[0])] = 0.99#列如0的目标是[0.99,0.01,0.01,...]
        n.train(inputs,targets)
        pass

七、测试

创建一个列,答对了就写1,不对就写0

# test the neural network
scorecard =[]
for record in  test_data_list:
    all_values =record.split(',')
    correct_label = int(all_values[0])
    print(correct_label,"correct label")
    inputs = (np.asfarray(all_values[1:]) /255 *0.99 +0.01)
    outputs = n.query(inputs)
    label = np.argmax(outputs)#np.argmax发现数组的最大值并返回它的位置
    print(label,"network's answer")
    if (label == correct_label):
        scorecard.append(1)
    else:
        scorecard.append(0)
        pass
    pass

八、计算正确率

# calculate the performance score ,the fraction(一小部分) of correct answers
scorecard_array = np.asarray(scorecard)
print("performance=",scorecard_array.sum() / scorecard_array.size)

在这里插入图片描述

你可能感兴趣的:(神经网络,python,机器学习,神经网络)