// 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 } // 堆代码 duidaima.com // 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 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)) }实现反向传播
// 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 }
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++ } } // 堆代码 duidaima.com // 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%,因为机器学习也是基于概率学范畴的学科。