“我的Go+语言初体验” | 征文活动进行中…

我的Go+语言初体验——Go+语言构建神经网络实战手写数字识别

  • 0. 前言
  • 1. 神经网络相关概念
  • 2. 构建神经网络实战手写数字识别
    • 2.1 构建神经网络
      • 2.1.1 节点计算
      • 2.1.2 激活函数
      • 2.1.3 网络架构
    • 2.2 读取手写数字MNIST数据集
    • 2.3 训练神经网络
      • 2.3.1 前向计算
      • 2.3.2 反向传播
    • 2.4 评估神经网络
  • 3. 程序运行
  • 后记

0. 前言

之前发blink说自己想学一门新语言,很多热心的小伙伴推荐了 Go,这时又恰逢看到官方创作活动“我的Go+语言初体验”征文大赛,看了官方文档,发现 Go+ 完全兼容 Go 语言,并且代码更加易读。这不就是说,这波实际学习了一门语言却掌握了两门语言,表示赚到了。
于是迫不及待的开始准备体验下,既然官方介绍说 Go+ 「for engineering, STEM education, and data science」,融合了数据科学领域的 Python,那作为人工智能领域的相关从业人员,探索 Go+ 在人工智能领域的应用,我辈当然又是义不容辞了。
本文,首先简要概述下神经网络的相关概念,然后使用 Go+ 语言构建神经网络实战手写数字识别。

1. 神经网络相关概念

人工神经网络的发展受到了人脑神经元的启发,并且在多个领域中都已经取得了广泛的应用,包括图像识别、语音识别以及推荐系统等等,本文并非人工智能的详尽教程,但会简要介绍相关基础,为使用 Go+ 语言构建神经网络奠定基础。
在人工神经网络中,使用神经元接受输入数据,对数据执行操作后传递到下一神经元,每个神经元的输出称为激活,获取激活的函数称为激活函数,神经元中的参数称为权重偏置。每个网络层中包含若干个神经元,其中接收初始输入的网络层称为输入层,产生最终结果的网络层称为输出层,位于输出层与隐藏层之间的网络层称为隐藏层。数据从输入到输出的整个传输过程称为正向传播;而反向传播是一种训练神经网络的方法,通过计算真实值与网络输出值间的误差,反向修改网络的权重。
在如下图所示的全连接网络中,每个节点表示一个神经元,整个网络包括一层输入层、一层输出层已经两层隐藏层。

虽然已经有一些现有的神经网络框架可以使用,但作为体验作,本文将从头开始构建简单的全连接网络,以更好了解神经网络的基本组成以及运行原理。
本文使用 MNIST 数据集和 gonum 构建简单的全连接网络,虽然全连接网络是十分基础简单的神经网络,但是相关的模型训练流程和原理是相通的。

2. 构建神经网络实战手写数字识别

2.1 构建神经网络

我们已经知道神经网络中的节点接受输入矩阵,通过与权重矩阵进行计算后,通过激活函数后,产生输出,接下来将讲解具体计算流程。

2.1.1 节点计算

每个神经元的计算形式如下图所示:

公式化后,如下所示:
o=[w1w2⋮wn][x1x2⋯xn]+bo = \begin{bmatrix} w_1\\ w_2 \\ \vdots \\ w_n \end{bmatrix} \begin{bmatrix} x_1 & x_2 & \cdots &x_n \end{bmatrix} + bo=⎣⎢⎢⎢⎡​w1​w2​⋮wn​​⎦⎥⎥⎥⎤​[x1​​x2​​⋯​xn​​]+b

其中,wiw_iwi​ 表示权重,bbb 表示偏置。
Go+ 中利用 gonum 实现上述计算过程如下:

 hiddenLayerInput.Mul(x, nn.wHidden)addBHidden := func(_, col int, v float64) float64 { return v + nn.bHidden.At(0, col) }hiddenLayerInput.Apply(addBHidden, hiddenLayerInput)

gonum 用于高效编写数字和科学算法的算法库,可以通过执行以下命令获取:

go get gonum.org/v1/gonum

2.1.2 激活函数

仅仅通过上述线性计算,无法拟合现实生活中广泛存在的非线性模型,因此,神经网络中引入了激活函数来赋予网络非线性,有很多激活函数:sigmoidReLUtanh 等等。这以简单的 sigmoid 函数为例:
sigmoid(x)=11+e−xsigmoid(x)=\frac 1 {1+e^{-x}}sigmoid(x)=1+e−x1​

Go+ 中实现 sigmoid 函数如下:

// activation functions
func sigmoid(x float64) float64 {return 1.0 / (1.0 + math.Exp(-x))
}

2.1.3 网络架构

接下来,构建包含一个输入层,一个隐藏层,一个输出层的神经网络。其中,输入层包含 784 个神经元,这是由于 MNIST 数据集中每张照片包含 784 个像素点,每个像素点就是一个输入;隐藏层包含 512 个神经元,也可以使用更多或更少的神经元数量进行测试;输出层包含 10 个神经元,每个节点对应一个数字类别,这在神经网络中也称为独热编码。
网络架构定义如下:

config := neuralNetConfig{// 输入层神经元inputNeurons:  784,// 输出层神经元outputNeurons: 10,// 隐藏层神经元hiddenNeurons: 128,// 训练 Epoch 数numEpochs:     5000,// 学习率learningRate:  0.01,
}

学习率用于控制每个 Epoch 中的参数的调整幅度。

2.2 读取手写数字MNIST数据集

训练数据是由 MNIST 手写数字组成的,MNIST 数据集来自美国国家标准与技术研究所,由来自 250 个不同人手写的数字构成,其中训练集包含 60000 张图片,测试集包含 10000 张图片,每个图片都有其标签,图片大小为 28*28

  1. 首先需要下载数据;
  2. 然后读取数据;
//读取数据
f, err := os.Open("new_mnist_train.csv")
if err != nil {log.Fatal(err)
}
defer f.Close()reader := csv.NewReader(f)
reader.FieldsPerRecord = 794// 读取所有CSV记录
mnistData, err := reader.ReadAll()
if err != nil {log.Fatal(err)
}// trainInputsData和trainLabelsData用于保存所有浮点值
trainInputsData := make([]float64, 784*len(mnistData))
println(len(inputsData))
trainLabelsData := make([]float64, 10*len(mnistData))// 记录输入矩阵值的当前索引
var trainInputsIndex int
var trainLabelsIndex intfor idx, record := range mnistData {// 跳过文件头if idx == 0 {continue}// 循环读取每行的每个数据for i, val := range record {// 将数据转换为浮点形parsedVal, err := strconv.ParseFloat(val, 64)if err != nil {log.Fatal(err)}// 构造标签数据if i == 0 || i == 1 || i == 2 || i == 3 || i == 4 || i == 5 || i == 6 || i == 7 || i == 8 || i == 9{trainLabelsData[trainLabelsIndex] = parsedValtrainLabelsIndex++continue}// 构建输入数据trainInputsData[trainInputsIndex] = parsedValtrainInputsIndex++}
}
  1. 最后将数据整形,使得其加油可用于网络输入的形状。
inputs := mat.NewDense(len(mnistData), 784, trainInputsData)
labels := mat.NewDense(len(mnistData), 10, trainLabelsData)

测试数据的读取方法与训练数据完全相同,不再赘述。

2.3 训练神经网络

网络的训练可以分为两部分,包括前向计算与反向传播。

2.3.1 前向计算

网络的前向计算十分简单,即通过数据流过网络层获得最终结果,首先需要初始化网络权重和偏置值:

 // 初始化网络权重和偏置值wHiddenRaw := make([]float64, nn.config.hiddenNeurons*nn.config.inputNeurons)bHiddenRaw := make([]float64, nn.config.hiddenNeurons)wOutRaw := make([]float64, nn.config.outputNeurons*nn.config.hiddenNeurons)bOutRaw := make([]float64, nn.config.outputNeurons)for _, param := range [][]float64{wHiddenRaw, bHiddenRaw, wOutRaw, bOutRaw} {for i := range param {param[i] = randGen.Float64()}}wHidden := mat.NewDense(nn.config.inputNeurons, nn.config.hiddenNeurons, wHiddenRaw)bHidden := mat.NewDense(1, nn.config.hiddenNeurons, bHiddenRaw)wOut := mat.NewDense(nn.config.hiddenNeurons, nn.config.outputNeurons, wOutRaw)bOut := mat.NewDense(1, nn.config.outputNeurons, bOutRaw)

然后,在每个 Epoch 中首先完成前向计算:

 // 前向计算过程hiddenLayerInput := &mat.Dense{}hiddenLayerInput.Mul(x, wHidden)addBHidden := func(_, col int, v float64) float64 { return v + bHidden.At(0, col) }hiddenLayerInput.Apply(addBHidden, hiddenLayerInput)hiddenLayerActivations := &mat.Dense{}applySigmoid := func(_, _ int, v float64) float64 { return sigmoid(v) }hiddenLayerActivations.Apply(applySigmoid, hiddenLayerInput)outputLayerInput := &mat.Dense{}outputLayerInput.Mul(hiddenLayerActivations, wOut)addBOut := func(_, col int, v float64) float64 { return v + bOut.At(0, col) }outputLayerInput.Apply(addBOut, outputLayerInput)output.Apply(applySigmoid, outputLayerInput)

2.3.2 反向传播

神经网络的反向传播,较为复杂,需要使用利用链式法则,计算每层的梯度信息,这里以 sigmoid 函数为例:
ddxσ(x)=ddx(11+e−x)=e−x(1+e−x)2=(1+e−x)−1(1+e−x)2=1+e−x(1+e−x)2−(11+e−x)2=σ(x)−σ(x)2=σ(1−σ)\begin{aligned} \frac d {dx} σ(x) & = \frac d {dx} (\frac 1 {1+e^{-x}}) \\ &=\frac {e^{-x}} {(1+e^{-x})^2} \\ &= \frac {(1+e^{-x})-1} {(1+e^{-x})^2}\\ &= \frac {1+e^{-x}} {(1+e^{-x})^2}-(\frac 1 {1+e^{-x}})^2\\ &=σ(x) - σ(x)^2=σ(1-σ) \end{aligned}dxd​σ(x)​=dxd​(1+e−x1​)=(1+e−x)2e−x​=(1+e−x)2(1+e−x)−1​=(1+e−x)21+e−x​−(1+e−x1​)2=σ(x)−σ(x)2=σ(1−σ)​
使用 Go+ 语言实现代码如下:

func sigmoidDerivation(x float64) float64 {return sigmoid(x) * (1.0 - sigmoid(x))
}

其他层的详细的计算步骤在此就不予展示了,这里直接给出 Go+ 语言代码:

 //梯度的反向传播networkError := &mat.Dense{}networkError.Sub(y, output)   // 损失函数slopeOutputLayer := &mat.Dense{}applySigmoidDerivation := func(_, _ int, v float64) float64 { return sigmoidDerivation(v) }slopeOutputLayer.Apply(applySigmoidDerivation, output)slopeHiddenLayer := &mat.Dense{}slopeHiddenLayer.Apply(applySigmoidDerivation, hiddenLayerActivations)dOutput := &mat.Dense{}dOutput.MulElem(networkError, slopeOutputLayer)errorAtHiddenLayer := &mat.Dense{}errorAtHiddenLayer.Mul(dOutput, wOut.T())dHiddenLayer := &mat.Dense{}dHiddenLayer.MulElem(errorAtHiddenLayer, slopeHiddenLayer)// 参数修改wOutAdj := &mat.Dense{}wOutAdj.Mul(hiddenLayerActivations.T(), dOutput)wOutAdj.Scale(nn.config.learningRate, wOutAdj)wOut.Add(wOut, wOutAdj)bOutAdj, err := sumAxis(0, dOutput)if err != nil {return err}bOutAdj.Scale(nn.config.learningRate, bOutAdj)bOut.Add(bOut, bOutAdj)wHiddenAdj := &mat.Dense{}wHiddenAdj.Mul(x.T(), dHiddenLayer)wHiddenAdj.Scale(nn.config.learningRate, wHiddenAdj)wHidden.Add(wHidden, wHiddenAdj)bHiddenAdj, err := sumAxis(0, dHiddenLayer)if err != nil {return err}bHiddenAdj.Scale(nn.config.learningRate, bHiddenAdj)bHidden.Add(bHidden, bHiddenAdj)

其中函数 sumAxis 用于根据维度对矩阵求和:

func sumAxis(axis int, m *mat.Dense) (*mat.Dense, error) {numRows, numCols := m.Dims()// println(numRows, numCols)var output *mat.Denseswitch axis {case 0:result := make([]float64, numCols)for i := 0; i < numCols; i++ {col := mat.Col(nil, i, m)result[i] = floats.Sum(col)}output = mat.NewDense(1, numCols, result)case 1:result := make([]float64, numRows)for i := 0; i < numRows; i++ {row := mat.Row(nil, i, m)result[i] = floats.Sum(row)}output = mat.NewDense(numRows, 1, result)default:return nil, errors.New("invalid axis, must be 0 or 1")}return output, nil
}

2.4 评估神经网络

网络的评估,首先需要利用测试数据集使用前向计算,获得网络的输出,然后和测试数据集中的标签进行对比。前向计算过程与训练过程类似,这里仅介绍如何进行评估:

 // 使用经过训练的模型进行预测predictions, err := network.predict(testInputs)if err != nil {log.Fatal(err)}// 计算模型预测准确率var truePred intnumPreds, _ := predictions.Dims()for i := 0; i < numPreds; i++ {// 获取标签labelRow := mat.Row(nil, i, testLabels)var species intfor idx, label := range labelRow {// println(idx, label)if label == 1.0 {// println(idx)category = idxbreak}}// 计算预测正确的个数if predictions.At(i, category) == floats.Max(mat.Row(nil, i, predictions)) {// for j:= 0; j < 10; j++ {//      println(j, mat.Row(nil, i, predictions)[j])// }truePred++}}// 计算准确率accuracy := float64(truePred) / float64(numPreds)// 输出准确率fmt.Printf("\nAccuracy = %0.2f\n\n", accuracy)

3. 程序运行

最后就是运行程序,检测模型运行效果的时候了,在命令行中使用以下命令运行程序:

gop run mnist_recognition.go

程序输出结果如下所示:

Accuracy = 0.89

考虑到仅使用了一层隐藏层,可以获得接近 90% 的准确率,已经超出了基线水平了。

后记

改进神经网络的方法有很多,包括使用不同的神经网络模型、加深神经网络、使用不同的损失函数、修改激活函数等等,通过之后的 Go+ 学习,再继续完善改进此网络。

我的Go+语言初体验——Go+语言构建神经网络实战手写数字识别相关推荐

  1. MindSpore手写数字识别初体验,深度学习也没那么神秘嘛

    摘要:想了解深度学习却又无从下手,不如从手写数字识别模型训练开始吧! 深度学习作为机器学习分支之一,应用日益广泛.语音识别.自动机器翻译.即时视觉翻译.刷脸支付.人脸考勤--不知不觉,深度学习已经渗入 ...

  2. 【人工智能】利用C语言实现KNN算法进行手写数字识别

    KNN算法称为邻近算法,或者说K最近邻(kNN,k-NearestNeighbor)分类算法.所谓K最近邻,就是k个最近的邻居的意思,说的是每个样本都可以用它最接近的k个邻居来代表. kNN算法的核心 ...

  3. opencl 加速 c语言程序_在AlveoU200加速卡上实现简单手写数字识别

    最近实验室租了块xilinx家的AlveoU200加速卡,过去几天被这块板吸引了注意力.刚开始了解,做点什么来试试水呢?一想,可以把曾经学 @蔡宇杰 大佬在pynq-z2上做的那个手写数字识别工程在这 ...

  4. MindSpore手写数字识别体验

    文章目录 1. 环境准备 2. 安装minspore及其套件 3. 程序撰写 4. 总结 今天带大家体验一下 MindSpore 这个 AI 框架来完成手写数字识别的任务 1. 环境准备 使用Anac ...

  5. 手写数字识别c语言作业,10 行代码,实现手写数字识别

    识别手写的阿拉伯数字,对于人类来说十分简单,但是对于程序来说还是有些复杂的. 不过随着机器学习技术的普及,使用10几行代码,实现一个能够识别手写数字的程序,并不是一件难事.这是因为有太多的机器学习模型 ...

  6. CVNLP基础6之手写数字识别代码体验

    文章目录 总流程(思路)预览 x是输入的图片y是图片对应的label 关于训练数据集的说明 搭建计算网络层 计算损失值loss 优化损失值loss(minimize loss) 手写数字初体验代码 代 ...

  7. C语言底层搭建CNN实现MNIST手写数字识别

    手写数字识别 手写数字识别是指使用计算机自动识别手写体阿拉伯数字的技术.作为光学字符识别OCR的一个分支,它可以被广泛应用到手写数据的自动录入场景中.传统的识别方法如最近邻算法k-NN.支持向量机SV ...

  8. 我的Go+语言初体验——iPad上搭建Go+开发环境(ish版)

    目录 欢迎来到用iPad来学习Go+之旅 一.在 iPad 安装 Go+ 前的准备 1. iSH Shell 的作用 2. 安装 iSH Shell 3. 在 iSH 里安装一些常用软件 apk 命令 ...

  9. 我的Go+语言初体验——go【Format】goplus

    欢迎大家参与[我的Go+语言初体验]活动: 活动地址:[https://bbs.csdn.net/topics/603464006?utm_source=1594742339] 前言: goplus的 ...

最新文章

  1. Codeforces Round #406 (Div. 1) B. Legacy(线段树上优化建图)
  2. 有用的SAP System Administration T-CODE
  3. mysql 1005 错误
  4. 计算机精英协会考核题 —— 第三题:斐波那契数
  5. android中设置菜单栏,android – 菜单项没有显示在操作栏
  6. SharePoint2013升级SP1后,运行配置向导报错:未注册sharepoint服务
  7. cogs62 [HNOI2004] 宠物收养所
  8. Android开发笔记(六十五)多样的菜单
  9. 单机版kubernetes1.13安装
  10. HDU4681 String(dp)
  11. L2-004 搜索树判断 (25 point(s))
  12. 【动手学深度学习】01 Windows下安装环境
  13. 安装谷歌浏览器特定版本后禁止自动更新
  14. win10没有realtek高清晰音频管理器_Win10如何让电脑睡眠不断网?电脑睡眠状态不断网继续下载的方法...
  15. 为国内软件质量呐喊:《2021年国内质量调查报告》发布
  16. 小猪佩奇粉红猪 成年人重新拾起英文笔记
  17. java获取中文拼音或拼音首字母
  18. 【动手学深度学习】Task05笔记汇总
  19. windows在此计算机上找不到系统映象,笔记本电脑没有系统映像怎么办
  20. 考计算机二级需要学哪些,考计算机二级需要学哪些内容

热门文章

  1. MySQL: 查看一次SQL的执行时间都花在哪些环节上
  2. 微信 input 照相机 呼出
  3. javaScript = == ===的区别
  4. SATA SAS SSD 硬盘介绍和评测
  5. Codeforces Round #295 (Div. 1) C. Pluses everywhere
  6. iOS 创建推送证书
  7. Ruby on Rails,创建模型,附赠模型与表名不一致时的解决方法
  8. Get_HD_Serial() 获得磁盘驱动器序列号
  9. 已解决——pycharm在同目录下import,pycharm会提示错误,但是可以运行
  10. [转载] Python学习之numpy函数 all()和any()比较矩阵