Goによるニューラルネットワーク構築:原理、構造、実装
James Reed
Infrastructure Engineer · Leapcell

Goでのニューラルネットワーク構築:原理、構造、実装
この記事では、Goプログラミング言語を使用して、シンプルなニューラルネットワークをゼロから構築し、Iris分類タスクを通じてそのワークフローを実証する方法を紹介します。原理の説明、コードの実装、視覚的な構造表示を組み合わせることで、読者がニューラルネットワークのコアメカニズムを理解するのに役立ちます。
Ⅰ. ニューラルネットワークの基本原理と構造
ニューラルネットワークは、生物学的ニューロンをシミュレートする計算モデルであり、複数層にわたるノードの接続を通じて非線形マッピングを実現します。典型的な3層ニューラルネットワーク構造には、入力層、隠れ層、出力層が含まれます。各層のノードは、重みとバイアスを介して接続され、層間の伝達は活性化関数を通じて処理されます。以下は、単純な3層ニューラルネットワーク構造の概略図です(ASCII文字で描画)。
+-----------+ +-----------+ +-----------+ | Input Layer | | Hidden Layer | | Output Layer | | 4 Nodes | | 3 Nodes | | 3 Nodes | +-----------+ +-----------+ +-----------+ ↑ ↑ ↑ │ Weights │ Weights │ Weights │ ├───────────────┼───────────────┼───────────────┤ ↓ ↓ ↓ +-----------+ +-----------+ +-----------+ | Bias | | Bias | | Bias | +-----------+ +-----------+ +-----------+ ↓ ↓ ↓ +-----------+ +-----------+ +-----------+ | Activation| | Activation| | Activation| | Function | | Function | | Function | +-----------+ +-----------+ +-----------+
コアコンセプト:
-
順伝播 入力データは、重み行列(
input × weights + bias
)による線形変換を受け、その後、活性化関数を介して非線形性を導入し、層ごとに伝播して出力層に到達します。 数式例:- 隠れ層への入力: ( Z_1 = X \cdot W_1 + b_1 )
- 隠れ層の出力: ( A_1 = \sigma(Z_1) ) ((\sigma) はシグモイド関数)
- 出力層への入力: ( Z_2 = A_1 \cdot W_2 + b_2 )
- 出力層の出力: ( A_2 = \sigma(Z_2) )
-
逆伝播 予測値と真の値の間の誤差(平均二乗誤差など)を計算することにより、各層の重みとバイアスは、連鎖律を使用して逆方向に更新され、モデルパラメータを最適化します。 主な手順:
- 出力誤差の計算: ( \delta_2 = A_2 - Y )
- 隠れ層の誤差: ( \delta_1 = \delta_2 \cdot W_2^T \odot \sigma'(Z_1) ) ((\odot) は要素ごとの乗算を示します)
- 重みの更新: ( W_2 \leftarrow W_2 - \eta \cdot A_1^T \cdot \delta_2 ), ( W_1 \leftarrow W_1 - \eta \cdot X^T \cdot \delta_1 )
- バイアスの更新: ( b_2 \leftarrow b_2 - \eta \cdot \sum \delta_2 ), ( b_1 \leftarrow b_1 - \eta \cdot \sum \delta_1 ) ((\eta) は学習率、(\sigma') は活性化関数の導関数)
Ⅱ. Goでのニューラルネットワーク実装の主要な設計
1. データ構造の定義
Goのgonum.org/v1/gonum/mat
パッケージをマトリックス演算に使用し、ネットワーク構造とパラメータを定義します。
// neuralNet stores trained neural network parameters type neuralNet struct { config neuralNetConfig // Network configuration wHidden *mat.Dense // Hidden layer weight matrix bHidden *mat.Dense // Hidden layer bias vector wOut *mat.Dense // Output layer weight matrix bOut *mat.Dense // Output layer bias vector } // neuralNetConfig defines network architecture and training parameters type neuralNetConfig struct { inputNeurons int // Number of input layer nodes (e.g., 4 features of Iris) outputNeurons int // Number of output layer nodes (e.g., 3 Iris species) hiddenNeurons int // Number of hidden layer nodes (tunable hyperparameter) numEpochs int // Number of training epochs learningRate float64 // Learning rate }
2. 活性化関数とその導関数
シグモイド関数を活性化関数として選択します。その導関数は、関数値に基づいて迅速に計算でき、逆伝播に適しています。
// sigmoid Sigmoid activation function func sigmoid(x float64) float64 { return 1.0 / (1.0 + math.Exp(-x)) } // sigmoidPrime Derivative of the Sigmoid function func sigmoidPrime(x float64) float64 { s := sigmoid(x) return s * (1.0 - s) }
3. 逆伝播訓練ロジック
パラメータの初期化
ネットワークが学習できるように、重みとバイアスを乱数で初期化します。
func (nn *neuralNet) train(x, y *mat.Dense) error { randGen := rand.New(rand.NewSource(time.Now().UnixNano())) // Random number generator // Initialize weights and biases for hidden and output layers 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) // Fill parameter matrices with random numbers for _, param := range [][]*mat.Dense{{wHidden, bHidden}, {wOut, bOut}} { for _, m := range param { raw := m.RawMatrix().Data for i := range raw { raw[i] = randGen.Float64() // Random values in [0, 1) } } } // Invoke backpropagation for training return nn.backpropagate(x, y, wHidden, bHidden, wOut, bOut) }
コア逆伝播ロジック
行列演算を通じて誤差逆伝播とパラメータ更新を実装します。このコードでは、Apply
メソッドを使用して、活性化関数と導関数を一括処理します。
func (nn *neuralNet) backpropagate(x, y, wHidden, bHidden, wOut, bOut *mat.Dense) error { for epoch := 0; epoch < nn.config.numEpochs; epoch++ { // Forward propagation to calculate outputs of each layer hiddenInput := new(mat.Dense).Mul(x, wHidden) // Hidden layer linear input: X·W_hidden hiddenInput.Apply(func(_, col int, v float64) float64 { // Add bias term return v + bHidden.At(0, col) }, hiddenInput) hiddenAct := new(mat.Dense).Apply(sigmoid, hiddenInput) // Hidden layer activated output outputInput := new(mat.Dense).Mul(hiddenAct, wOut) // Output layer linear input: A_hidden·W_out outputInput.Apply(func(_, col int, v float64) float64 { // Add bias term return v + bOut.At(0, col) }, outputInput) output := new(mat.Dense).Apply(sigmoid, outputInput) // Output layer activated output // Backpropagation to calculate errors and gradients error := new(mat.Dense).Sub(y, output) // Output error: Y - A_out // Calculate output layer gradients outputSlope := new(mat.Dense).Apply(sigmoidPrime, outputInput) // σ'(Z_out) dOutput := new(mat.Dense).MulElem(error, outputSlope) // δ_out = error * σ'(Z_out) // Calculate hidden layer gradients hiddenError := new(mat.Dense).Mul(dOutput, wOut.T()) // Error backpropagation: δ_out·W_out^T hiddenSlope := new(mat.Dense).Apply(sigmoidPrime, hiddenInput) // σ'(Z_hidden) dHidden := new(mat.Dense).MulElem(hiddenError, hiddenSlope) // δ_hidden = δ_out·W_out^T * σ'(Z_hidden) // Update weights and biases (stochastic gradient descent) wOut.Add(wOut, new(mat.Dense).Scale(nn.config.learningRate, new(mat.Dense).Mul(hiddenAct.T(), dOutput))) bOut.Add(bOut, new(mat.Dense).Scale(nn.config.learningRate, sumAlongAxis(0, dOutput))) wHidden.Add(wHidden, new(mat.Dense).Scale(nn.config.learningRate, new(mat.Dense).Mul(x.T(), dHidden))) bHidden.Add(bHidden, new(mat.Dense).Scale(nn.config.learningRate, sumAlongAxis(0, dHidden))) } // Save trained parameters nn.wHidden, nn.bHidden, nn.wOut, nn.bOut = wHidden, bHidden, wOut, bOut return nil }
4. 順方向予測関数
トレーニング後、トレーニング済みの重みとバイアスを使用して順伝播を行い、予測を出力します。
func (nn *neuralNet) predict(x *mat.Dense) (*mat.Dense, error) { // Check if parameters exist if nn.wHidden == nil || nn.wOut == nil { return nil, errors.New("neural network not trained") } hiddenAct := new(mat.Dense).Mul(x, nn.wHidden).Apply(func(_, col int, v float64) float64 { return v + nn.bHidden.At(0, col) }, nil).Apply(sigmoid, nil) output := new(mat.Dense).Mul(hiddenAct, nn.wOut).Apply(func(_, col int, v float64) float64 { return v + nn.bOut.At(0, col) }, nil).Apply(sigmoid, nil) return output, nil }
Ⅲ. データ処理と実験的検証
1. データセットの準備
古典的なIrisデータセットを使用します。これには、4つの特徴(がく片の長さ、がく片の幅、花びらの長さ、花びらの幅)と3つの種(Setosa、Versicolor、Virginica)が含まれます。データの前処理手順:
- 種のラベルをワンホットエンコーディングに変換します。たとえば、Setosaは
[1, 0, 0]
に対応し、Versicolorは[0, 0, 1]
に対応します。 - データの80%をトレーニングセットに、20%をテストセットに分割し、トレーニングの難易度を高めるために小さなランダムノイズを追加します。
- サンプルデータ(
train.csv
からの抜粋):sepal_length,sepal_width,petal_length,petal_width,setosa,virginica,versicolor 0.0873,0.6687,0.0,0.0417,1.0,0.0,0.0 0.7232,0.4533,0.6949,0.967,0.0,1.0,0.0 0.6617,0.4567,0.6580,0.6567,0.0,0.0,1.0
2. メインプログラムの流れ
データの読み取りとマトリックスへの変換
func main() { // Read training data file f, err := os.Open("data/train.csv") if err != nil { log.Fatalf("failed to open file: %v", err) } defer f.Close() reader := csv.NewReader(f) reader.FieldsPerRecord = 7 // 4 features + 3 labels rawData, err := reader.ReadAll() if err != nil { log.Fatalf("failed to read CSV: %v", err) } // Parse data into input features (X) and labels (Y) numSamples := len(rawData) - 1 // Skip header inputsData := make([]float64, 4*numSamples) labelsData := make([]float64, 3*numSamples) for i, record := range rawData { if i == 0 { continue // Skip header } for j, val := range record { fVal, err := strconv.ParseFloat(val, 64) if err != nil { log.Fatalf("invalid value: %v", val) } if j < 4 { inputsData[(i-1)*4+j] = fVal // First 4 columns are features } else { labelsData[(i-1)*3+(j-4)] = fVal // Last 3 columns are labels } } } inputs := mat.NewDense(numSamples, 4, inputsData) labels := mat.NewDense(numSamples, 3, labelsData) }
ネットワークパラメータの設定とトレーニング
// Define network structure: 4 inputs, 3 hidden nodes, 3 outputs config := neuralNetConfig{ inputNeurons: 4, outputNeurons: 3, hiddenNeurons: 5, numEpochs: 8000, // Train for 5000 epochs learningRate: 0.2, // Learning rate } network := newNetwork(config) if err := network.train(inputs, labels); err != nil { log.Fatalf("training failed: %v", err) }
モデルの精度をテスト
// Read test data and predict predictions, err := network.predict(testInputs) if err != nil { log.Fatalf("prediction failed: %v", err) } // Calculate classification accuracy trueCount := 0 numPreds, _ := predictions.Dims() for i := 0; i < numPreds; i++ { // Get true label (one-hot to index) trueLabel := mat.Row(nil, i, testLabels) trueClass := -1 for j, val := range trueLabel { if val == 1.0 { trueClass = j break } } // Get class with highest probability in predictions predRow := mat.Row(nil, i, predictions) maxVal := floats.Min(predRow) predClass := -1 for j, val := range predRow { if val > maxVal { maxVal = val predClass = j } } if trueClass == predClass { trueCount++ } } fmt.Printf("Accuracy: %.2f%%\n", float64(trueCount)/float64(numPreds)*100)
Ⅳ. 実験結果とまとめ
8000回のトレーニングエポックの後、モデルはテストセットで約98%の分類精度を達成します(ランダム初期化のため、結果はわずかに異なる場合があります)。これは、単純な3層ニューラルネットワークでも、非線形分類問題を効果的に解決できることを示しています。
主な利点:
- ピュアGo実装:C拡張機能(
cgo
なし)に依存せず、静的バイナリファイルにコンパイルでき、クロスプラットフォーム展開に適しています。 - 行列抽象化:
gonum/mat
パッケージに基づく数値計算、明確なコード構造、容易な拡張性。
改善の方向性:
- さまざまな活性化関数(例:ReLU)または最適化関数(例:Adam)を試してください。
- 過剰適合を防ぐために、正則化(例:L2正則化)を追加します。
- より深いニューラルネットワークを構築するために、複数の隠れ層をサポートします。
Leapcell:最高のサーバーレスWebホスティング
最後に、Goサービスをデプロイするための最適なプラットフォームをお勧めします:Leapcell
🚀 お気に入りの言語で構築
JavaScript、Python、Go、またはRustで簡単に開発できます。
🌍 無制限のプロジェクトを無料でデプロイ
使用量に応じて料金を支払うだけで、リクエストも料金もかかりません。
⚡ 従量課金制、隠れたコストなし
アイドル料金はかからず、シームレスなスケーラビリティのみが提供されます。
🔹 Twitterでフォローしてください:@LeapcellHQ