とりあえず前回で ruby と python のコードを動かす環境を作ったので、サンプルコードを ruby に書き換えていきます。まずは誤差関数(最小二乗法)による回帰分析のサンプルコード。書籍のコードは下記に公開されています。
この中で、まず今回はデータセットを作成するためのコードをrubyで書いてみます。上記サンプルコードの中の下記の部分です。
import numpy as np from pandas import Series, DataFrame from numpy.random import normal # データセット {x_n,y_n} (n=1...N) を用意 def create_dataset(num): dataset = DataFrame(columns=['x','y']) for i in range(num): x = float(i)/float(num-1) y = np.sin(2*np.pi*x) + normal(scale=0.3) dataset = dataset.append(Series([x,y], index=['x','y']), ignore_index=True) return dataset
ここでは下記のライブラリが使われています。
- NumPy:ベクトルや行列を扱う数値計算ライブラリ
- pandas:Rに類似のデータフレームを提供するライブラリ
こういったライブラリが充実しているのが、pythonが機械学習系に強い理由の一つですね。 これらのライブラリの中で、サンプルコードの中で使われている内容を、rubyで書くにはどうするのが良いか調べてみました。
pandas.DataFrame
http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.html#pandas.DataFrame
pythonのDataFrameクラスはスプレッドシートのようなデータ構造を持っており、サンプルの中では下記のように使われています。
>>> from pandas import DataFrame >>> >>> df = DataFrame(columns = ['x', 'y']) >>> df Empty DataFrame Columns: [x, y] Index: [] >>>
DataFrameのコンストラクタに渡している columns パラメータの意味は下記のように説明されています。データカラムのラベルとして使われます。
columns : Index or array-like Column labels to use for resulting frame. Will default to np.arange(n) if no column labels are provided
rubyで pandas にあたるライブラリとして、daruというgemがあるようなのでこれを利用し、Daru::DataFrameクラスを使用します。
Daru::DataFrame
http://www.rubydoc.info/gems/daru/0.1.4.1/Daru/DataFrame
irb(main):001:0> require 'daru' Install the reportbuilder gem version ~>1.4 for using reportbuilder functions. Install the spreadsheet gem version ~>1.1.1 for using spreadsheet functions. => true irb(main):002:0> df = Daru::DataFrame.new({'x': [], 'y': []}) => #<Daru::DataFrame(0x2)> x y irb(main):003:0>
pandas.Series
http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.html#pandas.Series
Seriesは一次元配列に似た構造を持っており、サンプルコードでは下記のような使われ方をしています。
>>> from pandas import Series >>> >>> x = 0.0 >>> y = 1.0 >>> s = Series([x, y], index = ['x', 'y']) >>> s x 0.0 y 1.0 dtype: float64 >>>
rubyではDataFrameと同じくdaruのVectorクラスを使用します。
Daru::Vector http://www.rubydoc.info/gems/daru/0.1.4.1/Daru/Vector
irb(main):005:0* x = 0.0 => 0.0 irb(main):006:0> y = 1.0 => 1.0 irb(main):007:0> v = Daru::Vector.new([x, y], index: [:x, :y]) => #<Daru::Vector(2)> x 0.0 y 1.0 irb(main):008:0>
pandas.DataFrame.append
サンプルコードでは空のDataFrameに下記のようにappendメソッドでSeriesのインスタンスを追加しています。
>>> dataset = DataFrame(columns = ['x', 'y']) >>> s = Series([x, y], index = ['x', 'y']) >>> dataset.append(s, ignore_index = True) x y 0 0.0 1.0 >>>
rubyではDataFrameクラスのadd_rowメソッドでVectorインスタンスを追加します。
Daru::DataFrame.add_row
http://www.rubydoc.info/gems/daru/0.1.4.1/Daru%2FDataFrame%3Aadd_row
irb(main):026:0* dataset = Daru::DataFrame.new({x: [], y: []}) => #<Daru::DataFrame(0x2)> x y irb(main):027:0> v = Daru::Vector.new([x, y], index: [:x, :y]) => #<Daru::Vector(2)> x 0.0 y 1.0 irb(main):028:0> dataset.add_row(v) => #<Daru::Vector(2)> x 0.0 y 1.0 irb(main):029:0> dataset => #<Daru::DataFrame(1x2)> x y 0 0.0 1.0 irb(main):030:0>
numpy.pi
pythonではpi(円周率)はnumpyに定数として用意されています。
>>> np.pi
3.141592653589793
>>>
rubyではMathモジュールの定数として用意されています。
Math::PI
https://docs.ruby-lang.org/ja/latest/class/Math.html#C_-P-I
irb(main):002:0* Math::PI => 3.141592653589793 irb(main):003:0>
numpy.sin
pythonでは三角関数のsinを求めるメソッドはnumpyに用意されています。
numpy.sin
https://docs.scipy.org/doc/numpy-1.9.1/reference/generated/numpy.sin.html
>>> np.sin(2 * np.pi) -2.4492935982947064e-16 >>>
rubyではMathモジュールに用意されています。
Math.sin
https://docs.ruby-lang.org/ja/latest/class/Math.html#M_SIN
irb(main):004:0* Math.sin(2 * Math::PI) => -2.4492935982947064e-16 irb(main):005:0>
正規分布
pythonでは正規分布を求めるメソッドがnumpyに用意されています。
numpy.random.normal
https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.normal.html
>>> np.random.normal(scale = 0.3) 0.0948107519352898 >>>
rubyでは調べてみた限りでは正規分布を求めるメソッドは用意されていないようなので、下記サイトを参考にボックス=ミュラー法で正規分布を求めるメソッドを定義します。
irb(main):363:0* def normal_rand(mu = 0, sigma = 1.0) irb(main):364:1> random = Random.new irb(main):365:1> (Math.sqrt(-2 * Math.log(random.rand)) * Math.sin(2 * Math::PI * random.rand) * sigma) + mu irb(main):366:1> end => :normal_rand irb(main):367:0> irb(main):368:0* normal_rand(0, 0.3) => -0.5960439895226316 irb(main):369:0>
下記のようなgemもあるようですが、あまり使われている感じではなさそうかなと。
create_datasetメソッドをrubyで
ここまでの内容を使ってサンプルコードのcreate_datasetメソッドをrubyで実装します。
require 'daru' def normal_rand(mu = 0, sigma = 1.0) random = Random.new (Math.sqrt(-2 * Math.log(random.rand)) * Math.sin(2 * Math::PI * random.rand) * sigma) + mu end def create_dataset(num) dataset = Daru::DataFrame.new({'x': [], 'y': []}) num.times do |i| x = i.to_f / (num - 1).to_f y = Math.sin(2 * Math::PI * x) + normal_rand(0, 0.3) dataset.add_row(Daru::Vector.new([x, y], index: [:x, :y])) end return dataset end
元のサンプルコードとrubyで実装したメソッドのそれぞれの実行結果は下記のようになりました。
>>> create_dataset(10) x y 0 0.000000 -0.470924 1 0.111111 0.417868 2 0.222222 0.420972 3 0.333333 0.494881 4 0.444444 0.654892 5 0.555556 -0.737098 6 0.666667 -0.605475 7 0.777778 -1.809355 8 0.888889 -1.211044 9 1.000000 -0.114083 >>>
irb(main):382:0* create_dataset(10) => #<Daru::DataFrame(10x2)> x y 0 0.0 -0.3909174 1 0.11111111 0.78616375 2 0.22222222 1.11320947 3 0.33333333 0.97217041 4 0.44444444 0.21530738 5 0.55555555 -0.8419577 6 0.66666666 -0.8907612 7 0.77777777 -1.2237687 8 0.88888888 -1.0662619 9 1.0 0.26103827 irb(main):383:0>
とりあえずちゃんと動いているようですが、他の部分の実装を進める上で不都合があった場合は都度修正していきたいと思います。
2016/11/15 追記: rubyのnormal_randへの引数の渡し方が間違っていたので修正しました。