最小二乗法による分析結果のグラフ表示コード

 今回は前回までのメソッドを使って分析した結果をグラフに表示する部分のコードを 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()

 引用元のサンプルスクリプトの全体は下記で公開されています。

github.com

iruby環境の用意

 書籍では python コードの実行環境として ipython を使用していますが、その ruby 版にあたる iruby を使用します。

github.com

 書籍の例ではGUI上のターミナルから ipython を起動し、pythonのコードを実行すると別ウィンドウでグラフが表示されるのですが、 iruby では同様なことの実現方法がわからなかったので、 jupyter notebook 上で iruby を使用します。

jupyter.org

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 を使います。

github.com

 下記を追記して、 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 上で実行すると下記のようなグラフが描画されます。

f:id:akanuma-hiroaki:20161205085255p:plain

f:id:akanuma-hiroaki:20161205085315p:plain

 Jupyter Notebook 上での実行結果は下記に公開しました。

http://nbviewer.jupyter.org/github/h-akanuma/ml4se_study/blob/master/02-square_error.ipynb

 また、実装の全体のコードやVagrantfile等も含めてgithubに公開してあります。

github.com