ゼロから作るDeep Learning
順伝播型ニューラルネットワーク「FFNNクラス」の実装(JavaScript)
昨今注目を集めているAI(人工知能)を学びたいと思い立ち、ディープラーニング(Deep Learning、深層学習)と呼ばれるAIの数理モデルである多層構造のニューラルネットワークを書籍「ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装」を参考にを独習していきたいと思います。本書籍ではプログラミング言語としてPythonが利用されていますが、本項ではJavaScriptで実装していきます。
目次
- 準備1:行列の和と積を計算する関数の実装
- 準備2:ベクトルと行列の積を計算する関数の実装
- 準備3:多変数関数の数値微分と極小値の探索
- 1.1層ニューラルネットワークの実装(バイアスなし、活性化関数なし、学習なし)
- 2.1層ニューラルネットワークへのバイアスと活性化関数の追加
- 3.1n1型2層ニューラルネットワークの実装(学習なし)
- 4.1変数関数を学習させてみる1:勾配法による学習計算アルゴリズム
- 5.1変数関数を学習させてみる2:勾配法による学習計算アルゴリズムの実装
- 6.1変数関数を学習させてみる3:ニューロン数による学習効果の違い
- 7.誤差逆伝搬法(バックプロパゲーション)の導出
- 8.順伝播型ニューラルネットワーク「FFNNクラス」の実装(JavaScript)
- 9.三角関数のサンプリング学習(WebWorkersによる並列計算)
- 10.学習後の各層ニューロンの重みの可視化
- 11.層数とニューロン数による学習効果の違い
順伝播型ニューラルネットワーク「FFNNクラス」の実装
前項の誤差逆伝搬法(バックプロパゲーション)を組み込んだ順伝播型ニューラルネットワークを実現するクラスを示します。
FNNNクラス
第一引数:初期重み多重配列(必須)
第二引数:初期バイアス多重配列(必須)
第三引数:学習率
第四引数:活性化関数
第五引数:活性化関数の導関数
第六引数:最終層の活性化関数
第七引数:最終層の活性化関数の導関数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 | //順伝播型ニューラルネットワーク 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の順伝播型ニューラルネットワークで三角関数を表現するプログラムを示します。 重みとバイアスに与える初期多重配列の構造がニューラルネットワークの層数と各層のニューロン数に対応します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | ////////////////////////////////// //ニューラルネットワークの生成 ////////////////////////////////// //学習対象関数 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 ]); } |
実行結果