Golang实现机器学习:使用Gorgonia构建神经网络模型
机器学习是当下最为热门的技术领域之一。而实现机器学习最重要的一步就是构建模型。Gorgonia是一个基于Go语言的神经网络库,可以帮助开发者更加便捷地构建神经网络模型。
在本文中,我们将使用Gorgonia构建一个用于手写数字识别的神经网络模型。本文着重介绍神经网络模型的构建过程,并详细解释其中的技术知识点。
1.准备工作
在开始构建神经网络模型之前,我们需要准备好环境。首先,需要安装Gorgonia库。可以通过以下命令进行安装:
```go get gorgonia.org/gorgonia```
安装好之后,我们需要先定义数据的输入和输出。在本例中,我们将使用手写数字数据集Mnist。Mnist数据集一共有60,000个训练数据和10,000个测试数据,每个数据都是28x28的灰度图像。我们希望神经网络能够根据这些图像输出对应的数字。
```go
//定义输入和输出
var (
inputSize = 28 * 28
outputSize = 10
)
```
2.构建神经网络模型
定义好输入和输出之后,我们开始构建神经网络模型。在本例中,我们将使用一个简单的全连接神经网络模型。模型的输入层有28x28个神经元,输出层有10个神经元。
```go
//构建神经网络模型
func buildModel(g *gorgonia.ExprGraph) (w0, w1, b0, b1 *gorgonia.Node) {
//输入层有28*28个神经元
input := gorgonia.NewMatrix(g, tensor.Float64, gorgonia.WithShape(-1, inputSize), gorgonia.WithName("x"))
//标签
labels := gorgonia.NewMatrix(g, tensor.Float64, gorgonia.WithShape(-1, outputSize), gorgonia.WithName("y"))
//隐藏层有256个神经元
hiddenSize := 256
//第一层权重
w0 = gorgonia.NewMatrix(g, tensor.Float64, gorgonia.WithShape(inputSize, hiddenSize), gorgonia.WithName("w0"))
//第二层权重
w1 = gorgonia.NewMatrix(g, tensor.Float64, gorgonia.WithShape(hiddenSize, outputSize), gorgonia.WithName("w1"))
//第一层偏置
b0 = gorgonia.NewMatrix(g, tensor.Float64, gorgonia.WithShape(1, hiddenSize), gorgonia.WithName("b0"))
//第二层偏置
b1 = gorgonia.NewMatrix(g, tensor.Float64, gorgonia.WithShape(1, outputSize), gorgonia.WithName("b1"))
//第一层
var h0, h1 *gorgonia.Node
gorgonia.Mul(input, w0, &h0)
gorgonia.Add(h0, b0, &h0)
gorgonia.Rectify(h0)
//输出层
gorgonia.Mul(h0, w1, &h1)
gorgonia.Add(h1, b1, &h1)
gorgonia.SoftMax(h1)
//损失函数
cost := gorgonia.Must(gorgonia.Neg(gorgonia.Must(gorgonia.Mean(gorgonia.Must(gorgonia.Sum(gorgonia.Must(gorgonia.HadamardProd(labels, gorgonia.Must(gorgonia.Log(h1))))))))))
//记录输出,方便后面预测结果
gorgonia.Read(h1, &output)
//训练误差
var loss *gorgonia.Node
gorgonia.Mean(gorgonia.Must(gorgonia.Norm(gorgonia.Must(gorgonia.Sub(h1, labels)), 2)), &loss)
//梯度下降
if _, err := gorgonia.Grad(cost, w0, w1, b0, b1); err != nil {
log.Fatal(err)
}
return w0, w1, b0, b1
}
```
3.训练模型
构建好神经网络模型之后,我们需要训练模型以提高准确率。在本例中,我们将使用随机梯度下降算法进行训练。随机梯度下降算法是一种常用的优化算法,用来找到损失函数的最小值。在每次迭代中,算法会计算模型预测值与真实值之间的误差,并通过反向传播算法更新模型参数。
```go
//训练神经网络模型
func trainModel(model *gorgonia.ExprGraph, machine *gorgonia.VM, w0, w1, b0, b1 *gorgonia.Node, trainData []*mnist.RawImage, trainLabels []*mnist.RawImage, batchSize int, epochs int) {
//批量梯度下降的批量大小
var iterations = len(trainData) / batchSize
//定义随机梯度下降优化器
var sgdSolver = &gorgonia.SGD{LearnerRate: 0.1}
//训练模型
for i := 0; i < epochs; i++ {
var loss float64
for j := 0; j < iterations; j++ {
startIdx := j * batchSize
endIdx := (j + 1) * batchSize
if endIdx > len(trainData) {
endIdx = len(trainData)
}
//构建输入数据
inputVals := make([]float64, (endIdx-startIdx)*inputSize)
labelVals := make([]float64, (endIdx-startIdx)*outputSize)
idx := 0
for k := startIdx; k < endIdx; k++ {
img := trainData[k]
label := trainLabels[k]
for _, p := range img.Data {
inputVals[idx] = float64(p) / 255.0
idx++
}
for _, p := range label.Data {
labelVals[idx-outputSize] = float64(p)
idx++
}
}
inputs := gorgonia.NewMatrix(machine, tensor.Float64, gorgonia.WithShape(endIdx-startIdx, inputSize), gorgonia.WithBacking(inputVals))
labels := gorgonia.NewMatrix(machine, tensor.Float64, gorgonia.WithShape(endIdx-startIdx, outputSize), gorgonia.WithBacking(labelVals))
//定义损失函数
_, err := machine.RunAll()
if err != nil {
log.Fatal("Failed to run the machine ", err)
}
//反向传播算法更新参数
if err = sgdSolver.Step(gorgonia.NodesToValueGrads(w0, w1, b0, b1)); err != nil {
log.Fatal("Failed to update parameters ", err)
}
loss += lossVal.Data().(float64)
}
fmt.Println("Epoch ", i+1, " completed. Loss = ", loss/float64(iterations))
}
}
```
4.预测结果
训练好模型之后,我们可以用它来预测手写数字图像所代表的数字。在本例中,我们将使用测试集中的图像进行预测,并将结果与实际值进行比较。
```go
//预测结果
func predict(model *gorgonia.ExprGraph, machine *gorgonia.VM, w0, w1, b0, b1 *gorgonia.Node, testData []*mnist.RawImage, testLabels []*mnist.RawImage) {
var correct int
for i, img := range testData {
label := testLabels[i]
inputVals := make([]float64, inputSize)
idx := 0
for _, p := range img.Data {
inputVals[idx] = float64(p) / 255.0
idx++
}
inputs := gorgonia.NewMatrix(machine, tensor.Float64, gorgonia.WithShape(1, inputSize), gorgonia.WithBacking(inputVals))
//前向传播
_, err := machine.RunAll()
if err != nil {
log.Fatal(err)
}
//获取预测值
maxVal := -math.MaxFloat64
maxIdx := -1
fmt.Print("Predicted: ")
for j := 0; j < outputSize; j++ {
val := output.Data().(tensor.Shape)[j]
fmt.Print(val, " ")
if val > maxVal {
maxVal = val
maxIdx = j
}
}
fmt.Print(" Actual: ", label.Data[0], "\n")
if maxIdx == int(label.Data[0]) {
correct++
}
}
fmt.Println("Accuracy: ", float64(correct)/float64(len(testData))*100, "%")
}
```
5.完整代码
```go
package main
import (
"fmt"
"log"
"math"
"math/rand"
"time"
"gorgonia.org/gorgonia"
"gorgonia.org/tensor"
"github.com/petar/GoMNIST"
)
var (
inputSize = 28 * 28
outputSize = 10
output *gorgonia.Node
)
func buildModel(g *gorgonia.ExprGraph) (w0, w1, b0, b1 *gorgonia.Node) {
input := gorgonia.NewMatrix(g, tensor.Float64, gorgonia.WithShape(-1, inputSize), gorgonia.WithName("x"))
labels := gorgonia.NewMatrix(g, tensor.Float64, gorgonia.WithShape(-1, outputSize), gorgonia.WithName("y"))
hiddenSize := 256
w0 = gorgonia.NewMatrix(g, tensor.Float64, gorgonia.WithShape(inputSize, hiddenSize), gorgonia.WithName("w0"))
w1 = gorgonia.NewMatrix(g, tensor.Float64, gorgonia.WithShape(hiddenSize, outputSize), gorgonia.WithName("w1"))
b0 = gorgonia.NewMatrix(g, tensor.Float64, gorgonia.WithShape(1, hiddenSize), gorgonia.WithName("b0"))
b1 = gorgonia.NewMatrix(g, tensor.Float64, gorgonia.WithShape(1, outputSize), gorgonia.WithName("b1"))
var h0, h1 *gorgonia.Node
gorgonia.Mul(input, w0, &h0)
gorgonia.Add(h0, b0, &h0)
gorgonia.Rectify(h0)
gorgonia.Mul(h0, w1, &h1)
gorgonia.Add(h1, b1, &h1)
gorgonia.SoftMax(h1)
cost := gorgonia.Must(gorgonia.Neg(gorgonia.Must(gorgonia.Mean(gorgonia.Must(gorgonia.Sum(gorgonia.Must(gorgonia.HadamardProd(labels, gorgonia.Must(gorgonia.Log(h1))))))))))
gorgonia.Read(h1, &output)
var loss *gorgonia.Node
gorgonia.Mean(gorgonia.Must(gorgonia.Norm(gorgonia.Must(gorgonia.Sub(h1, labels)), 2)), &loss)
if _, err := gorgonia.Grad(cost, w0, w1, b0, b1); err != nil {
log.Fatal(err)
}
return w0, w1, b0, b1
}
func trainModel(model *gorgonia.ExprGraph, machine *gorgonia.VM, w0, w1, b0, b1 *gorgonia.Node, trainData []*mnist.RawImage, trainLabels []*mnist.RawImage, batchSize int, epochs int) {
var iterations = len(trainData) / batchSize
var sgdSolver = &gorgonia.SGD{LearnerRate: 0.1}
for i := 0; i < epochs; i++ {
var loss float64
for j := 0; j < iterations; j++ {
startIdx := j * batchSize
endIdx := (j + 1) * batchSize
if endIdx > len(trainData) {
endIdx = len(trainData)
}
inputVals := make([]float64, (endIdx-startIdx)*inputSize)
labelVals := make([]float64, (endIdx-startIdx)*outputSize)
idx := 0
for k := startIdx; k < endIdx; k++ {
img := trainData[k]
label := trainLabels[k]
for _, p := range img.Data {
inputVals[idx] = float64(p) / 255.0
idx++
}
for _, p := range label.Data {
labelVals[idx-outputSize] = float64(p)
idx++
}
}
inputs := gorgonia.NewMatrix(machine, tensor.Float64, gorgonia.WithShape(endIdx-startIdx, inputSize), gorgonia.WithBacking(inputVals))
labels := gorgonia.NewMatrix(machine, tensor.Float64, gorgonia.WithShape(endIdx-startIdx, outputSize), gorgonia.WithBacking(labelVals))
_, err := machine.RunAll()
if err != nil {
log.Fatal("Failed to run the machine ", err)
}
if err = sgdSolver.Step(gorgonia.NodesToValueGrads(w0, w1, b0, b1)); err != nil {
log.Fatal("Failed to update parameters ", err)
}
loss += lossVal.Data().(float64)
}
fmt.Println("Epoch ", i+1, " completed. Loss = ", loss/float64(iterations))
}
}
func predict(model *gorgonia.ExprGraph, machine *gorgonia.VM, w0, w1, b0, b1 *gorgonia.Node, testData []*mnist.RawImage, testLabels []*mnist.RawImage) {
var correct int
for i, img := range testData {
label := testLabels[i]
inputVals := make([]float64, inputSize)
idx := 0
for _, p := range img.Data {
inputVals[idx] = float64(p) / 255.0
idx++
}
inputs := gorgonia.NewMatrix(machine, tensor.Float64, gorgonia.WithShape(1, inputSize), gorgonia.WithBacking(inputVals))
_, err := machine.RunAll()
if err != nil {
log.Fatal(err)
}
maxVal := -math.MaxFloat64
maxIdx := -1
fmt.Print("Predicted: ")
for j := 0; j < outputSize; j++ {
val := output.Data().(tensor.Shape)[j]
fmt.Print(val, " ")