人工智慧神經網路( 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股市場存在誤解,或者就是個純騙子,三者必居其一。