🔢

Golangの標準ライブラリのみでmnistの数字認識 By ChatGPT

2023/03/10に公開

Golangの標準ライブラリを使用して、MNISTデータセットから数字の認識を行う方法について説明する記事です。MNISTデータセットは、手書き数字の画像とそのラベルが含まれる有名なデータセットであり、機械学習の分野で広く使用されています。この記事では、深層学習のライブラリを使用せずに、Golangの標準ライブラリのみで数字認識を実現します。自分の勉強用にやってみたことの忘備録ですが、Golangを学んでいる方や、機械学習の基礎を学ぶ方にとって役立つ記事ともなっていることを願っています。

結果

なんと、ChatGPTへの質問のみで「Golangの標準ライブラリのみでmnistの数字認識」するコードの大枠を自動生成することができました。いくつか変更した箇所もありますが、ChatGPTが書いたコードで約6割の正答率、パラメータ調整で8割、ニューラルネットワークに用いる式の変更で9割7分の正解率を達成しました。

各エポックにおける教師データに対する正答率

epoch: 0, accuracy: 86.991667
epoch: 1, accuracy: 92.813333
epoch: 2, accuracy: 93.973333
epoch: 3, accuracy: 95.045000
epoch: 4, accuracy: 95.376667
epoch: 5, accuracy: 95.885000
epoch: 6, accuracy: 96.158333
epoch: 7, accuracy: 96.406667
epoch: 8, accuracy: 96.688333
epoch: 9, accuracy: 96.855000
epoch: 10, accuracy: 96.970000
epoch: 11, accuracy: 97.038333
epoch: 12, accuracy: 97.075000
epoch: 13, accuracy: 97.170000
epoch: 14, accuracy: 97.233333
epoch: 15, accuracy: 97.243333
epoch: 16, accuracy: 97.268333
epoch: 17, accuracy: 97.323333
epoch: 18, accuracy: 97.316667
epoch: 19, accuracy: 97.301667
epoch: 20, accuracy: 97.250000
epoch: 21, accuracy: 97.143333
epoch: 22, accuracy: 97.116667
epoch: 23, accuracy: 97.295000
epoch: 24, accuracy: 97.406667
epoch: 25, accuracy: 97.466667
epoch: 26, accuracy: 97.490000
epoch: 27, accuracy: 97.445000
epoch: 28, accuracy: 97.453333
epoch: 29, accuracy: 97.550000
epoch: 30, accuracy: 97.605000
epoch: 31, accuracy: 97.655000

最終的なテストデータに対する正答率

Correct answer rate: 9704 / 10000

Golangの標準ライブラリのみでmnistの数字認識を行うコード

このコードは、ニューラルネットワークを使用して多層パーセプトロンを訓練するためのものです。このネットワークは、任意の数の入力を受け取り、0または1のいずれかの値を出力します。ニューラルネットワークは、入力と出力の間に隠れたレイヤーを持ちます。ここでは、ReLU活性化関数とシグモイド活性化関数が使用されています。

package main

import (
	"bufio"
	"encoding/csv"
	"fmt"
	"io"
	"log"
	"math"
	"math/rand"
	"os"
	"strconv"
	"strings"
	"time"
)

type NeuralNetwork struct {
	inputSize  int
	hiddenSize int
	outputSize int
	weights1   [][]float64
	weights2   [][]float64
	bias1      []float64
	bias2      []float64
}

func sigmoid(x float64) float64 {
	return 1.0 / (1.0 + math.Exp(-x))
}

func sigmoidDerivative(x float64) float64 {
	return sigmoid(x) * (1 - sigmoid(x))
}

func relu(x float64) float64 {
	if x >= 0 {
		return x
	} else {
		return 0
	}
}

func reluDerivative(x float64) float64 {
	if x >= 0 {
		return 1
	} else {
		return 0
	}
}

func NewNeuralNetwork(inputSize, hiddenSize, outputSize int) *NeuralNetwork {
	nn := &NeuralNetwork{
		inputSize:  inputSize,
		hiddenSize: hiddenSize,
		outputSize: outputSize,
		weights1:   make([][]float64, inputSize),
		weights2:   make([][]float64, hiddenSize),
		bias1:      make([]float64, hiddenSize),
		bias2:      make([]float64, outputSize),
	}

	rand.Seed(time.Now().UnixNano())

	for i := range nn.weights1 {
		nn.weights1[i] = make([]float64, nn.hiddenSize)
		for j := range nn.weights1[i] {
			nn.weights1[i][j] = rand.Float64()
		}
	}

	for i := range nn.weights2 {
		nn.weights2[i] = make([]float64, nn.outputSize)
		for j := range nn.weights2[i] {
			nn.weights2[i][j] = rand.Float64()
		}
	}

	for i := range nn.bias1 {
		nn.bias1[i] = rand.Float64()
	}

	for i := range nn.bias2 {
		nn.bias2[i] = rand.Float64()
	}

	return nn
}

func (nn *NeuralNetwork) Forward(input []float64) []float64 {
	hidden := make([]float64, nn.hiddenSize)
	output := make([]float64, nn.outputSize)

	for i := range hidden {
		for j := range input {
			hidden[i] += input[j] * nn.weights1[j][i]
		}
		hidden[i] = relu(hidden[i] + nn.bias1[i])
	}

	for i := range output {
		for j := range hidden {
			output[i] += hidden[j] * nn.weights2[j][i]
		}
		output[i] = sigmoid(output[i] + nn.bias2[i])
	}

	return output
}

func (nn *NeuralNetwork) TrainNeuralNetwork(inputs [][]float64, outputs [][]float64, learningRate float64, epochs int) {
	for epoch := 0; epoch < epochs; epoch++ {
		correct := 0 // 正解数をカウントするための変数
		for i := range inputs {
			input := inputs[i]
			output := outputs[i]
			hidden := make([]float64, nn.hiddenSize)
			outputLayer := make([]float64, nn.outputSize)

			// Forward propagation
			for j := range hidden {
				for k := range input {
					hidden[j] += input[k] * nn.weights1[k][j]
				}
				hidden[j] = relu(hidden[j] + nn.bias1[j])
			}

			for j := range outputLayer {
				for k := range hidden {
					outputLayer[j] += hidden[k] * nn.weights2[k][j]
				}
				outputLayer[j] = sigmoid(outputLayer[j] + nn.bias2[j])
			}

			// 正解数をカウントする
			prediction := 0
			for j, val := range outputLayer {
				if val > outputLayer[prediction] {
					prediction = j
				}
			}
			if output[prediction] == 1 {
				correct++
			}

			// Backpropagation
			outputLayerError := make([]float64, nn.outputSize)
			for j := range outputLayer {
				outputLayerError[j] = output[j] - outputLayer[j]
			}

			outputLayerDelta := make([]float64, nn.outputSize)
			for j := range outputLayerDelta {
				outputLayerDelta[j] = outputLayerError[j] * sigmoidDerivative(outputLayer[j])
			}

			hiddenError := make([]float64, nn.hiddenSize)
			for j := range hidden {
				for k := range outputLayerDelta {
					hiddenError[j] += outputLayerDelta[k] * nn.weights2[j][k]
				}
			}

			hiddenDelta := make([]float64, nn.hiddenSize)
			for j := range hiddenDelta {
				hiddenDelta[j] = hiddenError[j] * reluDerivative(hidden[j])
			}

			// Update weights and biases
			for j := range nn.bias2 {
				nn.bias2[j] += learningRate * outputLayerDelta[j]
				for k := range hidden {
					nn.weights2[k][j] += learningRate * outputLayerDelta[j] * hidden[k]
				}
			}

			for j := range nn.bias1 {
				nn.bias1[j] += learningRate * hiddenDelta[j]
				for k := range input {
					nn.weights1[k][j] += learningRate * hiddenDelta[j] * input[k]
				}
			}
		}

		// トレーニングセット全体に対する正答率を出力する
		accuracy := float64(correct) / float64(len(inputs)) * 100.0
		fmt.Printf("epoch: %d, accuracy: %f\n", epoch, accuracy)
	}
}

func ReadCSVFile(filename string) ([][]float64, error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer file.Close()
	reader := csv.NewReader(bufio.NewReader(file))
	var data [][]float64
	for {
		record, err := reader.Read()
		if err == io.EOF {
			break
		}
		if err != nil {
			return nil, err
		}
		row := make([]float64, len(record))
		for i, v := range record {
			val, err := strconv.ParseFloat(strings.TrimSpace(v), 64)
			if err != nil {
				return nil, err
			}
			// row[i] = val
			if i != 0 {
				row[i] = val/256.0 + 0.0001
			} else {
				row[0] = val
			}
		}
		data = append(data, row)
	}
	return data, nil
}

func main() {
	// Load training data
	inputs, err := ReadCSVFile("data/mnist_train.csv")
	if err != nil {
		log.Fatal(err)
	}
	// Create one-hot encoded labels
	labels := [][]float64{
		{1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 1, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 1, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 1, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 1, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 1, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 1, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 1, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
	}

	// make outputs
	outputs := [][]float64{}
	for _, input := range inputs {
		outputs = append(outputs, labels[int(input[0])])
	}

	// Train neural network
	nn := NewNeuralNetwork(len(inputs[0]), 64, len(labels[0]))
	nn.TrainNeuralNetwork(inputs, outputs, 0.01, 50)

	// Load test data
	testInputs, err := ReadCSVFile("data/mnist_test.csv")
	if err != nil {
		log.Fatal(err)
	}

	// Test neural network
	ca := 0
	for i := range testInputs {
		outputs := nn.Forward(testInputs[i])
		bestIndex := 0
		bestValue := 0.0

		for j := range outputs {
			if outputs[j] > bestValue {
				bestValue = outputs[j]
				bestIndex = j
			}
		}
		fmt.Printf("Predicted: %d ,Correct answer: %d \n", bestIndex, int(testInputs[i][0]))
		if bestIndex == int(testInputs[i][0]) {
			ca++
		}
	}
	fmt.Printf("Correct answer rate: %d / %d", ca, len(testInputs))
}

解説

ニューラルネットワークは、NeuralNetwork構造体で表されます。

type NeuralNetwork struct {
	inputSize  int
	hiddenSize int
	outputSize int
	weights1   [][]float64
	weights2   [][]float64
	bias1      []float64
	bias2      []float64
}

NewNeuralNetwork関数を使用して、inputSize、hiddenSize、outputSizeを指定して初期化できます。weights1、weights2、bias1、bias2は、重みとバイアスを格納するための行列と配列です。weights1とweights2は、入力と隠れたレイヤー、隠れたレイヤーと出力の間の重みを格納します。bias1とbias2は、隠れたレイヤーと出力のバイアスを格納します。これらの重みとバイアスは、NewNeuralNetwork関数内でランダムに初期化されます。

func NewNeuralNetwork(inputSize, hiddenSize, outputSize int) *NeuralNetwork {
	nn := &NeuralNetwork{
		inputSize:  inputSize,
		hiddenSize: hiddenSize,
		outputSize: outputSize,
		weights1:   make([][]float64, inputSize),
		weights2:   make([][]float64, hiddenSize),
		bias1:      make([]float64, hiddenSize),
		bias2:      make([]float64, outputSize),
	}

	rand.Seed(time.Now().UnixNano())

	for i := range nn.weights1 {
		nn.weights1[i] = make([]float64, nn.hiddenSize)
		for j := range nn.weights1[i] {
			nn.weights1[i][j] = rand.Float64()
		}
	}

	for i := range nn.weights2 {
		nn.weights2[i] = make([]float64, nn.outputSize)
		for j := range nn.weights2[i] {
			nn.weights2[i][j] = rand.Float64()
		}
	}

	for i := range nn.bias1 {
		nn.bias1[i] = rand.Float64()
	}

	for i := range nn.bias2 {
		nn.bias2[i] = rand.Float64()
	}

	return nn
}

Forwardメソッドは、与えられた入力に対してニューラルネットワークを実行し、出力を返します。まず、隠れたレイヤーに入力を乗算し、ReLU活性化関数を適用します。次に、隠れたレイヤーを出力に乗算し、シグモイド活性化関数を適用します。これらの処理は、TrainNeuralNetworkメソッドでも実行されます。

func (nn *NeuralNetwork) Forward(input []float64) []float64 {
	hidden := make([]float64, nn.hiddenSize)
	output := make([]float64, nn.outputSize)

	for i := range hidden {
		for j := range input {
			hidden[i] += input[j] * nn.weights1[j][i]
		}
		hidden[i] = relu(hidden[i] + nn.bias1[i])
	}

	for i := range output {
		for j := range hidden {
			output[i] += hidden[j] * nn.weights2[j][i]
		}
		output[i] = sigmoid(output[i] + nn.bias2[i])
	}

	return output
}

TrainNeuralNetworkメソッドは、inputsとoutputsの2つの2次元配列を受け取ります。inputsは、各入力の特徴量を格納する1次元配列を含みます。outputsは、各入力に対応する期待される出力を格納する1次元配列を含みます。このメソッドは、ニューラルネットワークを訓練するために、各入力と出力のペアを使用します。
訓練中に、まず正解数をカウントし、予測が正しい場合にはカウントを増やします。次に、バックプロパゲーションを使用して、誤差を計算し、重みとバイアスを更新します。最後に、正解率と誤差を出力します。

func (nn *NeuralNetwork) TrainNeuralNetwork(inputs [][]float64, outputs [][]float64, learningRate float64, epochs int) {
	for epoch := 0; epoch < epochs; epoch++ {
		correct := 0 // 正解数をカウントするための変数
		for i := range inputs {
			input := inputs[i]
			output := outputs[i]
			hidden := make([]float64, nn.hiddenSize)
			outputLayer := make([]float64, nn.outputSize)

			// Forward propagation
			for j := range hidden {
				for k := range input {
					hidden[j] += input[k] * nn.weights1[k][j]
				}
				hidden[j] = relu(hidden[j] + nn.bias1[j])
			}

			for j := range outputLayer {
				for k := range hidden {
					outputLayer[j] += hidden[k] * nn.weights2[k][j]
				}
				outputLayer[j] = sigmoid(outputLayer[j] + nn.bias2[j])
			}

			// 正解数をカウントする
			prediction := 0
			for j, val := range outputLayer {
				if val > outputLayer[prediction] {
					prediction = j
				}
			}
			if output[prediction] == 1 {
				correct++
			}

			// Backpropagation
			outputLayerError := make([]float64, nn.outputSize)
			for j := range outputLayer {
				outputLayerError[j] = output[j] - outputLayer[j]
			}

			outputLayerDelta := make([]float64, nn.outputSize)
			for j := range outputLayerDelta {
				outputLayerDelta[j] = outputLayerError[j] * sigmoidDerivative(outputLayer[j])
			}

			hiddenError := make([]float64, nn.hiddenSize)
			for j := range hidden {
				for k := range outputLayerDelta {
					hiddenError[j] += outputLayerDelta[k] * nn.weights2[j][k]
				}
			}

			hiddenDelta := make([]float64, nn.hiddenSize)
			for j := range hiddenDelta {
				hiddenDelta[j] = hiddenError[j] * reluDerivative(hidden[j])
			}

			// Update weights and biases
			for j := range nn.bias2 {
				nn.bias2[j] += learningRate * outputLayerDelta[j]
				for k := range hidden {
					nn.weights2[k][j] += learningRate * outputLayerDelta[j] * hidden[k]
				}
			}

			for j := range nn.bias1 {
				nn.bias1[j] += learningRate * hiddenDelta[j]
				for k := range input {
					nn.weights1[k][j] += learningRate * hiddenDelta[j] * input[k]
				}
			}
		}

		// トレーニングセット全体に対する正答率を出力する
		accuracy := float64(correct) / float64(len(inputs)) * 100.0
		fmt.Printf("epoch: %d, accuracy: %f\n", epoch, accuracy)
	}
}

ChatGPTの書いたコードと変更点

ChatGPTの書いたコードに対して、以下の点で修正を行いました。

  1. シグモイド関数の導関数の修正

ChatGPTによるコード生成では、シグモイド関数の導関数が次のようになっていたため、正しい導関数に変更しました。なお、この間違いに気づいたのは、コードを実行した際に正しく動かなかったため、何がおかしいのかChatGPTに質問して判明しました。

ChatGPTのコード

func sigmoidDerivative(x float64) float64 {
	return sigmoid(x) * (1 - sigmoid(x))
}

修正したコード

func sigmoidDerivative(x float64) float64 {
	return x * (1.0 - x)
}
  1. CSV読込時にラベル以外を0から1の範囲に収める修正

ChatGPTのコード

			row[i] = val

修正したコード

			if i != 0 {
				row[i] = val/256.0 + 0.0001
			} else {
				row[0] = val
			}
  1. 活性化関数をシグモイド関数からReLU関数へ変更
    コードは動いたものの、パラメータを調整しても正答率が8割程だったため、ニューラルネットワークに詳しい大学の後輩 chilota0000 にどうすればいいか聞いたところ、活性化関数をシグモイド関数からLeRu関数へ変更することを提案してくれました。変更すると、正答率が97%まで上昇しました。
func relu(x float64) float64 {
	if x >= 0 {
		return x
	} else {
		return 0
	}
}

func reluDerivative(x float64) float64 {
	if x >= 0 {
		return 1
	} else {
		return 0
	}
}

ChatGPT君への質問と回答の全文

たこやき

Golangでニューラルネットワークを標準ライブラリだけで書いてください

ChatGPT

Go言語でニューラルネットワークを標準ライブラリだけで書くことは可能ですが、完全に実装するには多くの時間と労力が必要です。以下は、Go言語の標準ライブラリを使用して、単純な3層のフィードフォワードニューラルネットワークを実装する方法の例です。

package main

import (
	"fmt"
	"math"
)

// シグモイド活性化関数
func sigmoid(x float64) float64 {
	return 1.0 / (1.0 + math.Exp(-x))
}

// フィードフォワードニューラルネットワーク
func forward(input []float64, weights [][]float64) []float64 {
	hidden := make([]float64, len(weights[0]))
	for i := range hidden {
		for j := range input {
			hidden[i] += input[j] * weights[j][i]
		}
		hidden[i] = sigmoid(hidden[i])
	}

	output := make([]float64, len(weights[1]))
	for i := range output {
		for j := range hidden {
			output[i] += hidden[j] * weights[len(weights)-1][i]
		}
		output[i] = sigmoid(output[i])
	}

	return output
}

func main() {
	// ニューラルネットワークの重み
	weights := [][]float64{
		{0.1, 0.2, 0.3},
		{0.4, 0.5, 0.6},
		{0.7, 0.8},
	}

	// 入力
	input := []float64{0.9, 0.1, 0.8}

	// 出力を計算
	output := forward(input, weights)

	// 結果を表示
	fmt.Println(output)
}

このコードでは、シグモイド活性化関数を定義し、フィードフォワードニューラルネットワークを実装しています。ニューラルネットワークの重みは、weightsという2次元配列で表されます。forward関数は、与えられた入力を使用してニューラルネットワークの出力を計算します。最後に、main関数で入力を設定し、forward関数を呼び出して出力を計算し、結果を表示しています。

この例では、ニューラルネットワークが非常に単純であるため、標準ライブラリだけで実装できましたが、より大規模なニューラルネットワークを実装する場合は、より高度な数学ライブラリが必要になる場合があります。

たこやき

数字を認識するには?

ChatGPT

数字を認識するためには、機械学習による手法が一般的に使用されます。以下に代表的な手法をいくつか紹介します。

ニューラルネットワーク
ニューラルネットワークは、深層

※期待と回答が異なったため停止

たこやき

Golangで数字を認識するニューラルネットワークを標準ライブラリだけで書いてください

ChatGPT

Golangの標準ライブラリだけで数字を認識するニューラルネットワークを実装することは可能ですが、MNISTなどのより複雑なデータセットを使用する場合は、より高度な数学ライブラリが必要になる場合があります。以下は、簡単な数字認識問題を解決するための単純な2層のニューラルネットワークの実装例です。

package main

import (
	"bufio"
	"encoding/csv"
	"fmt"
	"io"
	"log"
	"math/rand"
	"os"
	"strconv"
	"strings"
	"time"
)

type NeuralNetwork struct {
	inputSize  int
	hiddenSize int
	outputSize int
	weights1   [][]float64
	weights2   [][]float64
	bias1      []float64
	bias2      []float64
}

func sigmoid(x float64) float64 {
	return 1.0 / (1.0 + math.Exp(-x))
}

func sigmoidDerivative(x float64) float64 {
	return x * (1.0 - x)
}

func NewNeuralNetwork(inputSize, hiddenSize, outputSize int) *NeuralNetwork {
	nn := &NeuralNetwork{
		inputSize:  inputSize,
		hiddenSize: hiddenSize,
		outputSize: outputSize,
		weights1:   make([][]float64, inputSize),
		weights2:   make([][]float64, hiddenSize),
		bias1:      make([]float64, hiddenSize),
		bias2:      make([]float64, outputSize),
	}

	rand.Seed(time.Now().UnixNano())

	for i := range nn.weights1 {
		nn.weights1[i] = make([]float64, nn.hiddenSize)
		for j := range nn.weights1[i] {
			nn.weights1[i][j] = rand.Float64()
		}
	}

	for i := range nn.weights2 {
		nn.weights2[i] = make([]float64, nn.outputSize)
		for j := range nn.weights2[i] {
			nn.weights2[i][j] = rand.Float64()
		}
	}

	for i := range nn.bias1 {
		nn.bias1[i] = rand.Float64()
	}

	for i := range nn.bias2 {
		nn.bias2[i] = rand.Float64()
	}

	return nn
}

func (nn *NeuralNetwork) Forward(input []float64) []float64 {
	hidden := make([]float64, nn.hiddenSize)
	output := make([]float64, nn.outputSize)

	for i := range hidden {
		for j := range input {
			hidden[i] += input[j] * nn.weights1[j][i]
		}
		hidden[i] = sigmoid(hidden[i] + nn.bias1[i])
	}

	for i := range output {
		for j := range hidden {
			output[i] += hidden[j] * nn.weights2[j][i]
		}
		output[i] = sigmoid(output[i] + nn.bias2[i])
	}

	return output
}

func TrainNeuralNetwork(nn *NeuralNetwork, inputs [][]float64, outputs [][]float64, learningRate float64, epochs int) {
	for epoch := 0; epoch < epochs; epoch++ {
		for i := range inputs {
			input :=

たこやき

続きは?

ChatGPT

		output := outputs[i]
		hidden := make([]float64, nn.hiddenSize)
		outputLayer := make([]float64, nn.outputSize)

		// Forward propagation
		for j := range hidden {
			for k := range input {
				hidden[j] += input[k] * nn.weights1[k][j]
			}
			hidden[j] = sigmoid(hidden[j] + nn.bias1[j])
		}

		for j := range outputLayer {
			for k := range hidden {
				outputLayer[j] += hidden[k] * nn.weights2[k][j]
			}
			outputLayer[j] = sigmoid(outputLayer[j] + nn.bias2[j])
		}

		// Backpropagation
		outputLayerError := make([]float64, nn.outputSize)
		for j := range outputLayer {
			outputLayerError[j] = output[j] - outputLayer[j]
		}

		outputLayerDelta := make([]float64, nn.outputSize)
		for j := range outputLayerDelta {
			outputLayerDelta[j] = outputLayerError[j] * sigmoidDerivative(outputLayer[j])
		}

		hiddenError := make([]float64, nn.hiddenSize)
		for j := range hidden {
			for k := range outputLayerDelta {
				hiddenError[j] += outputLayerDelta[k] * nn.weights2[j][k]
			}
		}

		hiddenDelta := make([]float64, nn.hiddenSize)
		for j := range hiddenDelta {
			hiddenDelta[j] = hiddenError[j] * sigmoidDerivative(hidden[j])
		}

		// Update weights and biases
		for j := range nn.weights2 {
			for k := range nn.weights2[j] {
				nn.weights2[j][k] += learningRate * hidden[j] * outputLayerDelta[k]
			}
		}

		for j := range nn.weights1 {
			for k := range nn.weights1[j] {
				nn.weights1[j][k] += learningRate * input[j] * hiddenDelta[k]
			}
		}

		for j := range nn.bias2 {
			nn.bias2[j] += learningRate * outputLayerDelta[j]
		}

		for j := range nn.bias1 {
			nn.bias1[j] += learningRate * hiddenDelta[j]
		}
	}
}

}

func ReadCSVFile(filename string) ([][]float64, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()

reader := csv.NewReader(bufio.NewReader(file))
var data [][]float64
for {
	record, err := reader.Read()
	if err == io.EOF {
		break
	}
	if err != nil {
		return nil, err
	}
	row := make([]float64, len(record))
	for i, v := range record {
		val, err := strconv.ParseFloat(strings.TrimSpace(v), 64)
		if err != nil {
			return nil, err
		}
		row[i] = val
	}
	data = append(data, row)
}
return data, nil

}

func main() {
// Load training data
inputs, err := ReadCSVFile("data/digits.csv")
if err != nil {
log.Fatal(err)
}

// Create one-hot encoded labels
labels := [][]float64{
	{1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
	{0, 1, 0, 0,

たこやき

続きは?

	0, 0, 0, 0, 0},
	{0, 0, 0, 0, 0, 0, 0, 1, 0, 0},
	{0, 0, 0, 0, 0, 0, 0, 0, 1, 0},
	{0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
}

// Train neural network
nn := NewNeuralNetwork(len(inputs[0]), 16, len(labels[0]))
err = nn.Train(inputs, labels, 10000, 0.1)
if err != nil {
	log.Fatal(err)
}

// Load test data
testInputs, err := ReadCSVFile("data/digits_test.csv")
if err != nil {
	log.Fatal(err)
}

// Test neural network
for i := range testInputs {
	outputs := nn.Predict(testInputs[i])
	bestIndex := 0
	bestValue := 0.0
	for j := range outputs {
		if outputs[j] > bestValue {
			bestValue = outputs[j]
			bestIndex = j
		}
	}
	fmt.Printf("Predicted: %d\n", bestIndex)
}

}


このコードは、入力データと正解ラベルが含まれるCSVファイルを読み込み、数字の画像を分類するニューラルネットワークを訓練しています。訓練が完了すると、テストデータに対して予測を行い、最も確率の高いクラスを出力しています。

このコードは標準ライブラリだけで実装されているため、他のライブラリをインストールする必要はありません。ただし、CSVファイルを読み込むために、"os""csv""bufio""io""strconv""strings"の各パッケージをインポートしています。

たこやき

digits.csvはどこで入手できる?

ChatGPT

digits.csvファイルは、手書きの数字の画像データセットであるMNIST(Modified National Institute of Standards and Technology)の一部です。このデータセットは、多くの機械学習のチュートリアルで使用されています。

MNISTデータセットは、多くの場合、オンラインで入手できます。たとえば、以下のサイトからダウンロードできます。

http://yann.lecun.com/exdb/mnist/
または、GitHubのリポジトリからもダウンロードできます。

https://github.com/makeyourownneuralnetwork/mnist
ダウンロードしたファイルには、トレーニングセットとテストセットが含まれています。digits.csvは、トレーニングセットの一部であり、28x28ピクセルのグレースケール画像を表す784のピクセル値と、各画像に対する正解の数字のラベルが含まれています。

たこやき

すべて同じ数字として認識されるのだけど、次のコードは何がおかしい?// Golangで数字を認識するニューラルネットワークを標準ライブラリだけで書いてください

// Golangの標準ライブラリだけで数字を認識するニューラルネットワークを実装することは可能ですが、MNISTなどのより複雑なデータセットを使用する場合は、より高度な数学ライブラリが必要になる場合があります。以下は、簡単な数字認識問題を解決するための単純な2層のニューラルネットワークの実装例です。

package main

import (
"bufio"
"encoding/csv"
"fmt"
"io"
"log"
"math"
"math/rand"
"os"
"strconv"
"strings"
"time"
)

type NeuralNetwork struct {
inputSize int
hiddenSize int
outputSize int
weights1 [][]float64
weights2 [][]float64
bias1 []float64
bias2 []float64
}

func sigmoid(x float64) float64 {
return 1.0 / (1.0 + math.Exp(-x))
}

func sigmoidDerivative(x float64) float64 {
return x * (1.0 - x)
}

func NewNeuralNetwork(inputSize, hiddenSize, outputSize int) *NeuralNetwork {
nn := &NeuralNetwork{
inputSize: inputSize,
hiddenSize: hiddenSize,
outputSize: outputSize,
weights1: make([][]float64, inputSize),
weights2: make([][]float64, hiddenSize),
bias1: make([]float64, hiddenSize),
bias2: make([]float64, outputSize),
}

rand.Seed(time.Now().UnixNano())

for i := range nn.weights1 {
	nn.weights1[i] = make([]float64, nn.hiddenSize)
	for j := range nn.weights1[i] {
		nn.weights1[i][j] = rand.Float64()
	}
}

for i := range nn.weights2 {
	nn.weights2[i] = make([]float64, nn.outputSize)
	for j := range nn.weights2[i] {
		nn.weights2[i][j] = rand.Float64()
	}
}

for i := range nn.bias1 {
	nn.bias1[i] = rand.Float64()
}

for i := range nn.bias2 {
	nn.bias2[i] = rand.Float64()
}

return nn

}

func (nn *NeuralNetwork) Forward(input []float64) []float64 {
hidden := make([]float64, nn.hiddenSize)
output := make([]float64, nn.outputSize)

for i := range hidden {
	for j := range input {
		hidden[i] += input[j] * nn.weights1[j][i]
	}
	hidden[i] = sigmoid(hidden[i] + nn.bias1[i])
}

for i := range output {
	for j := range hidden {
		output[i] += hidden[j] * nn.weights2[j][i]
	}
	output[i] = sigmoid(output[i] + nn.bias2[i])
}

return output

}

func (nn *NeuralNetwork) TrainNeuralNetwork(inputs [][]float64, outputs [][]float64, learningRate float64, epochs int) {
for epoch := 0; epoch < epochs; epoch++ {
fmt.Println("epoch:",epoch)
for i := range inputs {
input := inputs[i]
output := outputs[i]
hidden := make([]float64, nn.hiddenSize)
outputLayer := make([]float64, nn.outputSize)

		// Forward propagation
		for j := range hidden {
			for k := range input {
				hidden[j] += input[k] * nn.weights1[k][j]
			}
			hidden[j] = sigmoid(hidden[j] + nn.bias1[j])
		}

		for j := range outputLayer {
			for k := range hidden {
				outputLayer[j] += hidden[k] * nn.weights2[k][j]
			}
			outputLayer[j] = sigmoid(outputLayer[j] + nn.bias2[j])
		}

		// Backpropagation
		outputLayerError := make([]float64, nn.outputSize)
		for j := range outputLayer {
			outputLayerError[j] = output[j] - outputLayer[j]
		}

		outputLayerDelta := make([]float64, nn.outputSize)
		for j := range outputLayerDelta {
			outputLayerDelta[j] = outputLayerError[j] * sigmoidDerivative(outputLayer[j])
		}

		hiddenError := make([]float64, nn.hiddenSize)
		for j := range hidden {
			for k := range outputLayerDelta {
				hiddenError[j] += outputLayerDelta[k] * nn.weights2[j][k]
			}
		}

		hiddenDelta := make([]float64, nn.hiddenSize)
		for j := range hiddenDelta {
			hiddenDelta[j] = hiddenError[j] * sigmoidDerivative(hidden[j])
		}

		// Update weights and biases
		for j := range nn.weights2 {
			for k := range nn.weights2[j] {
				nn.weights2[j][k] += learningRate * hidden[j] * outputLayerDelta[k]
			}
		}

		for j := range nn.weights1 {
			for k := range nn.weights1[j] {
				nn.weights1[j][k] += learningRate * input[j] * hiddenDelta[k]
			}
		}

		for j := range nn.bias2 {
			nn.bias2[j] += learningRate * outputLayerDelta[j]
		}

		for j := range nn.bias1 {
			nn.bias1[j] += learningRate * hiddenDelta[j]
		}
	}
}

}

func ReadCSVFile(filename string) ([][]float64, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
reader := csv.NewReader(bufio.NewReader(file))
var data [][]float64
for {
record, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
row := make([]float64, len(record))
for i, v := range record {
val, err := strconv.ParseFloat(strings.TrimSpace(v), 64)
if err != nil {
return nil, err
}
// row[i] = val
if i != 0 {
row[i] = val / 256.0 + 0.0001
} else {
row[0] = val
}
}
data = append(data, row)
}
return data, nil
}

func main() {
// Load training data
inputs, err := ReadCSVFile("data/mnist_train.csv")
if err != nil {
log.Fatal(err)
}
// Create one-hot encoded labels
labels := [][]float64{
{1, 0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 0},
{0.00001, 1, 0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 0},
{0.00001, 0.00001, 1, 0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 0},
{0.00001, 0.00001, 0.00001, 1, 0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 0},
{0.00001, 0.00001, 0.00001, 0.00001, 1, 0.00001, 0.00001, 0.00001, 0.00001, 0},
{0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 1, 0.00001, 0.00001, 0.00001, 0},
{0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 1, 0.00001, 0.00001, 0},
{0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 1, 0.00001, 0},
{0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 1, 0},
{0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 0.00001, 1},
}

// make outputs
outputs := [][]float64{}
for _, input := range inputs {
	outputs = append(outputs, labels[int(input[0])])
}

// Train neural network
nn := NewNeuralNetwork(len(inputs[0]), 16, len(labels[0]))
nn.TrainNeuralNetwork(inputs, outputs, 0.1, 10)
// if err != nil {
// 	log.Fatal(err)
// }

// Load test data
testInputs, err := ReadCSVFile("data/mnist_test.csv")
if err != nil {
	log.Fatal(err)
}

// Test neural network
for i := range testInputs {
	fmt.Println(testInputs[i][0])
	outputs := nn.Forward(testInputs[i])
	bestIndex := 0
	bestValue := 0.0

	for j := range outputs {
		if outputs[j] > bestValue {
			bestValue = outputs[j]
			bestIndex = j
		}
	}
	fmt.Printf("Predicted: %d\n", bestIndex)
	fmt.Println(outputs)
}

}

// このコードは、入力データと正解ラベルが含まれるCSVファイルを読み込み、数字の画像を分類するニューラルネットワークを訓練しています。訓練が完了すると、テストデータに対して予測を行い、最も確率の高いクラスを出力しています。
// このコードは標準ライブラリだけで実装されているため、他のライブラリをインストールする必要はありません。ただし、CSVファイルを読み込むために、"os"、"csv"、"bufio"、"io"、"strconv"、"strings"の各パッケージをインポートしています。

// digits.csvはどこで入手できる?

// digits.csvファイルは、手書きの数字の画像データセットであるMNIST(Modified National Institute of Standards and Technology)の一部です。このデータセットは、多くの機械学習のチュートリアルで使用されています。

// MNISTデータセットは、多くの場合、オンラインで入手できます。たとえば、以下のサイトからダウンロードできます。

// http://yann.lecun.com/exdb/mnist/
// または、GitHubのリポジトリからもダウンロードできます。

// https://github.com/makeyourownneuralnetwork/mnist
// ダウンロードしたファイルには、トレーニングセットとテストセットが含まれています。digits.csvは、トレーニングセットの一部であり、28x28ピクセルのグレースケール画像を表す784のピクセル値と、各画像に対する正解の数字のラベルが含まれています。

ChatGPT

このコードには明らかなエラーはありませんが、実際に数字を認識するためには、トレーニングデータセットを提供する必要があります。また、ニューラルネットワークのトレーニングがどのように実行されるかを示す必要があります。

このコードでは、TrainNeuralNetwork関数が提供されており、トレーニングデータを取得し、エポックと学習率を指定します。Forward関数は、トレーニング後に新しい入力を提供すると、ニューラルネットワークが予測する出力を返します。

つまり、このコードを使用して数字を認識するためには、MNISTなどの数字データセットを取得し、トレーニングを実行する必要があります。また、Forward関数を使用して、トレーニング後のニューラルネットワークが提供された画像でどの数字を認識するかを予測することができます。

たこやき

正しく認識されないのだけど

ChatGPT

このコードには、数字認識の問題を解決するために必要ないくつかの機能が欠落しているため、正しく動作しない可能性があります。具体的には、以下の問題があります。

  1. ニューラルネットワークがトレーニングデータを読み込んでいない
    このコードでは、トレーニングデータを読み込むためのコードが欠落しています。ニューラルネットワークが正しく動作するためには、トレーニングデータを読み込み、トレーニングサンプルに対する予測値を計算する必要があります。

  2. 活性化関数が誤って実装されている
    このコードでは、sigmoid関数とその導関数が誤って実装されています。sigmoid関数の導関数は、sigmoid(x) * (1 - sigmoid(x))である必要がありますが、現在の実装では、x * (1 - x)となっています。

  3. ニューラルネットワークが小さすぎる
    このコードでは、ニューラルネットワークが2層しかないため、より複雑なデータセットを扱うことができません。MNISTなどのデータセットを使用する場合は、より深いニューラルネットワークが必要になる場合があります。

  4. 学習率が高すぎる
    このコードでは、学習率が0.1に設定されています。しかし、この学習率は高すぎるため、収束しない場合があります。適切な学習率を見つけるには、実験的に試す必要があります。

これらの問題を修正することで、ニューラルネットワークを正しく機能させることができるようになります。

Discussion