using System; using System.Collections.Generic; using System.IO; using System.Text.Json; // 追加: JSON操作用 namespace DeepLearning { public class Matrix { private readonly double[,] _data; public int Rows { get; } public int Cols { get; } public Matrix(int rows, int cols) { Rows = rows; Cols = cols; _data = new double[rows, cols]; } public Matrix(double[,] data) { Rows = data.GetLength(0); Cols = data.GetLength(1); _data = (double[,])data.Clone(); } public double this[int r, int c] { get { return _data[r, c]; } set { _data[r, c] = value; } } // --- 演算子オーバーロード --- public static Matrix operator +(Matrix a, Matrix b) { if (a.Rows != b.Rows || a.Cols != b.Cols) throw new ArgumentException("行列のサイズが一致しません。"); var result = new Matrix(a.Rows, a.Cols); for (int i = 0; i < a.Rows; i++) for (int j = 0; j < a.Cols; j++) result[i, j] = a[i, j] + b[i, j]; return result; } public static Matrix operator -(Matrix a, Matrix b) { if (a.Rows != b.Rows || a.Cols != b.Cols) throw new ArgumentException("行列のサイズが一致しません。"); var result = new Matrix(a.Rows, a.Cols); for (int i = 0; i < a.Rows; i++) for (int j = 0; j < a.Cols; j++) result[i, j] = a[i, j] - b[i, j]; return result; } public static Matrix operator *(Matrix a, Matrix b) { if (a.Cols != b.Rows) throw new ArgumentException("左の行列の列数と右の行列の行数が一致しません。"); var result = new Matrix(a.Rows, b.Cols); for (int i = 0; i < result.Rows; i++) { for (int j = 0; j < result.Cols; j++) { double sum = 0; for (int k = 0; k < a.Cols; k++) sum += a[i, k] * b[k, j]; result[i, j] = sum; } } return result; } public static Matrix operator *(Matrix a, double scalar) { var result = new Matrix(a.Rows, a.Cols); for (int i = 0; i < a.Rows; i++) for (int j = 0; j < a.Cols; j++) result[i, j] = a[i, j] * scalar; return result; } public static Matrix operator *(double scalar, Matrix a) { return a * scalar; } // --- 転置行列 (Transpose) --- public Matrix Transpose() { var result = new Matrix(Cols, Rows); // 行と列のサイズを入れ替え for (int i = 0; i < Rows; i++) { for (int j = 0; j < Cols; j++) { result[j, i] = _data[i, j]; // 成分を入れ替え } } return result; } // --- 逆行列 (Inverse) --- public Matrix Inverse() { if (Rows != Cols) throw new InvalidOperationException("正方行列ではありません。"); int n = Rows; double[,] augmented = new double[n, 2 * n]; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { augmented[i, j] = _data[i, j]; augmented[i, j + n] = (i == j) ? 1.0 : 0.0; } } for (int i = 0; i < n; i++) { double pivot = augmented[i, i]; int pivotRow = i; for (int k = i + 1; k < n; k++) { if (Math.Abs(augmented[k, i]) > Math.Abs(pivot)) { pivot = augmented[k, i]; pivotRow = k; } } if (Math.Abs(pivot) < 1e-10) throw new InvalidOperationException("特異行列のため逆行列が存在しません。"); if (pivotRow != i) { for (int j = 0; j < 2 * n; j++) { double temp = augmented[i, j]; augmented[i, j] = augmented[pivotRow, j]; augmented[pivotRow, j] = temp; } } for (int j = 0; j < 2 * n; j++) augmented[i, j] /= pivot; for (int k = 0; k < n; k++) { if (k != i) { double factor = augmented[k, i]; for (int j = 0; j < 2 * n; j++) augmented[k, j] -= factor * augmented[i, j]; } } } var inverse = new Matrix(n, n); for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) inverse[i, j] = augmented[i, j + n]; return inverse; } public override string ToString() { string s = ""; for (int i = 0; i < Rows; i++) { s += "| "; for (int j = 0; j < Cols; j++) s += $"{_data[i, j],6:F2} "; s += "|\n"; } return s; } // 1. 各要素に関数を適用する (Map) // 例: 行列の全要素にシグモイド関数を掛ける時などに使用 public Matrix Map(Func<double, double> func) { var result = new Matrix(Rows, Cols); for (int i = 0; i < Rows; i++) for (int j = 0; j < Cols; j++) result[i, j] = func(_data[i, j]); return result; } // 2. 要素ごとの掛け算 (アダマール積) // 通常の行列積(*)とは異なり、同じ位置の成分同士を掛け合わせる public static Matrix ElementWiseMultiply(Matrix a, Matrix b) { if (a.Rows != b.Rows || a.Cols != b.Cols) throw new ArgumentException("行列のサイズが一致しません。"); var result = new Matrix(a.Rows, a.Cols); for (int i = 0; i < a.Rows; i++) for (int j = 0; j < a.Cols; j++) result[i, j] = a[i, j] * b[i, j]; return result; } // 1. 行列データを「配列の配列(double[][])」に変換して取り出すメソッド // (JSON保存用) public double[][] ToJaggedArray() { double[][] result = new double[Rows][]; for (int i = 0; i < Rows; i++) { result[i] = new double[Cols]; for (int j = 0; j < Cols; j++) { result[i][j] = _data[i, j]; } } return result; } // 2. 「配列の配列」から行列を作る静的メソッド // (JSON読み込み用) public static Matrix FromJaggedArray(double[][] data) { int rows = data.Length; int cols = data[0].Length; var matrix = new Matrix(rows, cols); for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { matrix[i, j] = data[i][j]; } } return matrix; } } public class NeuralNetwork { // 層ごとの重みとバイアスのリスト // weights[0] は 入力層->隠れ層1、weights[1] は 隠れ層1->隠れ層2 ... private List<Matrix> weights; private List<Matrix> biases; private double learningRate; // シグモイド関数 private double Sigmoid(double x) => 1.0 / (1.0 + Math.Exp(-x)); private double SigmoidDerivative(double y) => y * (1.0 - y); // コンストラクタ // inputSize: 入力数 // outputSize: 出力数 // hiddenSizes: 隠れ層のノード数を配列で指定 (例: new int[]{ 4, 4 } なら2層) public NeuralNetwork(int inputSize, int outputSize, int[] hiddenSizes, double learnRate = 0.5) { learningRate = learnRate; weights = new List<Matrix>(); biases = new List<Matrix>(); // 全層のノード数を1つのリストにまとめる // [入力数, 隠れ1, 隠れ2, ..., 出力数] var layerSizes = new List<int>(); layerSizes.Add(inputSize); layerSizes.AddRange(hiddenSizes); layerSizes.Add(outputSize); // 各層の間の重みとバイアスを初期化 for (int i = 0; i < layerSizes.Count - 1; i++) { int inputNodes = layerSizes[i]; int outputNodes = layerSizes[i + 1]; weights.Add(RandomMatrix(inputNodes, outputNodes)); biases.Add(RandomMatrix(1, outputNodes)); } } // 学習メソッド public void Train(double[,] inputsArray, double[,] targetsArray, int epochs) { Matrix inputs = new Matrix(inputsArray); Matrix targets = new Matrix(targetsArray); for (int e = 0; e < epochs; e++) { for (int r = 0; r < inputs.Rows; r++) { // 1. 入力データの抽出 Matrix currentInput = ExtractRow(inputs, r); Matrix target = ExtractRow(targets, r); // --- 順伝播 (Forward) --- // 各層の出力を保存しておくリスト (逆伝播で使うため) // outputs[0]は入力層そのもの、outputs[1]は隠れ層1の出力... var outputs = new List<Matrix>(); outputs.Add(currentInput); for (int i = 0; i < weights.Count; i++) { // 次の層への入力 = (今の層の出力 * 重み) + バイアス Matrix layerInput = (outputs[i] * weights[i]) + biases[i]; // 活性化関数を通す Matrix layerOutput = layerInput.Map(Sigmoid); outputs.Add(layerOutput); } // --- 誤差逆伝播 (Back Propagation) --- // 最終層の誤差 Matrix finalOutput = outputs[outputs.Count - 1]; Matrix error = target - finalOutput; // 後ろの層から手前の層へループ for (int i = weights.Count - 1; i >= 0; i--) { Matrix layerOutput = outputs[i + 1]; // 現在計算している層の出力 Matrix prevLayerOutput = outputs[i]; // 一つ手前の層の出力 // 勾配の計算 Matrix gradient = Matrix.ElementWiseMultiply(error, layerOutput.Map(SigmoidDerivative)); gradient = gradient * learningRate; // 重みの更新量 (Delta) Matrix weightDelta = prevLayerOutput.Transpose() * gradient; // 次のループ(手前の層)のために、誤差を伝播させておく // ※重みを更新する前に計算する必要がある Matrix prevError = gradient * weights[i].Transpose(); // 重みとバイアスの更新 weights[i] = weights[i] + weightDelta; biases[i] = biases[i] + gradient; // 誤差を更新して次のループへ error = prevError; } } } } // テスト(予測)メソッド public double[,] Predict(double[,] inputArray) { Matrix input = new Matrix(inputArray); // 複数行の入力に対応 var results = new double[input.Rows, weights[weights.Count - 1].Cols]; for (int r = 0; r < input.Rows; r++) { Matrix currentSignal = ExtractRow(input, r); // 順伝播のみ行う for (int i = 0; i < weights.Count; i++) { currentSignal = ((currentSignal * weights[i]) + biases[i]).Map(Sigmoid); } // 結果を配列に格納 for (int c = 0; c < currentSignal.Cols; c++) { results[r, c] = currentSignal[0, c]; } } return results; } // --- 内部ヘルパー --- private Matrix RandomMatrix(int rows, int cols) { var rand = new Random(); var m = new Matrix(rows, cols); for (int i = 0; i < rows; i++) for (int j = 0; j < cols; j++) m[i, j] = (rand.NextDouble() * 2) - 1; return m; } private Matrix ExtractRow(Matrix m, int rowIndex) { var result = new Matrix(1, m.Cols); for (int c = 0; c < m.Cols; c++) result[0, c] = m[rowIndex, c]; return result; } // ========================================================= // 保存用のデータ構造 (内部クラス) // JSONにするために、Matrixではなくdouble[][]で持ちます // ========================================================= public class ModelData { public int InputSize { get; set; } public int OutputSize { get; set; } public int[] HiddenSizes { get; set; } public List<double[][]> Weights { get; set; } // 重みデータ public List<double[][]> Biases { get; set; } // バイアスデータ } // ========================================================= // 保存メソッド (Save) // ========================================================= public void Save(string filePath) { // 1. 保存用のデータ入れ物を作る var data = new ModelData(); // ネットワーク構造の記録(読み込み時の再現に必要) // 入力層のサイズは weights[0] の行数 data.InputSize = weights[0].Rows; // 出力層のサイズは 最後のweights の列数 data.OutputSize = weights[weights.Count - 1].Cols; // 隠れ層のサイズの復元リスト var hiddenList = new List<int>(); // weights[0]の列数が「隠れ層1」のサイズ... という風に取得 for (int i = 0; i < weights.Count - 1; i++) { hiddenList.Add(weights[i].Cols); } data.HiddenSizes = hiddenList.ToArray(); // 2. Matrixデータを配列に変換して格納 data.Weights = new List<double[][]>(); foreach (var w in weights) data.Weights.Add(w.ToJaggedArray()); data.Biases = new List<double[][]>(); foreach (var b in biases) data.Biases.Add(b.ToJaggedArray()); // 3. JSON文字列に変換してファイル保存 // (WriteIndented = true で人間が読みやすい形式にする) string jsonString = JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true }); File.WriteAllText(filePath, jsonString); } // ========================================================= // 読み込みメソッド (Load) - 静的メソッド // ファイルから新しい NeuralNetwork インスタンスを作って返します // ========================================================= public static NeuralNetwork Load(string filePath) { if (!File.Exists(filePath)) { throw new FileNotFoundException("ファイルが見つかりません", filePath); } // 1. ファイルを読んでJSONからデータに戻す string jsonString = File.ReadAllText(filePath); var data = JsonSerializer.Deserialize<ModelData>(jsonString); // 2. ニューラルネットワークのインスタンスを生成 // (保存されていた構造情報を使う) var nn = new NeuralNetwork(data.InputSize, data.OutputSize, data.HiddenSizes); // 3. 重みとバイアスを上書きする // 初期化時はランダムな値が入っているので、保存された値に入れ替える nn.weights.Clear(); foreach (var wData in data.Weights) { nn.weights.Add(Matrix.FromJaggedArray(wData)); } nn.biases.Clear(); foreach (var bData in data.Biases) { nn.biases.Add(Matrix.FromJaggedArray(bData)); } return nn; } } }