誤差関数(最小二乗法)による回帰分析サンプルのデータセット作成コード

 とりあえず前回で ruby と python のコードを動かす環境を作ったので、サンプルコードを ruby に書き換えていきます。まずは誤差関数(最小二乗法)による回帰分析のサンプルコード。書籍のコードは下記に公開されています。

github.com

 この中で、まず今回はデータセットを作成するためのコードを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

http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.append.html#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に定数として用意されています。

nullege.com

>>> 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もあるようですが、あまり使われている感じではなさそうかなと。

bitbucket.org

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への引数の渡し方が間違っていたので修正しました。