关于机器学习:动手造轮子自己实现人工智能神经网络ANN解决鸢尾花分类问题Golang118实现

4次阅读

共计 17631 个字符,预计需要花费 45 分钟才能阅读完成。

人工智能神经网络(Artificial Neural Network,又称为 ANN)是一种由人工神经元组成的网络结构,神经网络构造是所有机器学习的根本构造,换句话说,无论是深度学习还是强化学习都是基于神经网络构造进行构建。对于人工神经元,请参见:人工智能机器学习底层原理分析, 人造神经元, 您肯定能看懂, 艰深解释把 AI“黑话”转化为“白话文”。

机器学习能够解决什么问题

机器学习能够帮忙咱们解决两大类问题:回归问题和分类问题,它们的次要区别在于输入变量的类型和预测指标的不同。

在回归问题中,输入变量是间断值,预测指标是预测一个数值。例如,预测房价、预测销售额等都是回归问题。通常应用回归模型,如线性回归、决策树回归、神经网络回归等来解决这类问题。回归问题的评估指标通常是均方误差(Mean Squared Error,MSE)、均匀绝对误差(Mean Absolute Error,MAE)等。

在分类问题中,输入变量是离散值,预测指标是将样本划分到不同的类别中。例如,预测邮件是否是垃圾邮件、预测图像中的物体类别等都是分类问题。通常应用分类模型,如逻辑回归、决策树分类、反对向量机、神经网络分类等来解决这类问题。分类问题的评估指标通常是准确率、精度(Precision)、召回率(Recall)等。

事实上,机器学习只能解决“能够”被解决的问题,也就是说,机器学习能帮咱们做的是进步解决问题的效率,而不是解决咱们原本解决不了的问题,说白了,机器学习只能解决人目前能解决的问题,比如说人当初不能做什么?人不能永生,不能白日飞升,也不能治愈绝症,所以你指望机器学习解决此类问题,就是痴心妄想。

同时,机器学习输出的特征参数和输入的预期后果必须有逻辑相关性,什么意思?比如说咱们想预测房价,后果特征参数输出了很多没有任何逻辑相关性的数据,比方历年水稻的出产率,这就是没有逻辑相关性的数据,这样的问题再怎么调参也是无奈通过机器学习来解决的。

此外,回归问题中有一个畛域十分引人关注,那就是预测股票价格,国内常常有人说本人训练的模型能够预测某支 A 股的价格走势,甚至能够精准到具体价格单位。说实话,挺滑稽的,要害是还真有人置信靠机器学习能在 A 股市场大杀特杀。

因为,略微有点投资教训的人都晓得,股票的历史数据和将来某个工夫点或者某个时间段的理论价格,并不存在因果关系,尤其像 A 股市场这种可被操控的黑盒环境,连具体特色都是暗藏的,或者说特色是什么都是未知的,你认为的特色只是你认为的,并不是市场或者政策认为的,所以你输出之前十年或者二十年的历史股票数据,你让它预测,就是在搞笑,机器学习没法帮你解决此类问题。

为什么当初 GPT 模型当初这么火?是因为它在 NLP(自然语言剖析)畛域有了质的冲破,能够通过大数据模型分割上下文关系生成可信度高的答复,而这个上下文关系,就是咱们所谓的参数和预期后果的因果关系。

鸢尾花分类问题

鸢尾花分类问题是一个经典的机器学习问题,也是神经网络入门的罕用案例之一。它的指标是通过鸢尾花的花萼长度、花萼宽度、花瓣长度和花瓣宽度这四个特色来预测鸢尾花的种类,分为三种:山鸢尾(Iris Setosa)、变色鸢尾(Iris Versicolour)和维吉尼亚鸢尾(Iris Virginica)。

艰深来讲,就是咱们要训练一个神经网络模型,它可能依据鸢尾花的四个特色,主动地对鸢尾花的种类进行分类。

在这个案例中,咱们应用了一个蕴含一个暗藏层的神经网络,它的输出层有 4 个神经元,代表鸢尾花的 4 个特色;暗藏层有 3 个神经元;输入层有 3 个神经元,别离代表 3 种鸢尾花的种类:

由此可见,神经网络通常由三层组成:输出层、暗藏层和输入层。

输出层:输出层接管内部输出信号,是神经网络的终点。它的神经元数量与输出特色的数量雷同,每个神经元代表一个输出特色。输出层的次要作用是将内部输出转换为神经网络外部的信号。

暗藏层:暗藏层位于输出层和输入层之间,是神经网络的外围局部。它的神经元数量能够依据问题的复杂度自在设定,每个神经元接管上一层神经元输入的信号,并进行加权解决和激活函数解决,再将后果传递给下一层神经元。暗藏层的次要作用是对输出信号进行简单的非线性转换,提取出输出信号中的特色,从而使得神经网络可能对简单的问题进行解决。

输入层:输入层是神经网络的起点,它的神经元数量通常与问题的输入数量雷同。每个神经元代表一个输入后果,输入层的次要作用是将暗藏层解决后的信号进行进一步解决,并将最终的后果输入。

在神经网络中,输出信号从输出层开始,通过暗藏层的解决,最终达到输入层。每一层的神经元都与下一层的神经元相连,它们之间的连贯能够看成是一种权重关系,权重值代表了两个神经元之间的相关性强度。当神经网络接管到输出信号后,每个神经元都会对这些信号进行加权解决,并通过激活函数将后果输入给下一层神经元,最终造成输入后果。通过一直调整权重和激活函数,神经网络能够学习到输出和输入之间的简单非线性关系,从而对未知数据进行预测和分类等工作。

定义神经网络构造体

在开始训练之前,咱们先定义一些须要的构造体和函数:

// neuralNet contains all of the information  
// that defines a trained neural network.  
type neuralNet struct {  
    config  neuralNetConfig  
    wHidden *mat.Dense  
    bHidden *mat.Dense  
    wOut    *mat.Dense  
    bOut    *mat.Dense  
}  
  
// neuralNetConfig defines our neural network  
// architecture and learning parameters.  
type neuralNetConfig struct {  
    inputNeurons  int  
    outputNeurons int  
    hiddenNeurons int  
    numEpochs     int  
    learningRate  float64  
}

这里 neuralNet 是神经网络构造体,同时定义输出、暗藏和输入层神经元的配置。

随后申明函数初始化神经网络:

func newNetwork(config neuralNetConfig) *neuralNet {return &neuralNet{config: config}  
}

这里返回神经网络的指针。

除此之外,咱们还须要定义激活函数及其导数,这是在反向流传过程中须要应用的。激活函数有很多抉择,但在这里咱们将应用 sigmoid 函数。这个函数有很多长处,包含概率解释和不便的导数表达式:

// sigmoid implements the sigmoid function  
// for use in activation functions.  
func sigmoid(x float64) float64 {return 1.0 / (1.0 + math.Exp(-x))  
}  
  
// sigmoidPrime implements the derivative  
// of the sigmoid function for backpropagation.  
func sigmoidPrime(x float64) float64 {return sigmoid(x) * (1.0 - sigmoid(x))  
}

实现反向流传

反向流传是指在前向流传之后,计算神经网络误差并将误差反向流传到各层神经元中进行参数(包含权重和偏置)的更新。在反向流传过程中,首先须要计算网络的误差,而后通过链式法则将误差反向流传到各层神经元,以更新每个神经元的权重和偏置。这个过程也被称为“反向梯度降落”,因为它是通过梯度降落算法来更新神经网络参数的。

说白了,反向流传就是逆运算,用后果反推过程,这里咱们能够编写一个实现反向流传办法的办法,用于训练或优化咱们网络的权重和偏置。反向流传办法包含以下步骤:

1 初始化权重和偏置(例如,随机初始化)。

2 将训练数据输出神经网络中进行前馈,以生成输入。

3 将输入与正确输入进行比拟,以获取误差。

4 基于误差计算权重和偏置的变动。

5 将变动通过神经网络进行反向流传。

对于给定的迭代次数或满足进行条件时,反复步骤 2 -5。

在步骤 3 - 5 中,咱们将利用随机梯度降落(SGD)来确定权重和偏置的更新:

// train trains a neural network using backpropagation.  
func (nn *neuralNet) train(x, y *mat.Dense) error {  
  
    // Initialize biases/weights.  
    randSource := rand.NewSource(time.Now().UnixNano())  
    randGen := rand.New(randSource)  
  
    wHidden := mat.NewDense(nn.config.inputNeurons, nn.config.hiddenNeurons, nil)  
    bHidden := mat.NewDense(1, nn.config.hiddenNeurons, nil)  
    wOut := mat.NewDense(nn.config.hiddenNeurons, nn.config.outputNeurons, nil)  
    bOut := mat.NewDense(1, nn.config.outputNeurons, nil)  
  
    wHiddenRaw := wHidden.RawMatrix().Data  
    bHiddenRaw := bHidden.RawMatrix().Data  
    wOutRaw := wOut.RawMatrix().Data  
    bOutRaw := bOut.RawMatrix().Data  
  
    for _, param := range [][]float64{  
        wHiddenRaw,  
        bHiddenRaw,  
        wOutRaw,  
        bOutRaw,  
    } {  
        for i := range param {param[i] = randGen.Float64()}  
    }  
  
    // Define the output of the neural network.  
    output := new(mat.Dense)  
  
    // Use backpropagation to adjust the weights and biases.  
    if err := nn.backpropagate(x, y, wHidden, bHidden, wOut, bOut, output); err != nil {return err}  
  
    // Define our trained neural network.  
    nn.wHidden = wHidden  
    nn.bHidden = bHidden  
    nn.wOut = wOut  
    nn.bOut = bOut  
  
    return nil  
}

接着实现具体的反向流传逻辑:

// backpropagate completes the backpropagation method.  
func (nn *neuralNet) backpropagate(x, y, wHidden, bHidden, wOut, bOut, output *mat.Dense) error {  
  
    // Loop over the number of epochs utilizing  
    // backpropagation to train our model.  
    for i := 0; i < nn.config.numEpochs; i++ {  
  
        // Complete the feed forward process.  
        hiddenLayerInput := new(mat.Dense)  
        hiddenLayerInput.Mul(x, wHidden)  
        addBHidden := func(_, col int, v float64) float64 {return v + bHidden.At(0, col) }  
        hiddenLayerInput.Apply(addBHidden, hiddenLayerInput)  
  
        hiddenLayerActivations := new(mat.Dense)  
        applySigmoid := func(_, _ int, v float64) float64 {return sigmoid(v) }  
        hiddenLayerActivations.Apply(applySigmoid, hiddenLayerInput)  
  
        outputLayerInput := new(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)  
  
        // Complete the backpropagation.  
        networkError := new(mat.Dense)  
        networkError.Sub(y, output)  
  
        slopeOutputLayer := new(mat.Dense)  
        applySigmoidPrime := func(_, _ int, v float64) float64 {return sigmoidPrime(v) }  
        slopeOutputLayer.Apply(applySigmoidPrime, output)  
        slopeHiddenLayer := new(mat.Dense)  
        slopeHiddenLayer.Apply(applySigmoidPrime, hiddenLayerActivations)  
  
        dOutput := new(mat.Dense)  
        dOutput.MulElem(networkError, slopeOutputLayer)  
        errorAtHiddenLayer := new(mat.Dense)  
        errorAtHiddenLayer.Mul(dOutput, wOut.T())  
  
        dHiddenLayer := new(mat.Dense)  
        dHiddenLayer.MulElem(errorAtHiddenLayer, slopeHiddenLayer)  
  
        // Adjust the parameters.  
        wOutAdj := new(mat.Dense)  
        wOutAdj.Mul(hiddenLayerActivations.T(), dOutput)  
        wOutAdj.Scale(nn.config.learningRate, wOutAdj)  
        wOut.Add(wOut, wOutAdj)  
  
        bOutAdj, err := sumAlongAxis(0, dOutput)  
        if err != nil {return err}  
        bOutAdj.Scale(nn.config.learningRate, bOutAdj)  
        bOut.Add(bOut, bOutAdj)  
  
        wHiddenAdj := new(mat.Dense)  
        wHiddenAdj.Mul(x.T(), dHiddenLayer)  
        wHiddenAdj.Scale(nn.config.learningRate, wHiddenAdj)  
        wHidden.Add(wHidden, wHiddenAdj)  
  
        bHiddenAdj, err := sumAlongAxis(0, dHiddenLayer)  
        if err != nil {return err}  
        bHiddenAdj.Scale(nn.config.learningRate, bHiddenAdj)  
        bHidden.Add(bHidden, bHiddenAdj)  
    }  
  
    return nil  
}

接着申明一个工具函数,它帮忙咱们沿一个矩阵维度求和,同时放弃另一个维度不变:

// sumAlongAxis sums a matrix along a particular dimension,   
// preserving the other dimension.  
func sumAlongAxis(axis int, m *mat.Dense) (*mat.Dense, error) {numRows, numCols := m.Dims()  
  
        var output *mat.Dense  
  
        switch axis {  
        case 0:  
                data := make([]float64, numCols)  
                for i := 0; i < numCols; i++ {col := mat.Col(nil, i, m)  
                        data[i] = floats.Sum(col)  
                }  
                output = mat.NewDense(1, numCols, data)  
        case 1:  
                data := make([]float64, numRows)  
                for i := 0; i < numRows; i++ {row := mat.Row(nil, i, m)  
                        data[i] = floats.Sum(row)  
                }  
                output = mat.NewDense(numRows, 1, data)  
        default:  
                return nil, errors.New("invalid axis, must be 0 or 1")  
        }  
  
        return output, nil  
}

实现前向流传进行预测

在训练完咱们的神经网络之后,咱们心愿应用它进行预测。为此,咱们只须要将一些给定的鸢尾花特征值输出到网络中进行前向流传,用来生成输入。

有点像反向流传逻辑,不同之处在于,这里咱们将返回生成的输入:

// predict makes a prediction based on a trained  
// neural network.  
func (nn *neuralNet) predict(x *mat.Dense) (*mat.Dense, error) {  
  
    // Check to make sure that our neuralNet value  
    // represents a trained model.  
    if nn.wHidden == nil || nn.wOut == nil {return nil, errors.New("the supplied weights are empty")  
    }  
    if nn.bHidden == nil || nn.bOut == nil {return nil, errors.New("the supplied biases are empty")  
    }  
  
    // Define the output of the neural network.  
    output := new(mat.Dense)  
  
    // Complete the feed forward process.  
    hiddenLayerInput := new(mat.Dense)  
    hiddenLayerInput.Mul(x, nn.wHidden)  
    addBHidden := func(_, col int, v float64) float64 {return v + nn.bHidden.At(0, col) }  
    hiddenLayerInput.Apply(addBHidden, hiddenLayerInput)  
  
    hiddenLayerActivations := new(mat.Dense)  
    applySigmoid := func(_, _ int, v float64) float64 {return sigmoid(v) }  
    hiddenLayerActivations.Apply(applySigmoid, hiddenLayerInput)  
  
    outputLayerInput := new(mat.Dense)  
    outputLayerInput.Mul(hiddenLayerActivations, nn.wOut)  
    addBOut := func(_, col int, v float64) float64 {return v + nn.bOut.At(0, col) }  
    outputLayerInput.Apply(addBOut, outputLayerInput)  
    output.Apply(applySigmoid, outputLayerInput)  
  
    return output, nil  
}

筹备特色和冀望数据

上面咱们须要筹备鸢尾花的特色和冀望数据,能够在加州大学官网下载:https://archive.ics.uci.edu/ml/datasets/iris

这里蕴含花瓣和花蕊的具体数据,以及这些样本所对应的花的品种,别离对应上文提到的山鸢尾(Iris Setosa)、维吉尼亚鸢尾(Iris Virginica)和 变色鸢尾(Iris Versicolour),留神鸢尾花品种程序分先后,别离对应上表中的数据。

开始训练

训练之前,须要装置基于 Golang 的浮点库:

go get gonum.org/v1/gonum/floats

装置后之后,编写脚本:

package main  
  
import (  
    "encoding/csv"  
    "errors"  
    "fmt"  
    "log"  
    "math"  
    "math/rand"  
    "os"  
    "strconv"  
    "time"  
  
    "gonum.org/v1/gonum/floats"  
    "gonum.org/v1/gonum/mat"  
)  
  
// neuralNet contains all of the information  
// that defines a trained neural network.  
type neuralNet struct {  
    config  neuralNetConfig  
    wHidden *mat.Dense  
    bHidden *mat.Dense  
    wOut    *mat.Dense  
    bOut    *mat.Dense  
}  
  
// neuralNetConfig defines our neural network  
// architecture and learning parameters.  
type neuralNetConfig struct {  
    inputNeurons  int  
    outputNeurons int  
    hiddenNeurons int  
    numEpochs     int  
    learningRate  float64  
}  
  
func main() {  
  
    // Form the training matrices.  
    inputs, labels := makeInputsAndLabels("data/train.csv")  
  
    // Define our network architecture and learning parameters.  
    config := neuralNetConfig{  
        inputNeurons:  4,  
        outputNeurons: 3,  
        hiddenNeurons: 3,  
        numEpochs:     5000,  
        learningRate:  0.3,  
    }  
  
    // Train the neural network.  
    network := newNetwork(config)  
    if err := network.train(inputs, labels); err != nil {log.Fatal(err)  
    }  
  
    // Form the testing matrices.  
    testInputs, testLabels := makeInputsAndLabels("data/test.csv")  
  
    // Make the predictions using the trained model.  
    predictions, err := network.predict(testInputs)  
    if err != nil {log.Fatal(err)  
    }  
  
    // Calculate the accuracy of our model.  
    var truePosNeg int  
    numPreds, _ := predictions.Dims()  
    for i := 0; i < numPreds; i++ {  
  
        // Get the label.  
        labelRow := mat.Row(nil, i, testLabels)  
        var prediction int  
        for idx, label := range labelRow {  
            if label == 1.0 {  
                prediction = idx  
                break  
            }  
        }  
  
        // Accumulate the true positive/negative count.  
        if predictions.At(i, prediction) == floats.Max(mat.Row(nil, i, predictions)) {truePosNeg++}  
    }  
  
    // Calculate the accuracy (subset accuracy).  
    accuracy := float64(truePosNeg) / float64(numPreds)  
  
    // Output the Accuracy value to standard out.  
    fmt.Printf("\nAccuracy = %0.2f\n\n", accuracy)  
}  
  
// NewNetwork initializes a new neural network.  
func newNetwork(config neuralNetConfig) *neuralNet {return &neuralNet{config: config}  
}  
  
// train trains a neural network using backpropagation.  
func (nn *neuralNet) train(x, y *mat.Dense) error {  
  
    // Initialize biases/weights.  
    randSource := rand.NewSource(time.Now().UnixNano())  
    randGen := rand.New(randSource)  
  
    wHidden := mat.NewDense(nn.config.inputNeurons, nn.config.hiddenNeurons, nil)  
    bHidden := mat.NewDense(1, nn.config.hiddenNeurons, nil)  
    wOut := mat.NewDense(nn.config.hiddenNeurons, nn.config.outputNeurons, nil)  
    bOut := mat.NewDense(1, nn.config.outputNeurons, nil)  
  
    wHiddenRaw := wHidden.RawMatrix().Data  
    bHiddenRaw := bHidden.RawMatrix().Data  
    wOutRaw := wOut.RawMatrix().Data  
    bOutRaw := bOut.RawMatrix().Data  
  
    for _, param := range [][]float64{  
        wHiddenRaw,  
        bHiddenRaw,  
        wOutRaw,  
        bOutRaw,  
    } {  
        for i := range param {param[i] = randGen.Float64()}  
    }  
  
    // Define the output of the neural network.  
    output := new(mat.Dense)  
  
    // Use backpropagation to adjust the weights and biases.  
    if err := nn.backpropagate(x, y, wHidden, bHidden, wOut, bOut, output); err != nil {return err}  
  
    // Define our trained neural network.  
    nn.wHidden = wHidden  
    nn.bHidden = bHidden  
    nn.wOut = wOut  
    nn.bOut = bOut  
  
    return nil  
}  
  
// backpropagate completes the backpropagation method.  
func (nn *neuralNet) backpropagate(x, y, wHidden, bHidden, wOut, bOut, output *mat.Dense) error {  
  
    // Loop over the number of epochs utilizing  
    // backpropagation to train our model.  
    for i := 0; i < nn.config.numEpochs; i++ {  
  
        // Complete the feed forward process.  
        hiddenLayerInput := new(mat.Dense)  
        hiddenLayerInput.Mul(x, wHidden)  
        addBHidden := func(_, col int, v float64) float64 {return v + bHidden.At(0, col) }  
        hiddenLayerInput.Apply(addBHidden, hiddenLayerInput)  
  
        hiddenLayerActivations := new(mat.Dense)  
        applySigmoid := func(_, _ int, v float64) float64 {return sigmoid(v) }  
        hiddenLayerActivations.Apply(applySigmoid, hiddenLayerInput)  
  
        outputLayerInput := new(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)  
  
        // Complete the backpropagation.  
        networkError := new(mat.Dense)  
        networkError.Sub(y, output)  
  
        slopeOutputLayer := new(mat.Dense)  
        applySigmoidPrime := func(_, _ int, v float64) float64 {return sigmoidPrime(v) }  
        slopeOutputLayer.Apply(applySigmoidPrime, output)  
        slopeHiddenLayer := new(mat.Dense)  
        slopeHiddenLayer.Apply(applySigmoidPrime, hiddenLayerActivations)  
  
        dOutput := new(mat.Dense)  
        dOutput.MulElem(networkError, slopeOutputLayer)  
        errorAtHiddenLayer := new(mat.Dense)  
        errorAtHiddenLayer.Mul(dOutput, wOut.T())  
  
        dHiddenLayer := new(mat.Dense)  
        dHiddenLayer.MulElem(errorAtHiddenLayer, slopeHiddenLayer)  
  
        // Adjust the parameters.  
        wOutAdj := new(mat.Dense)  
        wOutAdj.Mul(hiddenLayerActivations.T(), dOutput)  
        wOutAdj.Scale(nn.config.learningRate, wOutAdj)  
        wOut.Add(wOut, wOutAdj)  
  
        bOutAdj, err := sumAlongAxis(0, dOutput)  
        if err != nil {return err}  
        bOutAdj.Scale(nn.config.learningRate, bOutAdj)  
        bOut.Add(bOut, bOutAdj)  
  
        wHiddenAdj := new(mat.Dense)  
        wHiddenAdj.Mul(x.T(), dHiddenLayer)  
        wHiddenAdj.Scale(nn.config.learningRate, wHiddenAdj)  
        wHidden.Add(wHidden, wHiddenAdj)  
  
        bHiddenAdj, err := sumAlongAxis(0, dHiddenLayer)  
        if err != nil {return err}  
        bHiddenAdj.Scale(nn.config.learningRate, bHiddenAdj)  
        bHidden.Add(bHidden, bHiddenAdj)  
    }  
  
    return nil  
}  
  
// predict makes a prediction based on a trained  
// neural network.  
func (nn *neuralNet) predict(x *mat.Dense) (*mat.Dense, error) {  
  
    // Check to make sure that our neuralNet value  
    // represents a trained model.  
    if nn.wHidden == nil || nn.wOut == nil {return nil, errors.New("the supplied weights are empty")  
    }  
    if nn.bHidden == nil || nn.bOut == nil {return nil, errors.New("the supplied biases are empty")  
    }  
  
    // Define the output of the neural network.  
    output := new(mat.Dense)  
  
    // Complete the feed forward process.  
    hiddenLayerInput := new(mat.Dense)  
    hiddenLayerInput.Mul(x, nn.wHidden)  
    addBHidden := func(_, col int, v float64) float64 {return v + nn.bHidden.At(0, col) }  
    hiddenLayerInput.Apply(addBHidden, hiddenLayerInput)  
  
    hiddenLayerActivations := new(mat.Dense)  
    applySigmoid := func(_, _ int, v float64) float64 {return sigmoid(v) }  
    hiddenLayerActivations.Apply(applySigmoid, hiddenLayerInput)  
  
    outputLayerInput := new(mat.Dense)  
    outputLayerInput.Mul(hiddenLayerActivations, nn.wOut)  
    addBOut := func(_, col int, v float64) float64 {return v + nn.bOut.At(0, col) }  
    outputLayerInput.Apply(addBOut, outputLayerInput)  
    output.Apply(applySigmoid, outputLayerInput)  
  
    return output, nil  
}  
  
// sigmoid implements the sigmoid function  
// for use in activation functions.  
func sigmoid(x float64) float64 {return 1.0 / (1.0 + math.Exp(-x))  
}  
  
// sigmoidPrime implements the derivative  
// of the sigmoid function for backpropagation.  
func sigmoidPrime(x float64) float64 {return sigmoid(x) * (1.0 - sigmoid(x))  
}  
  
// sumAlongAxis sums a matrix along a  
// particular dimension, preserving the  
// other dimension.  
func sumAlongAxis(axis int, m *mat.Dense) (*mat.Dense, error) {numRows, numCols := m.Dims()  
  
    var output *mat.Dense  
  
    switch axis {  
    case 0:  
        data := make([]float64, numCols)  
        for i := 0; i < numCols; i++ {col := mat.Col(nil, i, m)  
            data[i] = floats.Sum(col)  
        }  
        output = mat.NewDense(1, numCols, data)  
    case 1:  
        data := make([]float64, numRows)  
        for i := 0; i < numRows; i++ {row := mat.Row(nil, i, m)  
            data[i] = floats.Sum(row)  
        }  
        output = mat.NewDense(numRows, 1, data)  
    default:  
        return nil, errors.New("invalid axis, must be 0 or 1")  
    }  
  
    return output, nil  
}  
  
func makeInputsAndLabels(fileName string) (*mat.Dense, *mat.Dense) {  
    // Open the dataset file.  
    f, err := os.Open(fileName)  
    if err != nil {log.Fatal(err)  
    }  
    defer f.Close()  
  
    // Create a new CSV reader reading from the opened file.  
    reader := csv.NewReader(f)  
    reader.FieldsPerRecord = 7  
  
    // Read in all of the CSV records  
    rawCSVData, err := reader.ReadAll()  
    if err != nil {log.Fatal(err)  
    }  
  
    // inputsData and labelsData will hold all the  
    // float values that will eventually be  
    // used to form matrices.  
    inputsData := make([]float64, 4*len(rawCSVData))  
    labelsData := make([]float64, 3*len(rawCSVData))  
  
    // Will track the current index of matrix values.  
    var inputsIndex int  
    var labelsIndex int  
  
    // Sequentially move the rows into a slice of floats.  
    for idx, record := range rawCSVData {  
  
        // Skip the header row.  
        if idx == 0 {continue}  
  
        // Loop over the float columns.  
        for i, val := range record {  
  
            // Convert the value to a float.  
            parsedVal, err := strconv.ParseFloat(val, 64)  
            if err != nil {log.Fatal(err)  
            }  
  
            // Add to the labelsData if relevant.  
            if i == 4 || i == 5 || i == 6 {labelsData[labelsIndex] = parsedVal  
                labelsIndex++  
                continue  
            }  
  
            // Add the float value to the slice of floats.  
            inputsData[inputsIndex] = parsedVal  
            inputsIndex++  
        }  
    }  
    inputs := mat.NewDense(len(rawCSVData), 4, inputsData)  
    labels := mat.NewDense(len(rawCSVData), 3, labelsData)  
    return inputs, labels  
}

代码最初将测试集数据导入,并且开始进行预测:

// Form the testing matrices.  
    testInputs, testLabels := makeInputsAndLabels("data/test.csv")  
  
    fmt.Println(testLabels)  
  
    // Make the predictions using the trained model.  
    predictions, err := network.predict(testInputs)  
    if err != nil {log.Fatal(err)  
    }  
  
    // Calculate the accuracy of our model.  
    var truePosNeg int  
    numPreds, _ := predictions.Dims()  
    for i := 0; i < numPreds; i++ {  
  
        // Get the label.  
        labelRow := mat.Row(nil, i, testLabels)  
        var prediction int  
        for idx, label := range labelRow {  
            if label == 1.0 {  
                prediction = idx  
                break  
            }  
        }  
  
        // Accumulate the true positive/negative count.  
        if predictions.At(i, prediction) == floats.Max(mat.Row(nil, i, predictions)) {truePosNeg++}  
    }  
  
    // Calculate the accuracy (subset accuracy).  
    accuracy := float64(truePosNeg) / float64(numPreds)  
  
    // Output the Accuracy value to standard out.  
    fmt.Printf("\nAccuracy = %0.2f\n\n", accuracy)

程序输入:

&{{31 3 [0 1 0 1 0 0 1 0 0 0 1 0 0 1 0 0 0 1 1 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 0 1 1 0 0 0 0 1 0 0 1 0 0 1 0 0 1 0 1 0 0 0 1 1 0 0 1 0 0 0 1 0 1 0 0 0 0 1 0 0 1 1 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0] 3} 31 3}  
  
Accuracy = 0.97

能够看到,一共 31 个测试样本,只错了 3 次,成功率达到了 97%。

当然,就算是本人实现的小型神经网络,预测后果正确率也不可能达到 100%,因为机器学习也是基于概率学领域的学科。

为什么应用 Golang?

事实上,大部分人都存在这样一个刻板影响:机器学习必须要用 Python 来实现。就像前文所提到的,机器学习和 Python 语言并不存在因果关系,咱们应用 Golang 同样能够实现神经网络,同样能够实现机器学习的流程,编程语言,仅仅是实现的工具而已。

但不能否定的是,Python 以后在人工智能畛域的很多细分方向都有比拟宽泛的利用,比方自然语言解决、计算机视觉和机器学习等畛域,然而并不意味着人工智能研发肯定离不开 Python 语言,实际上很多其余编程语言也齐全能够代替 Python,比方 Java、C++、Golang 等等。

机器学习相干业务之所以大量应用 Python,是因为 Python 有着极其丰富的三方库进行反对,可能让研发人员把更多的精力放在算法设计和算法训练等方面,说白了,就是不必反复造轮子,进步研发团队整体产出的效率,比方面对基于 Python 的 Pytorch 和 Tensorflow 这两个颠扑不破的深度学习巨石重镇,Golang 就得败下阵来,没有任何劣势可言。

所以,单以人工智能生态圈的凋敝水平而论,Golang 还及不上 Python。

结语

至此,咱们就应用 Golang 实现了一个小型神经网络的实现,并且解决了一个实在存在的分类问题。那么,走完了整个流程,咱们应该对基于神经网络架构的机器学习过程有了一个大略的理解,那就是机器学习只能解决能够被解决的问题,有教训或者相干常识储备的人类通过肉眼也能辨认鸢尾花的品种,机器学习只是帮咱们进步了辨认效率而已,所以,如果还有人在你背后吹牛他可能用机器学习来预测 A 股价格赚大钱,那么,他可能对机器学习存在误会,或者可能对 A 股市场存在误会,或者就是个纯骗子,三者必居其一。

正文完
 0