欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ANN之手写数字识别

程序员文章站 2024-01-22 11:11:10
...

ANN之手写数字识别

MNIST数据集请查看上一篇博文:

https://blog.csdn.net/haohuang_ch/article/details/81297410

本文在数据存储形式上与上一篇博文区别为,将target从单一的数字转化为了一个10*1的矩阵,或者说是一个10维向量,对应的target的维度值为1,其它均为0。

简介

ANN是人工神经网络的简称。如果你对比ANN简单的线性回归、逻辑回归和梯度下降法没有认识,推荐先学习一下再学习ANN。
ANN与回归的不同之处在于,它有网络结构,大概如下:
ANN之手写数字识别
而对于回归,我们可以简单地理解为,只有输入层到输出层。而ANN有一个或多个隐藏层,隐藏层的每个节点称作神经元,每层之间依赖于一个权重系数矩阵。设计神经网络的主要工作在于设计隐藏层的结构和权重系数矩阵。
对于每层之间的传播过程,可以总结为如下:
ANN之手写数字识别

in=WiX+b
out=activation(in)
Wi 表示系数矩阵的第 i 行,由 in 在该层的位置决定。
1 是偏置项。
activation() 是**函数。
该层的每一个节点的 out 构成该层的输出 X

权重系数矩阵

#neurons是神经网络的结构表示,是一个数组,元素为每层的节点数,例如[784,40,10]。
#输出parameter是一个字典,键为Wi,值为系数矩阵。
def initialize_parameters(neurons):
    inputneurons=neurons[0]
    hideneurons=neurons[1:len(neurons)-1]
    outputneurons=neurons[-1]
    hidelayers=len(hideneurons)
    parameter={}
    np.random.seed(2);
    #input->hide
    W0=np.random.uniform(low=-np.sqrt(6)/np.sqrt(inputneurons+1+hideneurons[0]), high=np.sqrt(6)/np.sqrt(inputneurons+1+hideneurons[0]),size=(hideneurons[0],inputneurons+1))
    parameter["W0"]=W0
    #hide->hide
    for l in range(0,hidelayers-1):
        Wl=np.random.uniform(low=-np.sqrt(6)/np.sqrt(hideneurons[l]+1+hideneurons[l+1]), high=np.sqrt(6)/np.sqrt(hideneurons[l]+1+hideneurons[l+1]),size=(hideneurons[l+1],hideneurons[l]+1))
        parameter["W"+str(l+1)]=Wl
    #hide->output
    Wo=np.random.uniform(low=-np.sqrt(6)/np.sqrt(hideneurons[hidelayers-1]+1+outputneurons), high=np.sqrt(6)/np.sqrt(hideneurons[hidelayers-1]+1+outputneurons),size=(outputneurons,hideneurons[hidelayers-1]+1))
    parameter["W"+str(hidelayers)]=Wo
    return parameter

偏置项

如果我们只看一维的 y=wx+bw 是斜率,b 是截距。
如果 b=0 ,则图像经过原点,但并不是所有的数据都是关于原点线性可分的,如下图。因此需要一个偏置项来移动分割面。
ANN之手写数字识别
而这里的 b 可以看做 b1b 则可以作为系数矩阵的一列,作为可训练的系数。

**函数

**函数常用的有sigmod、tanh和relu。
sigmod(x)=11+ex

tanh(x)=2sigmod(2x)1

relu(x)=max(0,x)
**函数的作用是使神经网络层的输出非线性化。假设没有**函数,我们知道 y=WX+b 是线性函数,但是并不是所有的数据分类都是线性可分的,如下图。
ANN之手写数字识别
左图线性可分,右图非线性可分。如果没有**函数,用线性结果去拟合右图的数据,则显得非常僵硬。
关于**函数的细节,推荐博文:

https://www.cnblogs.com/rgvb178/p/6055213.html

  • 代码实现
def sigmod(x):
    _x = -1*x
    return 1.0/(1+np.exp(_x))

def tanh(x):
    x_2 = 2*x
    return 2*sigmod(x_2)-1

def relu(x):
    mx = np.maximum(x, 0)
    return mx

**函数的导数为:
sigmod(x)=sigmod(x)(1sigmod(x))

tanh(x)=1tanh2(x)

relu(x)={1,x>00,x0

代码实现:

def sigmod_prime(y):
    return y*(1-y)

def tanh_prime(y):
    return 1-y**2

def relu_prime(y):
    if(y>0):
        return 1
    else:
        return 0

神经网络的作用

为什么简单的回归的拟合效果没有神经网络好呢?神经网络各层的神经元究竟起到了什么样的作用?我们可以通过这个图来理解一下。
ANN之手写数字识别

每层的一个神经元其实是对数据做了一次划分的表示,多个神经元通过系数矩阵的运算其实是在取逻辑与的过程,从而实现了对数据比较完整的划分。通过下图可以更加明确这个效果。
ANN之手写数字识别

ANN学习过程

学习过程分为前向传播(forward_propagation)和反向传播(backward_propagation)两个过程。

  • 前向传播
      前向传播前面已经介绍过了,每层过程如下:
        in=WiX+b
        out=activation(in)
      代码实现:
#hidelayers为隐藏层层数,outputneurons为输出层节点数,parameters为所有系数矩阵的字典,data为输入层的数据,target为输出的预期结果。
#output是输出层的结果,error是误差总和
def forward_propagation(hidelayers,outputneurons,parameters,data,target):
    outputs=[]
    outputs.append(data)
    error=0.0
    for l in range(0,hidelayers+1):
        outputs[l]=np.vstack((outputs[l],np.ones((1,1))))
        outputs.append(tanh(np.dot(parameters["W"+str(l)],outputs[l])))
    for i in range(0, outputneurons):
        error=error+0.5*((target[i][0]-outputs[-1][i][0])**2)
    return outputs,error
  • 反向传播
      由于输出是有误差的,所以需要修正误差,于是就需要修正权重系数,我们可以根据误差对系数求偏导,以此来表示系数对误差的影响,来实现对误差的修正,本文误差计算采用二次代价函数。
      计算每个输出神经元的误差:Errori=12(targetioutputi)2
      修正系数:wijl=wijlη·Erroriwijl
      求偏导可以使用链氏求导法则:
      Erroriwijl=Errorioutputi·outputiwijl=Errorioutputi·outputiin·inwijl
      以上是隐藏层到输出层系数修正的求偏导示例,其他层之间的系数修正同理求导即可。我们把 Erroriwijl ( l 表示层与层之间的间隔序号,ij 分别是矩阵的行和列的下标 ) 称为梯度,η 称为学习速率。
      我们的误差计算函数其实是一个开口向上的二次函数,所以修正系数是为了是二次函数取到最小值。
      ANN之手写数字识别

      代码实现

#计算梯度
#hidelayers表示隐藏层层数,neurons是神经网络结构数组,outputs表示前向传播的输出,target表示数据本身的结果
#输出的Theta_W为梯度
def grad(hidelayers,neurons,parameters,outputs,target):
    Theta_W=[]
    t_w=np.zeros((neurons[-1],neurons[hidelayers]+1))
    e_o=[]
    #hide->out
    for o in range(0,neurons[-1]):
        e_o.append(-(target[o][0]-outputs[-1][o][0])*tanh_prime(outputs[-1][o][0]))
    eta_out=e_o
    W=parameters["W"+str(hidelayers)]
    for o in range(0,neurons[-1]):
        for k in range(0,neurons[hidelayers]+1):
            t_w[o][k]=eta_out[o]*outputs[hidelayers][k]
    Theta_W.insert(0,t_w)
    e_o=[]
    for k in range(0,neurons[hidelayers]+1):
        eta_w=0
        for o in range(0,neurons[-1]):
            eta_w=eta_w+eta_out[o]*W[o][k]
        e_o.append(eta_w*tanh_prime(outputs[hidelayers][k][0]))
    eta_out=e_o
    #hide->hide
    for hl in range(hidelayers,0,-1):
        W=parameters["W"+str(hl-1)]
        t_w=np.zeros((neurons[hl],neurons[hl-1]+1))
        for o in range(0,neurons[hl]):
            for k in range(0,neurons[hl-1]+1):
                t_w[o][k]=eta_out[o]*outputs[hl-1][k]
        Theta_W.insert(0,t_w)
        e_o=[]
        for k in range(0,neurons[hl-1]+1):
            eta_w=0
            for o in range(0,neurons[hl]):
                eta_w=eta_w+eta_out[o]*W[o][k]
            e_o.append(eta_w*tanh_prime(outputs[hl-1][k][0]))
        eta_out=e_o
    return Theta_W

#反向传播过程
#hidelayers表示隐藏层层数,neurons是神经网络结构数组,parameters表示系数矩阵,eta表示学习速率,Theta_W表示要修正的梯度。
def backward_propagation(hidelayers,neurons,parameters,eta,Theta_W): 
    cache_param={}
    for hl in range(0,hidelayers+1):
        W=parameters["W"+str(hl)]
        newW=parameters["W"+str(hl)]
        for o in range(0,neurons[hl+1]):
            for k in range(0,neurons[hl]+1):
                newW[o][k]=W[o][k]-eta*Theta_W[hl][o][k]
        cache_param["W"+str(hl)]=newW
    return cache_param 

整体代码

# -*- coding: utf-8 -*-
import pandas as pd
import numpy as np
import struct,os
from array import array as pyarray
from numpy import append, array, int8, uint8, zeros

pd.set_option('display.width',10000)
pd.set_option('display.max_colwidth',10000)
pd.set_option('display.max_columns',100)
pd.set_option('display.max_rows',100)
np.set_printoptions(threshold=np.nan)

def sigmod(x):
    _x = -1*x
    return 1.0/(1+np.exp(_x))

def tanh(x):
    x_2 = 2*x
    return 2*sigmod(x_2)-1

def relu(x):
    mx = np.maximum(x, 0)
    return mx

def sigmod_prime(y):
    return y*(1-y)

def tanh_prime(y):
    return 1-y**2

def relu_prime(y):
    if(y>0):
        return 1
    else:
        return 0

def softmax(x):
    return np.argmax(x)

def normalized(ima, inputneurons):
    ima_max=np.max(ima)
    ima_min=np.min(ima)
    newima=np.zeros((inputneurons,1))
    for i in range(0,inputneurons):
        newima[i][0]=(ima[i][0]-ima_min)/(ima_max-ima_min)
    return newima

def initialize_parameters(neurons):
    inputneurons=neurons[0]
    hideneurons=neurons[1:len(neurons)-1]
    outputneurons=neurons[-1]
    hidelayers=len(hideneurons)
    parameter={}
    np.random.seed(2);
    #inputneurons
    W0=np.random.uniform(low=-np.sqrt(6)/np.sqrt(inputneurons+1+hideneurons[0]), high=np.sqrt(6)/np.sqrt(inputneurons+1+hideneurons[0]),size=(hideneurons[0],inputneurons+1))
    parameter["W0"]=W0
    #hidelayers
    for l in range(0,hidelayers-1):
        Wl=np.random.uniform(low=-np.sqrt(6)/np.sqrt(hideneurons[l]+1+hideneurons[l+1]), high=np.sqrt(6)/np.sqrt(hideneurons[l]+1+hideneurons[l+1]),size=(hideneurons[l+1],hideneurons[l]+1))
        parameter["W"+str(l+1)]=Wl
    #outputneurons
    Wo=np.random.uniform(low=-np.sqrt(6)/np.sqrt(hideneurons[hidelayers-1]+1+outputneurons), high=np.sqrt(6)/np.sqrt(hideneurons[hidelayers-1]+1+outputneurons),size=(outputneurons,hideneurons[hidelayers-1]+1))
    parameter["W"+str(hidelayers)]=Wo
    return parameter

def forward_propagation(hidelayers,outputneurons,parameters,data,target):
    outputs=[]
    outputs.append(data)
    error=0.0
    for l in range(0,hidelayers+1):
        outputs[l]=np.vstack((outputs[l],np.ones((1,1))))
        outputs.append(sigmod(np.dot(parameters["W"+str(l)],outputs[l])))
    for i in range(0, outputneurons):
        error=error+0.5*((target[i][0]-outputs[-1][i][0])**2)
    return outputs,error

def grad(hidelayers,neurons,parameters,outputs,target):
    Theta_W=[]
    t_w=np.zeros((neurons[-1],neurons[hidelayers]+1))
    e_o=[]
    #hide->out
    for o in range(0,neurons[-1]):
        e_o.append(-(target[o][0]-outputs[-1][o][0])*sigmod_prime(outputs[-1][o][0]))
    eta_out=e_o
    W=parameters["W"+str(hidelayers)]
    for o in range(0,neurons[-1]):
        for k in range(0,neurons[hidelayers]+1):
            t_w[o][k]=eta_out[o]*outputs[hidelayers][k]
    Theta_W.insert(0,t_w)
    e_o=[]
    for k in range(0,neurons[hidelayers]+1):
        eta_w=0
        for o in range(0,neurons[-1]):
            eta_w=eta_w+eta_out[o]*W[o][k]
        e_o.append(eta_w*sigmod_prime(outputs[hidelayers][k][0]))
    eta_out=e_o
    #hide->hide
    for hl in range(hidelayers,0,-1):
        W=parameters["W"+str(hl-1)]
        t_w=np.zeros((neurons[hl],neurons[hl-1]+1))
        for o in range(0,neurons[hl]):
            for k in range(0,neurons[hl-1]+1):
                t_w[o][k]=eta_out[o]*outputs[hl-1][k]
        Theta_W.insert(0,t_w)
        e_o=[]
        for k in range(0,neurons[hl-1]+1):
            eta_w=0
            for o in range(0,neurons[hl]):
                eta_w=eta_w+eta_out[o]*W[o][k]
            e_o.append(eta_w*sigmod_prime(outputs[hl-1][k][0]))
        eta_out=e_o
    return Theta_W

def grad_average(batch_size,hidelayers,Theta_W_s):
    Theta_W=[]
    for hl in range(0,hidelayers+1):
        t_w=np.zeros((Theta_W_s[0][hl].shape[0],Theta_W_s[0][hl].shape[1]))
        for bs in range(0,batch_size):
            t_w=t_w+Theta_W_s[bs][hl]
        t_w=t_w/batch_size
        Theta_W.append(t_w)
    return Theta_W

def backward_propagation(hidelayers,neurons,parameters,theta,Theta_W): 
    cache_param={}
    for hl in range(0,hidelayers+1):
        W=parameters["W"+str(hl)]
        newW=parameters["W"+str(hl)]
        for o in range(0,neurons[hl+1]):
            for k in range(0,neurons[hl]+1):
                newW[o][k]=W[o][k]-theta*Theta_W[hl][o][k]
        cache_param["W"+str(hl)]=newW
    return cache_param 

def load_mnist(dataset="training_data", digits=np.arange(10), path="."):
    if dataset == "training_data":
        fname_image = os.path.join(path, 'train-images.idx3-ubyte')
        fname_label = os.path.join(path, 'train-labels.idx1-ubyte')
    elif dataset == "testing_data":
        fname_image = os.path.join(path, 't10k-images.idx3-ubyte')
        fname_label = os.path.join(path, 't10k-labels.idx1-ubyte')
    else:
        raise ValueError("dataset must be 'training_data' or 'testing_data'")

    flbl = open(fname_label, 'rb')
    magic_nr, size = struct.unpack(">II", flbl.read(8))
    lbl = pyarray("b", flbl.read())
    flbl.close()

    fimg = open(fname_image, 'rb')
    magic_nr, size, rows, cols = struct.unpack(">IIII", fimg.read(16))
    img = pyarray("B", fimg.read())
    fimg.close()

    ind = [ k for k in range(size) if lbl[k] in digits ]
    N = len(ind)

    images = zeros((N, rows, cols), dtype=uint8)
    labels = []
    for i in range(len(ind)):
        images[i] = array(img[ ind[i]*rows*cols : (ind[i]+1)*rows*cols ]).reshape((rows, cols))
        l=np.zeros((10,1))
        l[lbl[ind[i]]]=1
        labels.append(l)
    return images, labels

def load_samples(dataset="training_data"):
    image,label = load_mnist(dataset)
    X = [np.reshape(x,(28*28, 1)) for x in image]
    X = [x/255.0 for x in X]   # 灰度值范围(0-255),转换为(0-1)
    return X,label

if __name__ == "__main__":
    hideneurons=[40]
    hidelayers=len(hideneurons)
    inputneurons=784
    outputneurons=10
    neurons=[inputneurons]+hideneurons+[outputneurons]

    parameters = initialize_parameters(neurons)    

    eta = 1.0

    numtraindata = 60000
    numtestdata = 10000

    traindata, imgtraintarget = load_samples(dataset='training_data')
    testdata, imgtesttarget = load_samples(dataset='testing_data') 

    batch_size=20
    epoch=10

    Correct=[]
    for e in range(0,epoch):
        for ntd in range(0,numtraindata,batch_size):
            Theta_W_s=[]
            ER=[]
            for bs in range(0,batch_size):
                outputs,error = forward_propagation(hidelayers,outputneurons,parameters,traindata[ntd+bs],imgtraintarget[ntd+bs])
                Theta_W_s.append(grad(hidelayers,neurons,parameters,outputs,imgtraintarget[ntd+bs]))
                ER.append(error)
            Theta_W=grad_average(batch_size,hidelayers,Theta_W_s)
            parameters=backward_propagation(hidelayers,neurons,parameters,eta,Theta_W)
            er=0
            for e in ER:
                er=er+e
            error=er/len(ER)
            #print(str(ntd)+"-"+str(ntd+batch_size)+":"+str(error))

        preCorrect=0
        prenum=[0]*10
        targetnum=[0]*10
        for ntd in range(0,numtestdata):
            outputs,error = forward_propagation(hidelayers,outputneurons,parameters,testdata[ntd],imgtesttarget[ntd])
            predict=softmax(outputs[-1])
            target=softmax(imgtesttarget[ntd])
            targetnum[target]=targetnum[target]+1
            if(predict==target):
                preCorrect=preCorrect+1
                prenum[target]=prenum[target]+1
        print(preCorrect/numtestdata)
        Correct.append(preCorrect/numtestdata)

    print(Correct)

    for i in range(0,10):
        if(targetnum[i]==0):
            a=0.0
        else:
            a=prenum[i]/targetnum[i]
        print(str(i)+":    "+str(prenum[i])+"/"+str(targetnum[i])+" = "+str(a)+"\r\n")

    print("All Testdata are predicted......")

由于一个一个数据计算过慢,于是选择一组大小为batch_size的数据以梯度平均值进行迭代。神经网络结构为[784,40,10],学习速率为1.0,**函数为sigmod。预测结果如图:

ANN之手写数字识别

sklearn的ANN

sklearn也有人工神经网络的库

from sklearn.neural_network import MLPClassifier

clf = MLPClassifier(hidden_layer_sizes=(50,50), 
                        activation        ='relu', 
                        solver            ='sgd', 
                        random_state      =1,
                        learning_rate     ='constant', 
                        learning_rate_init=0.1, 
                        max_iter          =100, 
                        alpha             =1e-4,
                        verbose           =True
                        )

clf.fit(imgtraindata,imgtraintarget)
pre = clf.predict(imgtestdata)

结果:

ANN之手写数字识别

可见还是库写得好,瘫——