Rubyだとちょっと数値微分に一工夫必要。
Pythonだと
def numerical_diff(f,x):
h = 1e-4
return (f(x+h) - f(x-h)) / (2*h)
と引数に関数を渡せてしまうが、Rubyで同じことはできない。
Proc(lambda)オブジェクトを定義し、ブロック化して関数を渡すことで実行可能。
数値微分
numerical_diff.rb
def numerical_diff(f,x)
h = 1e-4
return (f.call(x+h) - f.call(x-h)) / (2*h)
end
def function_1(x)
0.01*x**2 + 0.1*x
end
# 実行
f = lambda {|arg| function_1(arg)}
numerical_diff(f,5) # => 0.1999999999990898
numerical_diff(f,10) # => 0.2999999999986347
勾配
require 'numo/narray'
def numerical_gradient(f, x)
h = 1e-4
grad = Numo::DFloat.zeros(x.shape)
# 各xで偏微分
x.length.times do |idx|
tmp_val = x[idx]
# 要素に差分を入れて、中心差分を取る
x[idx] = tmp_val + h
fxh1 = f.call(x)
x[idx] = tmp_val - h
fxh2 = f.call(x)
grad[idx] = (fxh1 - fxh2) / (2*h)
# 元のxに戻す
x[idx] = tmp_val
end
grad
end
def function_2(x)
return x[0]**2 + x[1]**2
end
x = Numo::DFloat[3.0, 4.0]
f = lambda{|arg| function_2(arg)}
numerical_gradient(f, x) # =>Numo::DFloat#shape=[2] [6, 8]
x = Numo::DFloat[0.0, 2.0]
numerical_gradient(f, x) # => Numo::DFloat#shape=[2] [0, 4]
x = Numo::DFloat[3.0, 0.0]
numerical_gradient(f, x) # => Numo::DFloat#shape=[2] [6, 0]
勾配法
def gradient_descent(f, init_x, lr=0.01, step_num=10)
x = init_x
step_num.times do |i|
grad = numerical_gradient(f, x)
x -= lr * grad
end
return x
end
init_x = Numo::DFloat[-3.0, 4.0]
f = lambda{|arg| function_2(arg)}
# 良い例
gradient_descent(f, init_x, lr=0.1, step_num=100) => Numo::DFloat#shape=[2][-6.11111e-10, 8.14814e-10]
# 悪い例 : 学習率が大きすぎる例
gradient_descent(f, init_x, lr=10, step_num=100) => Numo::DFloat#shape=[2][-2.58984e+13, -1.29525e+12]
# 悪い例 : 学習率が小さすぎる例
gradient_descent(f, init_x, lr=-10, step_num=100) => Numo::DFloat#shape=[2][-2.4204e+12, 3.0874e+12]