今回は前回までのメソッドを使って分析した結果をグラフに表示する部分のコードを ruby で実装します。書籍のコードの下記の部分になります。
# Main if __name__ == '__main__': train_set = create_dataset(N) test_set = create_dataset(N) df_ws = DataFrame() # 多項式近似の曲線を求めて表示 fig = plt.figure() for c, m in enumerate(M): f, ws = resolve(train_set, m) df_ws = df_ws.append(Series(ws,name="M=%d" % m)) subplot = fig.add_subplot(2,2,c+1) subplot.set_xlim(-0.05,1.05) subplot.set_ylim(-1.5,1.5) subplot.set_title("M=%d" % m) # トレーニングセットを表示 subplot.scatter(train_set.x, train_set.y, marker='o', color='blue') # 真の曲線を表示 linex = np.linspace(0,1,101) liney = np.sin(2*np.pi*linex) subplot.plot(linex, liney, color='green', linestyle='--') # 多項式近似の曲線を表示 linex = np.linspace(0,1,101) liney = f(linex) label = "E(RMS)=%.2f" % rms_error(train_set, f) subplot.plot(linex, liney, color='red', label=label) subplot.legend(loc=1) # 係数の値を表示 print "Table of the coefficients" print df_ws.transpose() fig.show() # トレーニングセットとテストセットでの誤差の変化を表示 df = DataFrame(columns=['Training set','Test set']) for m in range(0,10): # 多項式の次数 f, ws = resolve(train_set, m) train_error = rms_error(train_set, f) test_error = rms_error(test_set, f) df = df.append( Series([train_error, test_error], index=['Training set','Test set']), ignore_index=True) df.plot(title='RMS Error', style=['-','--'], grid=True, ylim=(0,0.9)) plt.show()
引用元のサンプルスクリプトの全体は下記で公開されています。
iruby環境の用意
書籍では python コードの実行環境として ipython を使用していますが、その ruby 版にあたる iruby を使用します。
書籍の例ではGUI上のターミナルから ipython を起動し、pythonのコードを実行すると別ウィンドウでグラフが表示されるのですが、 iruby では同様なことの実現方法がわからなかったので、 jupyter notebook 上で iruby を使用します。
Vagrantfile
Anaconda でインストールされる ipython のバージョンは4だったのですが、iruby が対応してるのは ipython3 のようだったので、 Vagrantfile に下記を追加し、ipython のバージョンを下げます。
config.vm.provision :shell, path: "provisionings/condas.sh"
provisionings/condas.sh
#!/bin/bash conda install ipython=3.2.3 -y
provisionings/libs.sh
jupyter notebook を起動してVM上のブラウザからアクセスするために、Chrome をインストールします。下記を追加して Chrome のリポジトリを追加し、 yum でインストールします。
cat <<'EOF' > /etc/yum.repos.d/google-chrome.repo [google-chrome] name=google-chrome baseurl=http://dl.google.com/linux/chrome/rpm/stable/$basearch enabled=1 gpgcheck=1 gpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub EOF yum install -y google-chrome-stable
provisionings/gems.sh
python 版ではグラフのプロットのために matplotlib を使用していますが、 ruby 版では iruby 上で使用できる nyaplot を使います。
下記を追記して、 nyaplot をインストールします。
gem install nyaplot
Nyaplot でのグラフ描画
次数が 0, 1, 3, 9 の4種類の多項式のグラフを扱うので、まず複数の図を一まとめで扱うための Nyaplot::Frame のインスタンスを作成します。
fig = Nyaplot::Frame.new
それぞれの図を作成するには、まず Nyaplot::Plot のインスタンスを生成し、そこに Nyaplot::DataFrame のオブジェクトを渡すことで作成できます。
Nyaplot::Plot#add_with_df
http://www.rubydoc.info/github/domitry/nyaplot/master/Nyaplot%2FPlot%3Aadd_with_df
戻り値のオブジェクトに対して凡例のタイトルやグラフの色の指定をすることができます。
plot = Nyaplot::Plot.new sc = plot.add_with_df(train_set.to_nyaplotdf, :scatter, :x, :y) sc.title("train_set") sc.color('blue')
Nyaplot::Plot のインスタンスに配列データを渡すことでもグラフを作成できます。
Nyaplot::Plot#add
http://www.rubydoc.info/github/domitry/nyaplot/master/Nyaplot%2FPlot%3Aadd
また、python版では numpy.linspace を使って、あるレンジ内で指定した数の数値を等間隔で取得していますが、 ruby では同様のメソッドが見つからなかったので、 step メソッドで代用しました。
linex = (0..1).step(1.0 / (101 - 1)).to_a liney = linex.map do |x| Math.sin(2 * Math::PI * x) end line_answer = plot.add(:line, linex, liney) line_answer.title('answer') line_answer.color('green')
図自体の大きさやメモリの範囲を指定するには、 plot.configure にブロックを渡して各値を設定します。
plot.configure do x_label("M=#{m}") y_label('') xrange([-0.05, 1.05]) yrange([-1.5, 1.5]) legend(true) height(300) width(490) end
最後に Nyaplot::Frame インスタンスに図のオブジェクトを追加します。
fig.add(plot)
Daru::DataFrame の内容をテキストで見やすい形に整形して表示するには、 Daru::DataFrame#inspect を使います。
Daru::DataFrame#inspect
http://www.rubydoc.info/gems/daru/0.1.0/Daru%2FDataFrame%3Ainspect
puts 'Table ot the coefficients' puts Daru::DataFrame.new(df_ws).inspect
出力は下記のようになります。
Table ot the coefficients #<Daru::DataFrame(10x4)> M=0 M=1 M=3 M=9 0 0.05554492 0.80497528 -0.0362814 0.08278865 1 nil -1.4988607 11.7919126 -24.851479 2 nil nil -34.392490 657.588025 3 nil nil 22.6689535 -5930.9070 4 nil nil nil 29069.5535 5 nil nil nil -84750.916 6 nil nil nil 148809.822 7 nil nil nil -153632.58 8 nil nil nil 85727.3726 9 nil nil nil -19925.297
Nyaplot::Frame#show を実行することで図が描画されます。
fig.show
また、Daru は Nyaplot でのプロットに対応しているので、一つの Daru::DataFrame オブジェクトの内容をプロットするだけであれば、 Daru::DataFrame#plot を使うことで描画可能です。
Daru::DataFrame#plot
http://www.rubydoc.info/gems/daru/0.1.0/Daru%2FPlotting%2FDataFrame%3Aplot
df.plot(type: [:line, :line], x: [:m, :m], y: ['Training set'.to_sym, 'Test set'.to_sym]) do |plot, diagrams| train_set_diagram = diagrams[0] test_set_diagram = diagrams[1] train_set_diagram.title('Training set') train_set_diagram.color('blue') test_set_diagram.title('Test set') test_set_diagram.color('green') plot.x_label('M') plot.y_label('RMS Error') plot.yrange([0, 0.9]) plot.legend(true) end
ここまでの内容を踏まえたコードの全体を下記のように実装しました。
train_set = create_dataset(N) test_set = create_dataset(N) df_ws = {} fig = Nyaplot::Frame.new M.each_with_index do |m, c| f, ws = resolve(train_set, m) df_ws["M=#{m}"] = Daru::Vector.new(ws, name: "M=#{m}") plot = Nyaplot::Plot.new sc = plot.add_with_df(train_set.to_nyaplotdf, :scatter, :x, :y) sc.title("train_set") sc.color('blue') linex = (0..1).step(1.0 / (101 - 1)).to_a liney = linex.map do |x| Math.sin(2 * Math::PI * x) end line_answer = plot.add(:line, linex, liney) line_answer.title('answer') line_answer.color('green') liney = linex.map do |x| f.call(x) end line_erms = plot.add(:line, linex, liney) line_erms.title("E(RMS#{sprintf("%.2f", rms_error(train_set, f))})") line_erms.color('red') plot.configure do x_label("M=#{m}") y_label('') xrange([-0.05, 1.05]) yrange([-1.5, 1.5]) legend(true) height(300) width(490) end fig.add(plot) end puts 'Table ot the coefficients' puts Daru::DataFrame.new(df_ws).inspect fig.show df = Daru::DataFrame.new({m: [], 'Training set': [], 'Test set': []}) 10.times do |m| f, ws = resolve(train_set, m) train_error = rms_error(train_set, f) test_error = rms_error(test_set, f) df.add_row(Daru::Vector.new([m, train_error, test_error], index: [:m, 'Training set'.to_sym, 'Test set'.to_sym])) end df.plot(type: [:line, :line], x: [:m, :m], y: ['Training set'.to_sym, 'Test set'.to_sym]) do |plot, diagrams| train_set_diagram = diagrams[0] test_set_diagram = diagrams[1] train_set_diagram.title('Training set') train_set_diagram.color('blue') test_set_diagram.title('Test set') test_set_diagram.color('green') plot.x_label('M') plot.y_label('RMS Error') plot.yrange([0, 0.9]) plot.legend(true) end
Jupyter Notebook 上で実行すると下記のようなグラフが描画されます。
Jupyter Notebook 上での実行結果は下記に公開しました。
http://nbviewer.jupyter.org/github/h-akanuma/ml4se_study/blob/master/02-square_error.ipynb
また、実装の全体のコードやVagrantfile等も含めてgithubに公開してあります。