HOME > natural science Laboratory > コンピュータ・シミュレーション講座 > ゼロから作るDeep Learning

ゼロから作るDeep Learning
順伝播型ニューラルネットワーク「FFNNクラス」の実装(JavaScript)

文責:遠藤 理平 (2017年4月23日) カテゴリ:ゼロから作るDeep Learning(15)

昨今注目を集めているAI(人工知能)を学びたいと思い立ち、ディープラーニング(Deep Learning、深層学習)と呼ばれるAIの数理モデルである多層構造のニューラルネットワークを書籍「ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装」を参考にを独習していきたいと思います。本書籍ではプログラミング言語としてPythonが利用されていますが、本項ではJavaScriptで実装していきます。

目次


順伝播型ニューラルネットワーク「FFNNクラス」の実装

前項の誤差逆伝搬法(バックプロパゲーション)を組み込んだ順伝播型ニューラルネットワークを実現するクラスを示します。

FNNNクラス

第一引数:初期重み多重配列(必須)
第二引数:初期バイアス多重配列(必須)
第三引数:学習率
第四引数:活性化関数
第五引数:活性化関数の導関数
第六引数:最終層の活性化関数
第七引数:最終層の活性化関数の導関数

//順伝播型ニューラルネットワーク
var FNNN = function( W, B, eta, h, hd, sigma, sigmad ){
	//重み
	this.W = W;
	//W.length : 層数
	//W[].length : 列(後ニューロン数)
	//W[][].length : 行(前ニューロン数)

	//1ステップ前のWを保持する多重配列
	this._W = [];

	//バイアス
	this.B = B;
	this._B = [];

	//偏微分値を格納する多重配列
	this.dEdW = [];
	this.dEdB = [];

	//誤差逆伝搬法で計算した偏微分値を格納する多重配列
	this._dEdW = [];
	this._dEdB = [];

	//活性化関数(ReLU関数)
	this.h = h || function( x ){
		if( x >= 0 ) return x;
		else return 0;
	}
	//活性化関数の微分
	this.hd = hd || function( x ){
		if( x >= 0 ) return 1;
		else return 0;
	}
	//出力層の活性化関数(恒等関数)
	this.sigma = sigma || function( x ){
		return x;
	}
	this.sigmad = sigmad ||  function( x ){
		return 1;
	}

	//ニューロンの初期化
	this.X = [];

	//活性化関数を通す前のニューロン
	this.x = [];

	//誤差逆伝搬法のデルタ値
	this.delta = [];

	//学習効率
	this.eta = eta || 0.1;

	this.setup();

}

//各種プロパティの初期化
FNNN.prototype.setup = function(  ){

	////////////////////////////////////////////
	// ニューロンの初期化
	////////////////////////////////////////////
	for( var i = 0; i < this.W.length; i++ ){

		this.X[ i ] = [];
		this.x[ i ] = [];
		this.delta[ i ] = [];

		for( var j = 0; j < this.W[ i ][ 0 ].length; j++ ){

			this.X[ i ][ j ] = 0;
			this.x[ i ][ j ] = 0;
			this.delta[ i ][ j ] = 0;

		}

	}
	//出力層
	this.X[ this.W.length ] = [];
	this.x[ this.W.length ] = [];
	this.delta[ this.W.length ] = [];
	for( var j = 0; j < this.W[ this.W.length-1 ].length; j++ ){
		this.X[ this.W.length ][ j ] = 0;
		this.x[ this.W.length ][ j ] = 0;
		this.delta[ this.W.length ][ j ] = 0;
	}

	////////////////////////////////////////////
	// 重み格納用多重配列と偏微分値格納多重配列の初期化
	////////////////////////////////////////////
	for( var i = 0; i < this.W.length; i++ ){

		this._W[ i ] =  [];
		this.dEdW[ i ] = [];
		this._dEdW[ i ] = [];

		for( var j = 0; j < this.W[ i ].length; j++ ){

			this._W[ i ][ j ] = [];
			this.dEdW[ i ][ j ] = [];
			this._dEdW[ i ][ j ] = [];

			for( var k = 0; k < this.W[ i ][ j ].length; k++ ){

				this._W[ i ][ j ][ k ] = this.W[ i ][ j ][ k ] ;
				this.dEdW[ i ][ j ][ k ] = 0;
				this._dEdW[ i ][ j ][ k ] = 0;

			}
		}
	}

	////////////////////////////////////////////
	// バイアス格納用多重配列
	////////////////////////////////////////////
	for( var i = 0; i < this.B.length; i++ ){

		this._B[ i ] = [];
		this.dEdB[ i ] = [];
		this._dEdB[ i ] = [];

		for( var j = 0; j < this.B[ i ].length; j++ ){

			this._B[ i ][ j ] = this.B[ i ][ j ];
			this.dEdB[ i ][ j ] = 0;
			this._dEdB[ i ][ j ] = 0;

		}
	}
}
//重みによる勾配
FNNN.prototype.resetDEdW = function(  ){
	for( var l = 0; l < this.W.length; l++ ){
		for( var i = 0; i < this.W[ l ].length; i++ ){
			for( var j = 0; j < this.W[ l ][ i ].length; j++ ){
				this.dEdW[ l ][ i ][ j ] = 0;
				this._dEdW[ l ][ i ][ j ] = 0;

			}
		}
	}
}
FNNN.prototype.add_DEdW = function( factor ){
	factor = factor || 1;
	for( var l = 0; l < this.W.length; l++ ){
		for( var i = 0; i < this.W[ l ].length; i++ ){
			for( var j = 0; j < this.W[ l ][ i ].length; j++ ){
				this._dEdW[ l ][ i ][ j ] += this.dEdW[ l ][ i ][ j ] * factor;
			}
		}
	}
}
FNNN.prototype.restoreDEdW = function( ){
	for( var l = 0; l < this.W.length; l++ ){
		for( var i = 0; i < this.W[ l ].length; i++ ){
			for( var j = 0; j < this.W[ l ][ i ].length; j++ ){
				this.dEdW[ l ][ i ][ j ] = this._dEdW[ l ][ i ][ j ];
			}
		}
	}
}


FNNN.prototype.resetDEdB = function(  ){
	for( var l = 0; l < this.B.length; l++ ){
		for( var i = 0; i < this.B[ l ].length; i++ ){
			this.dEdB[ l ][ i ] = 0;
			this._dEdB[ l ][ i ] = 0;
		}
	}
}
FNNN.prototype.add_DEdB = function( factor ){
	factor = factor || 1;
	for( var l = 0; l < this.B.length; l++ ){
		for( var i = 0; i < this.B[ l ].length; i++ ){
			this._dEdB[ l ][ i ] += this.dEdB[ l ][ i ] * factor;
		}
	}
}
FNNN.prototype.restoreDEdB = function( ){
	for( var l = 0; l < this.B.length; l++ ){
		for( var i = 0; i < this.B[ l ].length; i++ ){
			this.dEdB[ l ][ i ] = this._dEdB[ l ][ i ];
		}
	}
}
//重み多重配列を一時保持用多重配列へ格納
FNNN.prototype.storeW = function( ){

	for( var l = 0; l < this.W.length; l++ ){
		for( var i = 0; i < this.W[ l ].length; i++ ){
			for( var j = 0; j < this.W[ l ][ i ].length; j++ ){

				this._W[ l ][ i ][ j ] = this.W[ l ][ i ][ j ] ;

			}
		}
	}
}
//バイアス多重配列を一時保持用多重配列へ格納
FNNN.prototype.storeB = function( ){

	for( var l = 0; l < this.B.length; l++ ){
		for( var i = 0; i < this.B[ l ].length; i++ ){

			this._B[ l ][ i ] = this.B[ l ][ i ];

		}
	}

}
//重み多重配列を一時保持用多重配列へ格納
FNNN.prototype.updateW = function( ){

	for( var i = 0; i < this.W.length; i++ ){
		for( var j = 0; j < this.W[ i ].length; j++ ){
			for( var k = 0; k < this.W[ i ][ j ].length; k++ ){

				this.W[ i ][ j ][ k ] -=  this.eta * this.dEdW[ i ][ j ][ k ] ;

			}
		}
	}
}
//バイアス多重配列を一時保持用多重配列へ格納
FNNN.prototype.updateB = function(  ){

	for( var i = 0; i < this.B.length; i++ ){
		for( var j = 0; j < this.B[ i ].length; j++ ){

			this.B[ i ][ j ] -=  this.eta * this.dEdB[ i ][ j ];

		}
	}

}

//入力層(0層目ニューロン値)へのインプット
FNNN.prototype.setInput = function( Input ){

	for( var i = 0; i < Input.length; i++ ){

		this.x[ 0 ][ i ] = Input[ i ];

	}

	this.adoptAFh( this.x[ 0 ], this.X[ 0 ] );

}
//出力層へのアウトプット
FNNN.prototype.getOutput = function( ){

	//各層ニューロン値の計算
	for( var i = 0; i < this.W.length; i++ ){

		this.multiplayMatrixVector ( this.W[ i ], this.X[ i ], this.X[ i+1 ] );
		this.addVectors ( this.X[ i+1 ], this.B[ i ], this.x[ i+1 ] );

		//活性化関数の実行
		if( i < this.W.length-1 ){
			//隠れ層
			this.adoptAFh( this.x[ i+1 ], this.X[ i+1 ] );
		} else {
			//出力層
			this.adoptAFsigma( this.x[ i+1 ], this.X[ i+1 ] );
		}
	}

	return this.X[ this.X.length -1 ];
}

//誤差逆伝搬法のデルタ値へのインプット
FNNN.prototype.setDelta = function( Input ){

	for( var i = 0; i < Input.length; i++ ){

		this.delta[ this.delta.length - 1 ][ i ] = Input[ i ];

	}

}
//誤差逆伝搬法のデルタ値と勾配の計算
FNNN.prototype.computeBackPropagation = function( ){

	//層番号
	for( var l = this.delta.length - 2; l > 0; l-- ){

		for( var i = 0; i < this.delta[ l ].length; i++ ){

			this.delta[ l ][ i ] = 0;

			for( var j = 0; j < this.delta[ l + 1 ].length; j++ ){

				if( l + 2 == this.delta.length){
					//最終層
					this.delta[ l ][ i ] += this.delta[ l + 1 ][ j ] * this.W[ l ][ j ][ i ];

				} else{
					this.delta[ l ][ i ] += this.delta[ l + 1 ][ j ] * this.hd( this.x[ l + 1 ][ j ] ) * this.W[ l ][ j ][ i ];
				}

			}
		}

	}

	for( var l = 0; l < this.W.length; l++ ){
		for( var i = 0; i < this.W[ l ].length; i++ ){
			for( var j = 0; j < this.W[ l ][ i ].length; j++ ){

				if( l == this.W.length - 1){
					//最終層
					this.dEdW[ l ][ i ][ j ] = this.delta[ l + 1 ][ i ] * this.X[ l ][ j ];
				} else{
					this.dEdW[ l ][ i ][ j ] = this.delta[ l + 1 ][ i ] * this.X[ l ][ j ] * this.hd( this.x[ l + 1 ][ i ] );
				}

			}
		}
	}

	for( var l = 0; l < this.B.length; l++ ){
		for( var i = 0; i < this.B[ l ].length; i++ ){

			if( l == this.B.length - 1){
				//最終層
				this.dEdB[ l ][ i ] = this.delta[ l + 1 ][ i ];
			} else {
				this.dEdB[ l ][ i ] = this.delta[ l + 1 ][ i ] * this.hd( this.x[ l + 1 ][ i ] );
			}
		}
	}
}

//行列×ベクトルの計算
FNNN.prototype.multiplayMatrixVector = function( M, V, C ){
	C = C || [];

	var Mgyou = M.length;
	var Mretu = M[ 0 ].length;

	for( var i = 0; i < Mgyou; i++ ){

		C[ i ] =0;

		for( var j = 0; j < Mretu; j++ ){

			C[ i ] += M[ i ][ j ] * V[ j ];

		}

	}

	return C;
}
//ベクトルの和
FNNN.prototype.addVectors = function( V1, V2, V3 ){
	V3 = V3 || [];

	for( var i = 0; i < V1.length; i++ ){

		V3[ i ] = V1[ i ] + V2[ i ];

	}

	return V3;
}
//活性化関数の実行
FNNN.prototype.adoptAFh = function( V_in, V_out ){
	V_out = V_out || [];

	for( var i = 0; i < V_in.length; i++ ){

		V_out[ i ] = this.h( V_in[ i ] );

	}

	return V_out;
}
FNNN.prototype.adoptAFsigma = function( V_in, V_out ){
	V_out = V_out || [];

	for( var i = 0; i < V_in.length; i++ ){

		V_out[ i ] = this.sigma( V_in[ i ] );

	}

	return V_out;
}

実行方法

FNNNクラスを用いて、入力数1,出力数1の順伝播型ニューラルネットワークで三角関数を表現するプログラムを示します。 重みとバイアスに与える初期多重配列の構造がニューラルネットワークの層数と各層のニューロン数に対応します。

//////////////////////////////////
//ニューラルネットワークの生成
//////////////////////////////////
//学習対象関数
function f( x ){
	return Math.sin(2*Math.PI*x);
}

//重みとバイアスの初期値
var W = [];
var B = [];
for( var l = 0; l < N.length-1; l++ ){
	W[ l ] = [];
	for( var j = 0; j < N[l+1]; j++){
		W[ l ][ j ] = [];
		for( var i = 0; i < N[l]; i++){
			var w = (Math.random() - 0.5);
			W[ l ][ j ][ i ] = w; //W^{(1)}_0i
		}
	}
	B[ l ] = [];
	for( var i = 0; i < N[l+1]; i++){
		var b = (Math.random() - 0.5);
		B[ l ][ i ] = b;      //b^{(0)}_i
	}
}

//////////////////////////////////
//ニューラルネットワークの生成
//////////////////////////////////
var nn = new FNNN( W, B, eta);

//グラフ用データ
var data1 = [];
//T回の学習
for( var t=0; t<T; t++){
	//勾配データの初期化
	nn.resetDEdW();
	nn.resetDEdB();
	//ミニバッチによる学習
	for( var xi = 0; xi<=M; xi++ ){
		//入力値
		var x = x_min + (x_max - x_min) * xi/M;
		var X0 = [ x ];

		//入力層へのインプット
		nn.setInput( X0 );
		//出力層へのアウトプット
		var X2 = nn.getOutput();
		var y = X2[ 0 ];

		//逆誤差伝搬の初期値
		nn.setDelta( [ y - f( x ) ] );

		//逆誤差伝搬の計算
		nn.computeBackPropagation();
		//ミニバッチ平均勾配の計算(加算平均)
		nn.add_DEdB( 1/(M+1) );
		nn.add_DEdW( 1/(M+1) );
	}

	//勾配データの更新
	nn.restoreDEdB();
	nn.restoreDEdW();

	//重みとバイアスデータの更新
	nn.updateW();
	nn.updateB();

	//学習結果のチェック
	var sumL = 0;
	for( var xi = 0; xi<M; xi++ ){
		//入力値
		var x = x_min + (x_max - x_min) * xi/M;
		var X0 = [ x ];

		nn.setInput( X0 );
		var X2 = nn.getOutput( );
		var y = X2[0];
		sumL += 1.0/2.0*( y-f( x ) )*( y-f( x ) );

	}
	data1.push([ t, sumL ]);

}

実行結果



▲このページのトップNPO法人 natural science トップ

関連記事

ゼロから作るDeep Learning







▲このページのトップNPO法人 natural science トップ