昨日はゲートネットワークの実装を行った。
今日はそれを使って関数近似のためのHMEの実装を行う。
ValueHMEクラス
関数近似のためのHMEをValueHMEクラスとして実装していく。
#==================== # value_hme.rb #-------------------- # 価値ベクトルを関数近似するためのHME #==================== require_relative "gate_nn" class ValueHME # 続く
イニシャライザとアクセサメソッド
まずはイニシャライザとアクセサメソッド。
# 続き def initialize(input_size, experts) @input_size = input_size @experts = experts @gate_nn = GateNN.new(input_size, @experts.size) # cache @experts_weights_size = Array.new @experts.each do |expert| @experts_weights_size.push(expert.get_weights.size) end @learn_mode = false end attr_reader :experts attr_accessor :learn_mode # 続く
エキスパートネットワークは外部から受け取るようにしている。
こうすることで、任意の学習器(HME自身を含む)をエキスパートネットワークにすることが出来る。
もちろん、決まったインタフェースを備えている必要はあるけど。
あと、エキスパートネットワークのパラメータのサイズをキャッシュとして持っているようにしている。
これは、パラメータやパラメータに関する勾配を1本のベクトルとして扱うので、そのベクトルのうち、どこまでがどのエキスパートネットワーク(とゲートネットワーク)のパラメータなのかを知る必要があるから。
もちろん、毎回パラメータのサイズをエキスパートネットワークに問い合わせてもいいんだけど、そのたびにエキスパートネットワーク側では重みのベクトル化を行うのでは、効率が悪いので。
それと、拡張のために@learn_mode
というインスタンス変数を用意している。
出力と勾配の計算
次は出力と勾配の計算。
# 続き def get_value_and_weights_gradient(input) experts_output = Array.new experts_weights_gradient = Array.new @experts.each do |expert| expert_output, expert_weights_gradient = expert.get_value_and_weights_gradient(input) experts_output.push expert_output experts_weights_gradient.push expert_weights_gradient end gate_outputs, gate_weights_gradient = @gate_nn.get_value_and_weights_gradient(input, experts_output) output = experts_output.zip(gate_outputs).reduce(0.0) do |result, (expert_output, gate_output)| result += expert_output * gate_output end weights_gradient = Array.new experts_weights_gradient.zip(gate_outputs).each do |expert_weights_gradient, gate_output| weights_gradient.push(expert_weights_gradient.map{|expert_weight_gradient| expert_weight_gradient * gate_output}) end weights_gradient.push(gate_weights_gradient) weights_gradient.flatten! [output, weights_gradient] end # 続く
強化学習とニューラルネットワークを組合せてみた。(その11) - いものやま。の最後に書いた手順に従って、HMEの出力と、その重みに関する勾配を計算している。
感度確認
続いて、感度確認。
# 続き def get_value_with_weights_gradient(input, weights_gradient, alpha=1.0) weights_gradient_copy = weights_gradient.dup experts_output = Array.new @experts.each_with_index do |expert, expert_index| expert_weights_gradient = weights_gradient_copy.shift(@experts_weights_size[expert_index]) expert_output = expert.get_value_with_weights_gradient(input, expert_weights_gradient, alpha) experts_output.push expert_output end gate_outputs = @gate_nn.get_value_with_weights_gradient(input, weights_gradient_copy, alpha) output = experts_output.zip(gate_outputs).reduce(0.0) do |result, (expert_output, gate_output)| result += expert_output * gate_output end output end # 続く
まぁ、いつものとおり。
重みの取得と更新
最後に重みの取得と更新。
# 続き def get_weights weights = Array.new @experts.each do |expert| weights.push(expert.get_weights) end weights.push(@gate_nn.get_weights) weights.flatten end def add_weights(weights_diff) weights_diff_copy = weights_diff.dup @experts.each_with_index do |expert, expert_index| expert_weights_diff = weights_diff_copy.shift(@experts_weights_size[expert_index]) expert.add_weights(expert_weights_diff) end @gate_nn.add_weights(weights_diff_copy) end end # 続く
これも特に難しいことはなく。
動作確認
関数近似のためのニューラルネットワークと同じように、以下の動作確認のコードで動作を確認。
# 続き if __FILE__ == $PROGRAM_NAME require_relative "value_nn" value_nn_1_1 = ValueNN.new(3, 10, -1.0, 1.0) value_nn_1_2 = ValueNN.new(3, 10, -1.0, 1.0) value_nn_1_3 = ValueNN.new(3, 10, -1.0, 1.0) value_nn_2_1 = ValueNN.new(3, 10, -1.0, 1.0) value_nn_2_2 = ValueNN.new(3, 10, -1.0, 1.0) value_hme_1 = ValueHME.new(3, [value_nn_1_1, value_nn_1_2, value_nn_1_3]) value_hme_2 = ValueHME.new(3, [value_nn_2_1, value_nn_2_2]) value_hme = ValueHME.new(3, [value_hme_1, value_hme_2]) [ [1.0, 1.0, 1.0], [1.0, 1.0, 0.0], [1.0, 0.0, 1.0], [0.0, 1.0, 1.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0], [0.0, 0.0, 0.0], ].each do |input| output, weights_gradient = value_hme.get_value_and_weights_gradient(input) output_with_weights_gradient = value_hme.get_value_with_weights_gradient(input, weights_gradient) diff = output_with_weights_gradient - output alpha = 1.0 upper_bound = nil lower_bound = nil last = 100 100.times do |t| if diff < 0.0 upper_bound = alpha if lower_bound.nil? alpha /= 2.0 else alpha = (upper_bound + lower_bound) / 2.0 end elsif diff < 0.9 lower_bound = alpha if upper_bound.nil? alpha /= diff else alpha = (upper_bound + lower_bound) / 2.0 end elsif diff > 1.1 upper_bound = alpha if lower_bound.nil? alpha /= diff else alpha = (upper_bound + lower_bound) / 2.0 end else last = t break end output_with_weights_gradient_and_alpha = value_hme.get_value_with_weights_gradient(input, weights_gradient, alpha) diff = output_with_weights_gradient_and_alpha - output end output_with_01alpha = value_hme.get_value_with_weights_gradient(input, weights_gradient, 0.1 * alpha) diff_01alpha = output_with_01alpha - output puts "input: #{input}, output: #{output}" puts " alpha: #{alpha}, iterations: #{last}" puts " diff (alpha): #{diff}, diff (0.1*alpha): #{diff_01alpha}" weights_diff = weights_gradient.map{|i| i * 0.1 * alpha} value_hme.add_weights(weights_diff) new_output, _ = value_hme.get_value_and_weights_gradient(input) new_diff = new_output - output puts " new output: #{new_output}, diff: #{new_diff}" end end
ここでは、関数近似のためのニューラルネットワークを5つ用意して、value_nn_1_1〜value_nn_1_3をvalue_hme_1というHMEに、value_2_1〜value_2_2をvalue_hme2というもう1つのHMEにして、さらにvalue_hme_1とvalue_hme_2をエキスパートネットワークとしたHMEを作って動作確認している。
(同様にして、何階層にも出来る)
これを実行してみると、次のような出力になる:
input: [1.0, 1.0, 1.0], output: 0.05986416012358227 alpha: 1.0, iterations: 0 diff (alpha): 0.9289888817805412, diff (0.1*alpha): 0.2585116061468381 new output: 0.31837576627042036, diff: 0.2585116061468381 input: [1.0, 1.0, 0.0], output: -0.0088557389615605 alpha: 1.6901813093565354, iterations: 3 diff (alpha): 0.9083100963636939, diff (0.1*alpha): 0.2150878300932007 new output: 0.2062320911316402, diff: 0.2150878300932007 input: [1.0, 0.0, 1.0], output: 0.4098816295435096 alpha: 2.45126158569981, iterations: 3 diff (alpha): 0.9349657780321493, diff (0.1*alpha): 0.24806198244540345 new output: 0.657943611988913, diff: 0.24806198244540345 input: [0.0, 1.0, 1.0], output: 0.606900855467693 alpha: 22.403913017504227, iterations: 6 diff (alpha): 0.9483159536041491, diff (0.1*alpha): 0.4518258839565471 new output: 1.0587267394242401, diff: 0.4518258839565471 input: [1.0, 0.0, 0.0], output: 0.47960205270690637 alpha: 7.034555778405797, iterations: 4 diff (alpha): 0.9510846974619821, diff (0.1*alpha): 0.2590382423336586 new output: 0.7386402950405649, diff: 0.2590382423336586 input: [0.0, 1.0, 0.0], output: 0.82224932422473 alpha: 6.27189544455273, iterations: 2 diff (alpha): 0.9216118044509434, diff (0.1*alpha): 0.17414874787148726 new output: 0.9963980720962172, diff: 0.17414874787148726 input: [0.0, 0.0, 1.0], output: 1.0708394764756104 alpha: 16.73667461474436, iterations: 2 diff (alpha): 0.9788545561868958, diff (0.1*alpha): 0.16173027182584243 new output: 1.2325697483014528, diff: 0.16173027182584243 input: [0.0, 0.0, 0.0], output: 0.8046987548914151 alpha: 24.164416551615396, iterations: 3 diff (alpha): 0.9837389148124958, diff (0.1*alpha): 0.2792031061429212 new output: 1.0839018610343363, diff: 0.2792031061429212
ちゃんと期待通りに動いていることが分かる。
今日はここまで!