C#で行列計算プログラムを作成(基本計算と行列表示メソッド)
C#で行列計算プログラムを作成
はじめに
今回は、自作の行列計算クラスの説明を行います。
私は一応大学で数学を学んでいたこともあり、学生時代に行列計算のプログラムを作成したことがありました。
どのプログラミング言語でも行列計算のライブラリは存在すると思います。しかし、自分でコードを書くことに意味があると思うので今回はメソッドとして作成を行っていきたいと思います。
NumPyでよくねって思った人。その通りだと思いますが、私はNumPyには負けない(?)
作成自体は終わっているので、解説を書いていく感じになります。ちなみに言語はC#です。
今回のコードはこちら
行列の足し算、引き算
まずは、簡単なところから作成します。足し算です。
行列は2次元配列で表すので、引数はdouble型の2次元配列を2つ指定します。計算した結果も行列を返すので、戻り値はdouble型の2次元配列となります。
行列の足し算の仕組みは以下のようになっています。
計算前のチェックとして、引数の2つの行列(配列)の型が等しいかを確認する必要があります。〇行△列の〇と△の値が等しくないと計算ができません。計算ができない場合は、nullを返すようにします。
コードは以下のようになります。
public static double[,] MatrixSum( double[,] x, double[,] y )
{
if( x.GetLength(0) == y.GetLength(0) && x.GetLength(1) == y.GetLength(1) )
{
double[,] z = new double[x.GetLength(0), x.GetLength(1)];
for (int i=0;i<x.GetLength(0);i++)
{
for (int w=0;w<x.GetLength(1);w++)
{
z[i, w] = x[i, w] + y[i, w];
}
}
return z;
}
else
{
Console.WriteLine( "Matrix type mismatch." );
return null;
}
}
行列計算では基本的に2重ループを組んでいきます。ここで注意が必要なことがあります。それは、引数の2次元配列に計算結果を代入することです。これは以前紹介した参照渡しと値渡し、引数には注意が必要で書いたのですが、配列はクラス型のためメソッド内で変更したものはメソッドを出た後も変更したデータが残ります。
次は引き算です。
引き算は足し算と仕組みは同じで、「+」を「-」にするだけで行けます。 コードは以下のようになります。
public static double[,] MatrixSubtraction( double[,] x, double[,] y )
{
if( x.GetLength(0) == y.GetLength(0) && x.GetLength(1) == y.GetLength(1) )
{
double[,] z = new double[x.GetLength(0), x.GetLength(1)];
for (int i=0;i<x.GetLength(0);i++)
{
for (int w=0;w<x.GetLength(1);w++)
{
z[i, w] = x[i, w] - y[i, w];
}
}
return z;
}
else
{
Console.WriteLine( "Matrix type mismatch." );
return null;
}
}
行列の足し算と引き算は、計算部分の演算子が異なるだけ、かつ、複雑なコードではないのですぐに実装できると思います。
行列の掛け算
次は行列の掛け算を実装しましょう。
掛け算も足し算とかと同様に、戻り地はdouble型の2次元配列で、引数はdouble型の2次元配列です。
足し算と引き算は2重ループであったのに対して、掛け算は3重ループで実装します。
掛け算は計算方法がやや複雑なので一つ一つ見ていきましょう。
計算をするときには、左の行列と右の行列で動作が変わります。
左の行列は行のみ操作し、右の行列は列のみ操作します。
- 左の行列の1行目と右の行列の1列目を計算
- 左の行列の1行目と右の行列の2列目を計算
- 左の行列の2行目と右の行列の1列目を計算
- 左の行列の2行目と右の行列の2列目を計算
という順番に計算を行っていきます。このように計算過程を書くと複雑に感じますが、これをコードで書くとものすごくシンプルになります。
ではコードを見てみましょう。
public static double[,] MatrixProduct( double[,] x, double[,] y )
{
if( x.GetLength(1) == y.GetLength(0) )
{
double[,] product = new double[x.GetLength(0),y.GetLength(1)];
for(int i=0;i<x.GetLength(0);i++)
{
for(int w=0;w<y.GetLength(1);w++)
{
for(int v=0;v<x.GetLength(1);v++)
{
product[i, w] += x[i, v] * y[v, w];
}
}
}
return product;
}
else
{
Console.WriteLine( "Matrix type mismatch." );
return null;
}
}
このように、3重ループの中に、計算式を1つ加えるだけです。なお行列の掛け算も計算できるかの確認を行い、できない場合はnullを返します。掛け算の場合は、左の行列の行の数と右の行列の列の数が等しくなくてはいけません。
3重ループの仕組みは、左の行列の列を操作する変数「 i 」と右の行列の行を操作する変数「 w 」、左の行列の行と右の行列の列を操作する変数「 v 」となっています。
計算自体は掛け算と足し算しかありません。そのため、それぞれの配列の要素をかけ、格納する配列の場所に加えていくという処理で実装しています。これで行列の掛け算は実装できました。
行列の掛け算は、数学だけでなく、ディープラーニングなどの処理でも使われているので実装できるようにしておくといいでしょう。
(といっても基本的にライブラリで済ませますが、、、)
行列の出力
今回は行列を2次元配列で実装しているため、計算結果を確かめるために出力するとき2重ループを組みます。
何回も2重ループを書くのは大変なので、行列を出力するメソッドも作成しておきます。
また、行列の要素は小数になることもあるため、double型で実装しています。そのため小数点以下の数字の数で出力が見づらくなるときがあります。
これを解消するために、文字数の最大値を得るメソッドも合わせて実装します。
public static void MatrixPrint(double[,] x)
{
if(x != null)
{
int n = MaxDigit(x);
Console.WriteLine("[");
for (int i = 0; i < x.GetLength(0); i++)
{
for (int w = 0; w < x.GetLength(1); w++)
{
Console.Write(" {0," + n + "} ", x[i, w]);
}
Console.WriteLine("");
}
Console.WriteLine("]");
}
else
{
Console.WriteLine("Matrix is null.");
}
}
private static int MaxDigit( double[,] x )
{
int max = 0;
for(int i=0;i<x.GetLength(0);i++)
{
for(int w=0;w<x.GetLength(1);w++)
{
if(max < x[i, w].ToString().Length)
{
max = x[i, w].ToString().Length;
}
}
}
return max;
}
MaxDigitは外から呼び出す予定はないのでprivateにしています。MatrixPrintには、2次元配列を引数に渡すことで調整して表示ができます。
まとめ
ということで今回は、行列計算の自作クラスの足し算と引き算、掛け算を実装し、表示するためのメソッドも作成しました。
数学をやってない人は行列計算をする機会があまりないと思いますが、プログラミングやっている人にはコードの参考に、数学やってる人には便利ツールとして活用してもらえれば幸いです。
次回は、行列式や逆行列などを実装します。
今回はこの辺で、ではまた!