Javaでbpニューラルネットワークを書く(三)

6004 ワード

孔子の曰わく、吾日三省吾身.私たちがプログラムと付き合うと、一日三省吾身のほかに、三日一省吾コードが必要です.コードがより簡潔で、より分かりやすく、拡張しやすく、より汎用的であるかどうか、アルゴリズムが再最適化できるかどうか、構造がより抽象的であるかどうかを見る.コードは絶えず再構築する過程で、より進化している.くるまを巻く者は蜩を受け、大工が剣を鋳造するのも同じで、芸は小さいが、その道は一つである.いわゆる苟日新、再日新、日新.
今回は前の2つの文章コードを再構築し,主に関数インタフェース体系と重み行列のパッケージを再構築した.
単純関数
関数とは、数学の概念上の関数です.数学的な関数には、一般的に引数$x$(入力)と対応する値$y=f(x)$(出力)があります.ここで、$x$は、数値、ベクトル、マトリクスなどであってもよい.一般的な定義は次のとおりです.
public interface Function<I,O> {

  O valueAt(I x);

}


Iは入力タイプを表し、Oは出力タイプを表す.
ニューラルネットワークの活性化関数など、微小な関数もあります.マイクロ関数は、1つの関数に加えて、所与の$x$における導関数、または勾配を求めることができる.また、勾配タイプは引数タイプと一致します.一般的な定義は次のとおりです.
public interface DifferentiableFunction<I,O> extends Function<I,O> {

  I derivativeAt(I x);

}


同時に,いくつかの関数を考慮して,値と導関数を求める際に,いくつかの中間変数を共に用いたか,あるいは後者が前者に用いられる結果を考慮して,PreCaculateインタフェースを定義した.関数がPreCaculateインタフェースを実装していると判定すると、まずそのPreCaculateインタフェースを呼び出し、事前にいくつかの有用な中間変数を計算してから、valueAtとderivativeAtを呼び出して具体的な値を求めることで、いくつかの操作手順を節約することができます.次のように定義します.
public interface PreCaculate<I> {

	void preCaculate(I x);

}


上記の定義に基づいて、ニューラルネットワークのアクティブ化関数のタイプを定義します.
public interface ActivationFunction extends DifferentiableFunction<DoubleMatrix, DoubleMatrix>


すなわち,アクティブ化関数はマイクロ関数であり,入力はマトリクス(netResult),出力はマトリクス(finalResult)である.
パラメトリック関数
いくつかの関数は、引数のほかに、いくつかの他の係数、またはパラメータがあり、スーパーパラメータと呼ばれています.例えば誤差関数は,目標値がパラメータ,出力値が引数である.このような関数インタフェースは、次のように定義されます.
public interface ParamFunction<I,O,P> {

	O valueAt(I x,P param);

}


同様に、微分インタフェースを以下のように定義します.
public interface DifferentiableParamFunction<I, O, P> extends ParamFunction<I, O, P> {

	I derivativeAt(I x,P param);

}


我々の誤差関数は次のように定義されています.
public interface CostFunction extends DifferentiableParamFunction<DoubleMatrix,DoubleMatrix,DoubleMatrix>


入力、出力、パラメータはマトリクスです.
コンビネーションマトリクス
ニューラルネットワークの概念では,各2層の間に重み行列,バイアス行列があり,入力ワードベクトルも調整する場合,辞書行列もある.これらのすべての行列は反復過程とともに更新され,誤差関数を最小限に抑えることを期している.広義には,訓練サンプルは超パラメータであり,これらのすべての行列は自己変数であり,誤差関数は最適化関数である.実質的には、重みマトリクスを調整する際に、自己変数、すなわち一連のマトリクスを拡張して超長ベクトルに接合することができ、その内部の構造は重要ではない.jareのソースコードでは,これらの重み行列の値を長いdouble[]に格納し,計算が完了した後,このdoulbe[]から各行列の構造を復元する.ここでは、スーパーマトリクスというクラスCompactDoubleMatrixを定義し、これらのマトリクス変数をより高いレベルからカプセル化し、マトリクスのように外部に表現します.
このCompactDoubleMatrixの実現形態は、内部でDoubleMatrixのシーケンステーブルListを維持し、その後、加算減算乗算を実行すると、リスト内のすべてのマトリクスに対して一括して実行される.このようなパッケージは、私たちが大量のコードを簡略化することを発見します.まず完全な定義を載せます.
public class CompactDoubleMatrix {

	List<DoubleMatrix> mats = new ArrayList<DoubleMatrix>();



	@SafeVarargs

	public CompactDoubleMatrix(List<DoubleMatrix>... matListArray) {

		super();

		this.append(matListArray);

	}



	public CompactDoubleMatrix(DoubleMatrix... matArray) {

		super();

		this.append(matArray);

	}



	public CompactDoubleMatrix() {

		super();

	}



	public CompactDoubleMatrix addi(CompactDoubleMatrix other) {

		this.assertSize(other);

		for (int i = 0; i < this.length(); i++)

			this.get(i).addi(other.get(i));

		return this;

	}



	public void subi(CompactDoubleMatrix other) {

		this.assertSize(other);

		for (int i = 0; i < this.length(); i++)

			this.get(i).subi(other.get(i));

	}



	public CompactDoubleMatrix add(CompactDoubleMatrix other) {

		this.assertSize(other);

		CompactDoubleMatrix result = new CompactDoubleMatrix();

		for (int i = 0; i < this.length(); i++) {

			result.append(this.get(i).add(other.get(i)));

		}

		return result;

	}



	public CompactDoubleMatrix sub(CompactDoubleMatrix other) {

		this.assertSize(other);

		CompactDoubleMatrix result = new CompactDoubleMatrix();

		for (int i = 0; i < this.length(); i++) {

			result.append(this.get(i).sub(other.get(i)));

		}

		return result;

	}



	public CompactDoubleMatrix mul(CompactDoubleMatrix other) {

		this.assertSize(other);

		CompactDoubleMatrix result = new CompactDoubleMatrix();

		for (int i = 0; i < this.length(); i++) {

			result.append(this.get(i).mul(other.get(i)));

		}

		return result;

	}



	public CompactDoubleMatrix muli(double d) {



		for (int i = 0; i < this.length(); i++) {

			this.get(i).muli(d);

		}

		return this;

	}



	public CompactDoubleMatrix mul(double d) {

		CompactDoubleMatrix result = new CompactDoubleMatrix();

		for (int i = 0; i < this.length(); i++) {

			result.append(this.get(i).mul(d));

		}

		return result;

	}



	public CompactDoubleMatrix dup() {

		CompactDoubleMatrix result = new CompactDoubleMatrix();

		for (int i = 0; i < this.length(); i++) {

			result.append(this.get(i).dup());

		}

		return result;

	}



	public double dot(CompactDoubleMatrix other) {

		double sum = 0;

		for (int i = 0; i < this.length(); i++) {

			sum += this.get(i).dot(other.get(i));

		}

		return sum;

	}



	public double norm() {

		double sum = 0;

		for (int i = 0; i < this.length(); i++) {

			double subNorm = this.get(i).norm2();

			sum += subNorm * subNorm;

		}

		return Math.sqrt(sum);

	}



	public void assertSize(CompactDoubleMatrix other) {

		assert (other != null && this.length() == other.length());

		for (int i = 0; i < this.length(); i++) {

			assert (this.get(i).sameSize(other.get(i)));

		}

	}



	@SuppressWarnings("unchecked")

	public void append(List<DoubleMatrix>... matListArray) {

		for (List<DoubleMatrix> list : matListArray) {

			this.mats.addAll(list);

		}

	}



	public void append(DoubleMatrix... matArray) {

		for (DoubleMatrix mat : matArray)

			this.mats.add(mat);

	}



	public int length() {

		return mats.size();

	}



	public DoubleMatrix get(int index) {

		return this.mats.get(index);

	}



	public DoubleMatrix getLast() {

		return this.mats.get(this.length() - 1);

	}

}


以上,各抽象概念のカプセル化について述べたが,次の章では,これらのカプセル化を用いて我々のコードをどのように簡略化するかについて説明する.