Golang新手问题:矩阵维度为零导致的panic错误 - 神经网络应用

Golang新手问题:矩阵维度为零导致的panic错误 - 神经网络应用 我正在学习O’Reilly的《使用Go进行机器学习》一书,目前处于初步阶段。在学习过程中,我遇到了一些困惑,不知道问题出在哪里。

我运行了训练神经网络的代码,但收到了一个警告,提示矩阵长度为零。我不明白为什么会这样,因为我正在神经网络的输出变量中使用那个零矩阵。

package main

import (
    "errors"
    "fmt"
    "log"
    "math"
    "math/rand"
    "time"

    "gonum.org/v1/gonum/floats"
    "gonum.org/v1/gonum/mat"
)

// 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
}

// 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 x * (1.0 - x)
}

// 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
}

// 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)

    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)

    // Define the output of the neural network.
    output := mat.NewDense(0, 0, nil)

    // 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 := mat.NewDense(0, 0, nil)
        hiddenLayerInput.Mul(x, wHidden)
        addBHidden := func(_, col int, v float64) float64 { return v + bHidden.At(0, col) }
        hiddenLayerInput.Apply(addBHidden, hiddenLayerInput)

        hiddenLayerActivations := mat.NewDense(0, 0, nil)
        applySigmoid := func(_, _ int, v float64) float64 { return sigmoid(v) }
        hiddenLayerActivations.Apply(applySigmoid, hiddenLayerInput)

        outputLayerInput := mat.NewDense(0, 0, nil)
        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 := mat.NewDense(0, 0, nil)
        networkError.Sub(y, output)

        slopeOutputLayer := mat.NewDense(0, 0, nil)
        applySigmoidPrime := func(_, _ int, v float64) float64 { return sigmoidPrime(v) }
        slopeOutputLayer.Apply(applySigmoidPrime, output)
        slopeHiddenLayer := mat.NewDense(0, 0, nil)
        slopeHiddenLayer.Apply(applySigmoidPrime, hiddenLayerActivations)

        dOutput := mat.NewDense(0, 0, nil)
        dOutput.MulElem(networkError, slopeOutputLayer)
        errorAtHiddenLayer := mat.NewDense(0, 0, nil)
        errorAtHiddenLayer.Mul(dOutput, wOut.T())

        dHiddenLayer := mat.NewDense(0, 0, nil)
        dHiddenLayer.MulElem(errorAtHiddenLayer, slopeHiddenLayer)

        // Adjust the parameters.
        wOutAdj := mat.NewDense(0, 0, nil)
        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 := mat.NewDense(0, 0, nil)
        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)
    }

    nn.wHidden = wHidden
    nn.bHidden = bHidden
    nn.wOut = wOut
    nn.bOut = bOut

    return nil

}

func main() {
    // Define our input attributes.
    input := mat.NewDense(3, 4, []float64{
        1.0, 0.0, 1.0, 0.0,
        1.0, 0.0, 1.0, 1.0,
        0.0, 1.0, 0.0, 1.0,
    })

    // Define our labels.
    labels := mat.NewDense(3, 1, []float64{1.0, 1.0, 0.0})

    // Define our network architecture and
    // learning parameters.
    config := neuralNetConfig{
        inputNeurons:  4,
        outputNeurons: 1,
        hiddenNeurons: 3,
        numEpochs:     5000,
        learningRate:  0.3,
    }

    // Train the neural network.
    network := newNetwork(config)
    if err := network.train(input, labels); err != nil {
        log.Fatal(err)
    }

    // Output the weights that define our network!
    f := mat.Formatted(network.wHidden, mat.Prefix(" "))
    fmt.Printf("\nwHidden = % v\n\n", f)

    f = mat.Formatted(network.bHidden, mat.Prefix(" "))
    fmt.Printf("\nbHidden = % v\n\n", f)

    f = mat.Formatted(network.wOut, mat.Prefix(" "))
    fmt.Printf("\nwOut = % v\n\n", f)

    f = mat.Formatted(network.bOut, mat.Prefix(" "))
    fmt.Printf("\nbOut = % v\n\n", f)
}

我收到了以下反馈:

panic: mat: zero length in matrix dimension

goroutine 1 [running]: gonum.org/v1/gonum/mat.NewDense(...) C:/Users/fabri/go/pkg/mod/gonum.org/v1/gonum@v0.9.2/mat/dense.go:50 main.(*neuralNet).train(0xc00010ff30, 0xc000024080, 0xc0000240c0, 0x8f3bc0, 0x952088) D:/Tech/go/src/go-text-classification/main.go:106 +0x870 main.main() D:/Tech/go/src/go-text-classification/main.go:204 +0x2a5

提前感谢您的帮助。


更多关于Golang新手问题:矩阵维度为零导致的panic错误 - 神经网络应用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

你好 @fabricioism,欢迎来到 Go 论坛。

看起来这个 panic 是预期内的。

堆栈跟踪指向了你代码的第 106 行,即:

output := mat.NewDense(0, 0, nil)

根据文档,如果两个尺寸参数中任何一个为零,mat.NewDense() 就会 panic。

你提到的书是 2017 年的,而 NewDense() 直到 2018 年才在尺寸为零时 panic。这就解释了为什么书中建议使用 mat.NewDense(0, 0, nil)——因为作者在 2017 年时仍然可以这样使用 NewDense。

尝试改用:

output := &mat.Dense{}

可能会奏效,因为你的代码在每次调用 NewDense(0, 0, nil) 之后直接执行的所有 Dense 操作,似乎无论如何都会通过调用内部方法 reuseAsNonZeroed() 来调整矩阵的大小。

免责声明 - 我完全不是 gonum 专家。我只是在四处窥探了他们的代码后做出假设。因此,你的实际效果可能会有所不同。

更多关于Golang新手问题:矩阵维度为零导致的panic错误 - 神经网络应用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


问题出在您初始化矩阵时使用了零维度。在train方法中,您创建了多个零维矩阵(如output := mat.NewDense(0, 0, nil)),但在后续操作中直接使用这些空矩阵进行计算,导致维度不匹配。

具体来说,第106行(根据堆栈跟踪)的output.Apply(applySigmoid, outputLayerInput)会失败,因为output是0x0的矩阵,而outputLayerInput是3x1的矩阵。

以下是修复后的关键代码部分:

func (nn *neuralNet) train(x, y *mat.Dense) error {
    // ... [权重初始化代码保持不变] ...

    // 删除这行:output := mat.NewDense(0, 0, nil)
    // 改为在需要时创建适当维度的矩阵

    for i := 0; i < nn.config.numEpochs; i++ {
        // 前向传播
        hiddenLayerInput := mat.NewDense(0, 0, nil)
        hiddenLayerInput.Mul(x, wHidden)
        addBHidden := func(_, col int, v float64) float64 { return v + bHidden.At(0, col) }
        hiddenLayerInput.Apply(addBHidden, hiddenLayerInput)

        hiddenLayerActivations := mat.NewDense(0, 0, nil)
        applySigmoid := func(_, _ int, v float64) float64 { return sigmoid(v) }
        hiddenLayerActivations.Apply(applySigmoid, hiddenLayerInput)

        outputLayerInput := mat.NewDense(0, 0, nil)
        outputLayerInput.Mul(hiddenLayerActivations, wOut)
        addBOut := func(_, col int, v float64) float64 { return v + bOut.At(0, col) }
        outputLayerInput.Apply(addBOut, outputLayerInput)
        
        // 创建正确维度的输出矩阵
        output := mat.NewDense(0, 0, nil)
        output.Apply(applySigmoid, outputLayerInput)

        // 反向传播
        networkError := mat.NewDense(0, 0, nil)
        networkError.Sub(y, output)

        slopeOutputLayer := mat.NewDense(0, 0, nil)
        applySigmoidPrime := func(_, _ int, v float64) float64 { return sigmoidPrime(v) }
        slopeOutputLayer.Apply(applySigmoidPrime, output)
        slopeHiddenLayer := mat.NewDense(0, 0, nil)
        slopeHiddenLayer.Apply(applySigmoidPrime, hiddenLayerActivations)

        dOutput := mat.NewDense(0, 0, nil)
        dOutput.MulElem(networkError, slopeOutputLayer)
        errorAtHiddenLayer := mat.NewDense(0, 0, nil)
        errorAtHiddenLayer.Mul(dOutput, wOut.T())

        dHiddenLayer := mat.NewDense(0, 0, nil)
        dHiddenLayer.MulElem(errorAtHiddenLayer, slopeHiddenLayer)

        // ... [参数调整代码保持不变] ...
    }

    // ... [权重保存代码保持不变] ...
    return nil
}

主要修改:

  1. output矩阵的创建移到循环内部,在每次迭代时重新创建
  2. 确保所有矩阵操作前,目标矩阵都有正确的维度

这样修改后,矩阵维度就能正确匹配,避免panic错误。

回到顶部