Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Rubyで学ぶニューラルネットワーク超入門――思っているほど難しくない!

この記事では、ニューラルネットワークの基礎と、Rubyを使った実装方法を学びます。人工知能やディープラーニングに興味はあるものの、何から始めればいいのか分からない方にぴったりの内容です。シンプルな例題を通じて、重要な概念をわかりやすく解説していきます。実際にRubyで多層ニューラルネットワークを書くことはほとんどありませんが、コードがシンプルで読みやすいため、内部で何が起きているのかを理解するには最適なアプローチです。まずは少し立ち止まって、私たちがここに至るまでの道のりを見てみましょう。

Rubyで学ぶニューラルネットワーク超入門――思っているほど難しくない!
映画『エクス・マキナ』のワンシーン

『エクス・マキナ(Ex Machina)』は2014年に公開された映画です。Googleでタイトルを検索すると、ジャンルは「ドラマ/ファンタジー」と分類されています。私が初めてこの映画を観たとき、確かにSFのように感じられました。

でも、AIが人間と見分けがつかなくなるのは、もっとずっと先の話ではないでしょうか?

Googleに勤める著名な未来学者、レイ・カーツワイル氏に聞けば、答えは違います。同氏によれば、2029年には人工知能が有効なチューリングテスト(人間が相手が機械なのか別の人間なのかを判別できるかを試す実験)に合格する年になるかもしれません。さらに、シンギュラリティ(コンピュータが人間の知能を超える瞬間)は2045年までに訪れると予測しています。

カーツワイル氏がこれほど自信を持つ理由は何なのでしょうか?

ディープラーニングの台頭

簡単に言えば、ディープラーニングとは、大量のデータから洞察を抽出するためにニューラルネットワークを活用する機械学習の一分野です。ディープラーニングの具体的な応用例には、次のようなものがあります。

  • 自動運転車
  • がん検出
  • SiriやAlexaなどのバーチャルアシスタント
  • 地震などの大規模な気象現象の予測

そもそも「ニューラルネットワーク」とは?

ニューラルネットワークの名前は、電気的・化学的な信号によって情報を処理・伝達する脳細胞「ニューロン(神経細胞)」に由来しています。豆知識ですが、人間の脳は800億個以上のニューロンで構成されているのです!

コンピューティングの世界では、ニューラルネットワークは次のような構造で表されます。

Rubyで学ぶニューラルネットワーク超入門――思っているほど難しくない!
ニューラルネットワークの構造図の例

ご覧のとおり、3つの部分から構成されています。

  1. 入力層 ―― 最初に入力されるデータ
  2. 隠れ層 ―― ニューラルネットワークには1つ以上の隠れ層を持たせられます。すべての計算はここで行われます!
  3. 出力層 ―― 最終的な結果・予測

簡単な歴史のおさらい

ニューラルネットワークは決して新しい技術ではありません。実は、初めて訓練可能なニューラルネットワーク(パーセプトロン)は、1950年代にコーネル大学で開発されました。しかし、当初のモデルは隠れ層が1層しかなかったこともあり、ニューラルネットワークの実用性については悲観的な見方が大勢を占めていました。1969年に出版された書籍では、パーセプトロンを使って比較的単純な計算を行うことさえ非現実的であることが示されたのです。

ニューラルネットワークが復活した要因の一つは、コンピュータゲームです。現代のゲームは非常に高性能なGPU(Graphics Processing Unit)を必要としますが、そのアーキテクチャはニューラルネットの構造とよく似ています。違いは隠れ層の数です。1層ではなく、現在訓練されているニューラルネットワークは10層、15層、さらには50層以上を使用しています!

実践してみよう!

仕組みを理解するために、実際に例を動かしてみましょう。まずはruby-fann gemをインストールします。ターミナルを開いて作業ディレクトリに移動し、次のコマンドを実行してください。

gem install ruby-fann

続いて、新しいRubyファイルを作成します(ここではneural-net.rbという名前にしました)。

次に、Kaggleの「Student Alcohol Consumption」データセットを使用します。こちらからダウンロードできます。「student-mat.csv」ファイルをGoogleスプレッドシート(またはお好みのエディタ)で開き、次の列以外をすべて削除してください。

  • Dalc(平日の飲酒量。1が非常に少なく、5が非常に多い)
  • Walc(週末の飲酒量。1が非常に少なく、5が非常に多い)
  • G3(0〜20の最終成績)

ruby-fann gemが動作するように、最終成績の列をバイナリ(0または1)に変換する必要があります。この例では、10以下を「0」、10より大きい値を「1」とします。使用しているツールにもよりますが、セルに数式を入力すれば、セルの値に基づいて自動的に1か0へ変換できます。Googleスプレッドシートでは、たとえば次のようになります。

=IF(C3 >= 10, 1, 0)

このデータをCSVファイル(ここではstudents.csvという名前)として保存し、Rubyファイルと同じディレクトリに置いてください。

今回構築するニューラルネットワークは、次の層構成にします。

  • 入力層:2ノード(平日の飲酒量と週末の飲酒量)
  • 隠れ層:6つの隠れノード(最初はある程度任意の数でOK。テストしながら調整できます)
  • 出力層:1ノード(0または1)

まず、ruby-fann gemと標準ライブラリのcsvを読み込みます。Rubyプログラムの冒頭に次の行を追加してください。

require 'ruby-fann'
require 'csv'

次に、CSVファイルからデータを配列に読み込みます。

# 空の配列を2つ作成。1つは独立変数(x_data)、もう1つは従属変数(y_data)を格納する。
x_data = []
y_data = []

# CSVデータを走査して、対応する配列に要素を追加する。
# 注意:.to_f と .to_i を付けないと配列の中身が文字列になり、
# ruby-fannライブラリが正しく動作しない。
CSV.foreach("students.csv", headers: false) do |row|
  x_data.push([row[0].to_f, row[1].to_f])
  y_data.push(row[2].to_i)
end

続いて、データを訓練用とテスト用に分割します。80/20の分割が一般的で、データの20%をテストに、80%を訓練に使います。ここでの「訓練」とは、モデルがこのデータに基づいて学習することを意味し、その後「テスト」データを使って、モデルがどの程度正確に結果を予測できるかを確認します。

# データを訓練セットとテストセットに分割する。
testing_percentage = 20.0

# 全要素数にテスト割合を掛ける。
testing_size = x_data.size * (testing_percentage/100.to_f)

# 配列は0始まりなので、先頭から testing_size - 1 までをテストデータとする。
x_test_data = x_data[0 .. (testing_size-1)]
y_test_data = y_data[0 .. (testing_size-1)]

# 残りを訓練データとしてデータセットの末尾まで取得する。
x_train_data = x_data[testing_size .. x_data.size]
y_train_data = y_data[testing_size .. y_data.size]

これでデータの準備が整いました。さあ、次は魔法の時間です!

# 訓練データモデルをセットアップする。
train = RubyFann::TrainData.new(:inputs=> x_train_data, :desired_outputs=>y_train_data)

RubyFann::TrainDataオブジェクトを使い、平日と週末の飲酒量であるx_train_dataと、最終成績に基づく0か1のy_train_dataを渡します。

続いて、先ほど説明した隠れニューロンの数で、実際のニューラルネットワークモデルをセットアップします。

# モデルをセットアップし、訓練データで学習させる。
model = RubyFann::Standard.new(
              num_inputs: 2,
              hidden_neurons: [6],
              num_outputs: 1 );

さあ、いよいよ訓練です!

model.train_on_data(train, 1000, 10, 0.01)

ここでは、先ほど作成したtrain変数を渡しています。1000は最大エポック数、10はレポート間のエラー出力間隔、0.01は目標とする平均二乗誤差です。1エポックとは、データセット全体がニューラルネットワークを一度通過することを指します。平均二乗誤差は、私たちが最小化を目指す指標です。詳細については、関連ドキュメントを参照してください。

次に、モデルがテストデータに対して出力した予測と実際の結果を比較し、精度を確認しましょう。次のコードで実現できます。

predicted = []

# x_test_data を反復処理し、それぞれに対してモデルを実行して predicted 配列に追加する。
x_test_data.each do |params|
  predicted.push( model.run(params).map{ |e| e.round } )
end

# 予測結果と実際の結果を比較する。
correct = predicted.collect.with_index { |e,i| (e == y_test_data[i]) ? 1 : 0 }.inject{ |sum,e| sum+e }

# 正解率を出力する。
puts "Accuracy: #{((correct.to_f / testing_size) * 100).round(2)}% - test set of size #{testing_percentage}%"

プログラムを実行してみましょう。どうなるでしょうか?

ruby neural-net.rb

エポックに関する大量の出力が表示されますが、一番下には次のような結果が出ているはずです。

Accuracy: 56.82% - test set of size 20.0%

おっと、あまり良い数字ではありませんね!それでも、独自のデータポイントを与えてモデルを実行してみましょう。

prediction = model.run( [1, 1] )
# 出力を四捨五入して予測値を取得する。
puts "Algorithm predicted class: #{prediction.map{ |e| e.round }}"

prediction_two = model.run( [5, 4] )
# 出力を四捨五入して予測値を取得する。
puts "Algorithm predicted class: #{prediction_two.map{ |e| e.round }}"

ここには2つの例があります。1つ目は、平日と週末の飲酒量に「1」を渡したケースです。賭けをするなら、この学生の最終成績は10より上(つまり「1」)だと予想できます。2つ目の例は、飲酒量に高い値(5と4)を渡しているので、この学生の最終成績は10以下(つまり「0」)だと予想します。プログラムをもう一度実行して、結果を確認してみましょう!

出力は次のようになるはずです。

Algorithm predicted class: [1]
Algorithm predicted class: [0]

モデルは、数値がスペクトラムの下限側や上限側にある場合には期待どおりに機能しているようです。しかし、数値の組み合わせが逆の場合(1と5、2と3など、いろいろなパターンを試してみてください)や中間の値になると、うまく予測できません。また、エポックのログを見ると、誤差は減少しているものの依然として高水準(20%台中盤)にとどまっています。これは、飲酒量と成績の間に強い関係性がない可能性を示唆しています。ぜひKaggleの元データセットをいろいろ触ってみてください。コースの成果を予測するために使える他の説明変数はないでしょうか?

まとめ

ここで紹介した仕組みが動作する裏側では、(主に数学に関わる)多くの複雑な処理が行われています。さらに深く知りたい方は、FANNの公式ドキュメントを読んだり、ruby-fann gemのソースコードを覗いたりすることをお勧めします。また、Netflixのドキュメンタリー『AlphaGo ―世界最強VS人工知能―』もチェックしてみてください。専門知識がなくても十分楽しめるうえ、ディープラーニングがコンピュータの能力の限界をどこまで押し広げているかを示す、素晴らしい実例として観ることができます。

カーツワイル氏の予測は本当に的中するのでしょうか? それは、時間だけが教えてくれるでしょう。

  1. Ruby 2.6の新機能9選|コード例でわかる注目ポイントを徹底解説

    Ruby 2.6には、開発者の生産性を高める新しい機能やパフォーマンス改善が多数盛り込まれています。 本記事では、Ruby 2.6で導入された9つの注目新機能を、実際のコード例とともにわかりやすく紹介します。最新のRuby動向をキャッチアップしたい方は、ぜひ最後までご覧ください。 1. 無限Range(Endless Range) Ruby 2.5以前でもFloat::INFINITYを使えば終端のない範囲を表現できましたが、Ruby 2.6ではさらに直感的な記法が使えるようになりました。 新しい無限Rangeは次のように書きます。 (1..) 通常のRangeが(1..10)のように終端

  2. カプセルネットワークは従来のニューラルネットワークに取って代わるのか?

    ここ数年における人工知能(AI)の目覚ましい成功は、ディープニューラルネットワークの存在なしには語れません。画像認識からスマートサーモスタット、自動運転車まで、AIはスマートフォンをはじめとするあらゆる分野に浸透しています。それほどまでにニューラルネットワークは普遍的な技術となったのです。しかし、このシステムの根幹をなす原理の一部が、AIが直面する重大な問題を克服できないのではないかという懸念が高まっています。つまり、「従来型」のニューラルネットワークは市場から姿を消し、より先進的な「カプセル」ニューラルネットワークに取って代わられる可能性があるのです。 従来の(人工)ニューラルネットワークと