バックプロパゲーションとは何なのか-Rubyでニューラルネットワークで学習やってみる#2
ということで前回の続き、バックプロパゲーションを用いたプログラムを実際に動かしてみます。
動作確認
今回はこちらのプログラムを動かしてみることにした。
https://github.com/gbuesing/neural-net-ruby
まず、iris data setのデータを読み込んで学習するサンプルを動かしてみる。
ここで使うiris data setであるiris.data
は以下のようなフォーマットになっていて、右からSepal length(がくの長さ), Sepal width(がくの幅), Petal length(弁の長さ), Petal width(弁の幅), Species(種)となっている。サンプル数は150となっている。
5.1,3.5,1.4,0.2,Iris-setosa 4.9,3.0,1.4,0.2,Iris-setosa 4.7,3.2,1.3,0.2,Iris-setosa . . . 5.7,2.8,4.5,1.3,Iris-versicolor 6.3,3.3,4.7,1.6,Iris-versicolor 4.9,2.4,3.3,1.0,Iris-versicolor . . . 6.5,3.0,5.5,1.8,Iris-virginica 7.7,3.8,6.7,2.2,Iris-virginica 7.7,2.6,6.9,2.3,Iris-virginica
このプログラムは、がくと弁の大きさによって、アヤメの3種のうちどのクラスかということを出力として期待する。
This neural network will predict the species of an iris based on sepal and petal size
プログラムの内容
まず、プログラムの処理内容を確認していきたい。
pryでiris.rb
を逐次処理しながらプログラム内容を確認した。
18行目x_data
では各サンプルの4つのパラメータが入っている。
x_data = rows.map {|row| row[0,4].map(&:to_f) }
次のy_data
では、3つの要素を持つ配列のうち1を立てることでどの種か判別するように格納している
y_data = rows.map {|row| label_encodings[row[4]] }
またここではiris data setのうち0個目から100個のデータを訓練データにし、101個目から50個をテストデータとして用いている。
x_train = x_data.slice(0, 100) y_train = y_data.slice(0, 100) x_test = x_data.slice(100, 50) y_test = y_data.slice(100, 50)
そしてNeuralNetクラスのインスタンスを生成。引数により入力層、中間層、出力層の数を指定している。
# Build a 3 layer network: 4 input neurons, 4 hidden neurons, 3 output neurons # Bias neurons are automatically added to input + hidden layers; no need to specify these nn = NeuralNet.new [4,4,3]
このプログラムでは2回識別を試みていて、最初に学習なしのテストを行い、次に学習を行った後、もう一度識別を試みている。
まずは学習なしのテストの部分で、以下のように実行されている。
run_test = -> (nn, inputs, expected_outputs) { success, failure, errsum = 0,0,0 inputs.each.with_index do |input, i| output = nn.run input prediction_success.(output, expected_outputs[i]) ? success += 1 : failure += 1 errsum += mse.(output, expected_outputs[i]) end [success, failure, errsum / inputs.length.to_f] } puts "Testing the untrained network..." success, failure, avg_mse = run_test.(nn, x_test, y_test)
->
はRuby1.9からの新しいlambda記法である。
->(a,b){ p [a,b] } Ruby1.9 で導入された lambda の新しい記法。以下と同じ。
lambda{|a, b| p [a, b] } http://docs.ruby-lang.org/ja/2.0.0/doc/symref.html
ここでNeuralNet#runを呼び、識別処理を行ったものをoutput
に格納される。
NeuralNet本体のソースコードは割愛する。以下を参照していただきたい。
https://github.com/totzYuta/neural-net-ruby/blob/master/neural_net.rb
成功判定はprediction_success
が以下のようにあらかじめ作成しておいたy_test
のクラス分けの配列を用いて行う。
prediction_success = -> (actual, ideal) { predicted = (0..1).max_by {|i| actual[i] } ideal[predicted] == 1 }
次に、学習・学習後の識別の部分は以下のようなプログラムとなっている。
puts "\nTraining the network...\n\n" t1 = Time.now result = nn.train(x_train, y_train, error_threshold: 0.01, max_iterations: 1_000, log_every: 100 ) # puts result puts "\nDone training the network: #{result[:iterations]} iterations, #{(result[:error] * 100).round(2)}% mse, #{(Time.now - t1).round(1)}s" puts "\nTesting the trained network..." success, failure, avg_mse = run_test.(nn, x_test, y_test) puts "Trained classification success: #{success}, failure: #{failure} (classification error: #{error_rate.(failure, x_test.length)}%, mse: #{(avg_mse * 100).round(2)}%)"
動作確認
出力結果は以下のようになった。
$ ruby examples/iris.rb Testing the untrained network... Untrained classification success: 16, failure: 34 (classification error: 68%, mse: 28.17%) Training the network... [100] 1.72% mse [200] 1.04% mse [300] 1.03% mse [400] 1.03% mse [500] 1.03% mse [600] 1.03% mse [700] 1.03% mse [800] 1.03% mse [900] 1.03% mse [1000] 1.03% mse Done training the network: 1000 iterations, 1.03% mse, 5.3s Testing the trained network... Trained classification success: 48, failure: 2 (classification error: 4%, mse: 1.57%)
学習していない状態では32%の精度だったのに対し、学習後は96%で識別できていることが確認できる。
4x4の学習データを学習させる
次に、用意した4つのサンプルデータを学習させてみる。
学習データは以下の4種類。
number_image.data
というデータファイルを以下のように定義した。一列がひとつのデータにあたり、カンマで区切られた最初の16個の数字が実際の学習データで、17個目の数字はその学習データが文字'1'を表しているのか文字'0'を表しているのかを表している。
5行目はテスト用の未知データとなっている。
0,0,1,0,0,1,0,0,0,1,0,0,0,1,0,0,1 0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,1 0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,0 1,1,1,1,1,0,0,1,1,0,0,1,1,1,1,1,0 0,0,1,0,0,1,0,1,0,1,0,1,0,1,1,0,0
新たにexamples/number_image.rb
というファイルを作成した。プログラムは以下のようになった。
#!/usr/bin/env ruby require_relative '../neural_net' # This neural network will predict the character '0' or '1' rows = File.readlines("examples/number_image.data").map {|l| l.chomp.split(',') } class_flags = { 0 => [1, 0], 1 => [0, 1] } x_data = rows.map {|row| row[0,16].map(&:to_i) } y_data = rows.map {|row| class_flags[row[17].to_i] } # Training Data x_train = x_data.slice(0, 4) y_train = y_data.slice(0, 4) # Testing Data x_test = x_data.slice(4, 1) y_test = y_data.slice(4, 1) # Build a 3 layer network: 16 input neurons, 8 hidden neurons, 2 output neurons # Bias neurons are automatically added to input + hidden layers; no need to specify these nn = NeuralNet.new [16,8,2] prediction_success = -> (actual, ideal) { predicted = (0..1).max_by {|i| actual[i] } ideal[predicted] == 1 } mse = -> (actual, ideal) { errors = actual.zip(ideal).map {|a, i| a - i } (errors.inject(0) {|sum, err| sum += err**2}) / errors.length.to_f } error_rate = -> (errors, total) { ((errors / total.to_f) * 100).round } run_test = -> (nn, inputs, expected_outputs) { success, failure, errsum = 0,0,0 inputs.each.with_index do |input, i| output = nn.run input prediction_success.(output, expected_outputs[i]) ? success += 1 : failure += 1 errsum += mse.(output, expected_outputs[i]) end [success, failure, errsum / inputs.length.to_f] } puts "\nTraining the network...\n\n" t1 = Time.now result = nn.train(x_train, y_train, error_threshold: 0.01, max_iterations: 100, log_every: 10 ) # puts result puts "\nDone training the network: #{result[:iterations]} iterations, #{(result[:error] * 100).round(2)}% mse, #{(Time.now - t1).round(1)}s"
これを実行したところ、以下のように出力され、学習が適切に行われていることを確認できた。
$ ruby examples/number_image2.rb Training the network... Done training the network: 4 iterations, 0.4% mse, 0.0s
未知のデータの分類
次に、number_image.rb
に以下を加え、学習させたネットワークでテストデータの認識を行わせてみる。
puts "\nTesting the trained network..." success, failure, avg_mse = run_test.(nn, x_test, y_test) puts "Trained classification success: #{success}, failure: #{failure} (classification error: #{error_rate.(failure, x_test.length)}%, mse: #{(avg_mse * 100).round(2)}%)"
出力結果は以下のようになった。
$ ruby examples/number_image.rb Training the network... Done training the network: 5 iterations, 0.5% mse, 0.0s Testing the trained network... Trained classification success: 1, failure: 0 (classification error: 0%, mse: 0.03%)
もともとのテストデータでは未知データの期待値を0として入力していたので、この未知データx1は0と識別されたことになる。
まとめ
バックプロパゲーションで学習させることが高い認識精度につながることがわかった。
次はもっと大きな学習データで中間層の数を工夫させながら学習して識別させたり、NN法などとの精度の違いなどについても検討したい。
参考資料
[1] 石井健一郎、上田修功、前田英作、村瀬洋 (1998) 『パターン認識』オーム社
[2] 小高知宏 (2011) 『はじめての機械学習』オーム社
参考記事
- gbuesing/neural-net-ruby
https://github.com/gbuesing/neural-net-ruby
- バックプロパゲーション(誤差逆伝播法)の実行時間をRubyとPythonで比較してみました。
http://blog.yusugomori.com/post/21858253979/ruby-python
- ニューラルネットワークで数字を認識するWebアプリを作る(python)
http://qiita.com/ginrou@github/items/07b52a8520efcaebce37