最尤推定による回帰分析のコード

 今回は「ITエンジニアのための機械学習理論入門」の最尤推定による回帰分析のサンプルコードを ruby で実装してみます。書籍のサンプルコードは下記に公開されています。

github.com

自然対数

 今回は前回までのコードと似ている部分が多く、新しい要素は自然対数のみで、python では自然対数を求めるために numpy.log を使います。

numpy.log
https://docs.scipy.org/doc/numpy/reference/generated/numpy.log.html

 ruby では Math.log メソッドを使います。

Math.log
https://docs.ruby-lang.org/ja/latest/method/Math/m/log.html

サンプルスクリプト全体

 ruby でのコード全体としては下記のように実装しました。

require 'daru'
require 'nyaplot'

N = 10
M = [0, 1, 3, 9]

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

def log_likelihood(dataset, f)
  dev = 0.0
  n = dataset.size.to_f
  dataset.each_row do |line|
    x = line.x
    y = line.y
    dev += (y - f.call(x))**2
  end
  err = dev * 0.5
  beta = n / dev
  -beta * err + 0.5 * n * Math.log(0.5 * beta / Math::PI)
end

def resolve(dataset, m)
  t = dataset.y

  columns = {}
  (m+1).times do |i|
    columns["x**#{i}"] = dataset.x ** i
  end
  phi = Daru::DataFrame.new(columns)

  tmp = (phi.transpose.to_matrix * phi.to_matrix).inv
  ws = (tmp * phi.transpose.to_matrix) * Vector.elements(t.to_a)

  f = lambda {|x|
    y = 0
    ws.each_with_index do |w, i|
      y += w * (x ** i)
    end

    y
  }

  sigma2 = 0.0
  dataset.each_row do |line|
    sigma2 += (f.call(line.x) - line.y)**2
  end
  sigma2 /= dataset.size

  return f, ws, Math.sqrt(sigma2)
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, sigma = 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_middle = plot.add(:line, linex, liney)
  line_middle.title("Sigma=#{sprintf("%.2f", sigma)}")
  line_middle.color('red')
  line_upper = plot.add(:line, linex, liney.map {|y| y + sigma })
  line_upper.color('red')
  line_lower = plot.add(:line, linex, liney.map {|y| y - sigma } )
  line_lower.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

fig.show

df = Daru::DataFrame.new({m: [], 'Test set': [], 'Training set': []})  
9.times do |m|
  f, ws, sigma = resolve(train_set, m)
  train_mlh = log_likelihood(train_set, f)
  test_mlh = log_likelihood(test_set, f)
  df.add_row(Daru::Vector.new([m, test_mlh, train_mlh], index: [:m, 'Test set'.to_sym, 'Training set'.to_sym]))
end

df.plot(type: [:line, :line], x: [:m, :m], y: ['Test set'.to_sym, 'Training set'.to_sym]) do |plot, diagrams|
  test_set_diagram = diagrams[0]
  train_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("Log likelihood for N=#{N}")
  plot.y_label('')
  plot.legend(true)
end

 これを jupyter notebook 上で実行すると下記のようなグラフが描画されます。

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

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

 Jupyter Notebook 上での実行結果は下記に公開してあります。

http://nbviewer.jupyter.org/github/h-akanuma/ml4se_study/blob/master/03-maximum_likelihood.ipynb

github.com