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