次の方法で共有



2018 年 11 月

Volume 33 Number 11

人工知能 - 強化学習の詳細

Frank La La

Frank La Vigne先月のコラムでは強化学習 (RL) のいくつかの基本的な概念について取り上げました。簡単な環境を使用した厳密にランダムな方法、そして、過去の行動を記憶して、どの行動が報酬を引き出すかを覚え込ませる Q テーブルを実装する方法を試しました。デモでは、ランダムに動作するエージェントは対象回数のうち約 1% が目標の状態に達することができ、過去の行動を記憶させた Q テーブルを使用すると全体の回数の約半数で目標の状態に達することができました。ただし、この実験は、RL の有望で拡張可能な分野のほんの表面を垣間見たに過ぎません。

以前のコラム (msdn.com/magazine/mt830356) を思い起こしてください。RL の問題空間は、環境、エージェント、行動、状態、報酬で構成されています。エージェントは環境の状態を確認して、行動を起こします。行動によって、エージェントまたは環境 (あるいはその両方) の状態が変わります。エージェントは報酬を受け取り、環境の更新状態を確認します。その後このサイクルが再開され、エージェントが事前に定義された目標に関して成功または失敗するまで繰り返して実行されます。エージェントが成功または失敗すると、シミュレーションを終了します。エージェントは Q テーブルを使用して、適切な報酬を生み出す行動を記憶し、その後のシミュレーションで意思決定を行うときに Q テーブルを参照します。

多腕バンディット問題

RL において従来から存在する問題の 1 つは、探索と活用間の対立です。“1 アーム バンディット” とも言われるスロット マシンが、この問題にインスピレーションを与えてくれます。スロット マシンを何台も集めると “多腕バンディット” ができます。 これらのスロット マシンそれぞれに、当たりとはずれの確率があります。各回で当たりを出す確率を P として表すと、はずれる確率は 1 - P となります。あるマシンの当たり確率 (JP) が 0.5 の場合、レバーを引くごとに当たりとはずれの確率が半々で等しくなります。対照的に、マシンの JP が 0.1 の場合、90% の割合ではずれることになります。

では、5 台のスロット マシンがあり、プレーヤー (つまりエージェント) には当たる確率を最大化し、はずれる確率を最小にするという目標があるとします。マシンの当たり確率 (JP) をあらかじめ知らない場合、最初はエージェントに必ずリスクが生じます。最初にレバーを回して、エージェントが当たりを引き、払戻金を受け取ります。ただし、その後にスロットを回していくと、このマシンで払戻金を受ける JP は約半分、つまり 0.54 であることが分かります。スロット マシンでは、これはかなり気前のいい台です。エージェントは、既知のリソースを活用すべきか、新しいマシンを探索するかを決めなければなりません。最初のスロット マシンで払戻金を受ける確率がかなり高い場合、5 台の中の他の台を試して、払戻確率が今よりよいことを確認する価値がありますか。

この問題空間をさらに探索する最善の方法は、Jupyter Notebook で Python コードを使用する方法です。好みのプラットフォームで、Python 3 ノートブックを作成します。前の記事 (msdn.com/magazine/mt829269) で Jupyter Notebook について取り上げました。空のセルを作成し、以下のコードを入力してセルを実行します。

import numpy as np
import matplotlib.pyplot as plt
number_of_slot_machines = 5
np.random.seed(100)
JPs =  np.random.uniform(0,1, number_of_slot_machines)
print(JPs)
plt.bar(np.arange(len(JPs)),JPs)
plt.show()

出力は、示されている図 1 のようになり、値のプロットが示されます。

[0.54340494 0.27836939 0.42451759 0.84477613 0.00471886]

5 台のスロット マシンの当たり確率
図 1: 5 台のスロット マシンの当たり確率

このコードによって、一連の 5 台のスロット マシンの JP 値配列が 0.004 から 0.844 の範囲で作成されます。エージェントが試した最初のマシンは確率が高いものの、最高ではありません。言うまでもなく、払戻率 84.4% の 4 台目のスロット マシンが、この環境で払戻金を受けるには最高のマシンです。また、当たりの払戻金を受けるには、最後のスロット マシンの確率が最悪であることにも注目できます。エージェントは、事前に払戻率について知らず、自分で発見する必要があることに注意してください。もしエージェントが最初のマシンのままで、探索よりも活用を優先させると、払戻率が最高のスロット マシンを見つけることはできません。

シミュレーションの開始時にエージェントが把握している事柄を表すため、以下のコードを新しいセルに追加します。

known_JPs = np.zeros(number_of_slot_machines)

これにより、ゼロで構成される配列が作成されます。つまり、エージェントは各スロット マシンの JP をゼロであると想定していることになります。これはすべてのセルにおける初期値として最適であるとは言えない場合がありますが、ここでの目的では十分です。特定のスロット マシンのシミュレーションを作成するため、以下のコードを新しいセルに追加して、実行します。

def play_machine(slot_machine):
  x = np.random.uniform(0, 1)
  if (x <= JPs[slot_machine]):
    return(10)
  else:
    return(-1)

このコード スニペットでは、対象マシンが払い戻す場合には報酬 10 を払い、払い戻さない場合には -1 の負の報酬を払うとシミュレートします。払戻確率は、JPs numpy 配列で定義されている確率に基づいています。コードをテストするため、以下の Python コードを新しいセルに入力して、実行します。

# Test Slot Machine 4
for machines in range(10):
  print(play_machine(3))
print ("------")      
# Test Slot Machine 5
for machines in range(10):
  print(play_machine(4))

このコードでは、確率の最も高いマシンと最も低いマシンを比較しています。これは確率の問題なので、出力結果の保証はありません。マシン 4 は多くが値 10 で、マシン 5 はほぼすべてが値 -1 という結果になります。シミュレートされたスロット マシン コードは予想通りに動作するので、次に RL:Epsilon Greedy で一般的なアルゴリズムを調べます。

Epsilon Greedy アルゴリズム

ここで、エージェントが直面する主要なジレンマは、貪欲を優先、つまり既知のリソースを活用するのか、それとも好奇心を優先、つまりもっと報酬を得るチャンスにかけるため他のスロット マシンを探索するかどうかです。このジレンマを解決する最も簡単なアルゴリズムの 1 つは Epsilon Greedy アルゴリズムと呼ばれています。このアルゴリズムでは、エージェントは、それまでに払戻確率が最も高かったスロット マシンを使用し続けるという選択と、より高い払戻確率があるかもしれないという期待に基づいて別のマシンを試すという選択をランダムに行います。Epsilon の値が小さいと、このアルゴリズムは Greedy アルゴリズムに従いますが、時折別のスロット マシンを試します。たとえば、Epsilon 値が 0.1 である場合は、このアルゴリズムは全体の 90 パーセントで活用を選択し、全体の 10 パーセントのみで探索を行います。通常、Epsilon の既定値は 0.05 から 0.1 の間になる傾向があります。つまり、エージェントは自分が把握している中で最適なスロット マシンで主にプレイし、時々新しいマシンを試します。覚えておくべきこととして、レバーを回すたびに費用がかかり、エージェントはスロット 4 の払戻確率が最高であるという事実を知らないという点です。

この点が RL の概念を際立たせます。エージェントは最初は環境について何も知らないため、最初に探索し、後に活用する必要があります。プロセス全体を通じて学習が継続されます。基本的には、これが遅延満足という概念です。エージェントが最も関心を示すのは完全に貪欲になるということではないので、探索にも余地が残されています。

Epsilon Greedy 仮説のテスト

この仮説をテストするため、図 2 のコードを新しいセルに追加して、実行します。このコードによって、multi_armed_bandit 関数が作成されます。この関数は、一連の実行をスロット マシンのコレクションに対してシミュレートします。この関数は、当たりによる払戻に関して生じた確率を格納します。繰り返し実行するたびに、エージェントは、それまでに観察された最高の払戻確率のスロット マシンと、任意の別のマシンをランダムに試します。argmax 関数は、numpy 配列内の最も大きい値を返します。この場合には、当たり確率が最高のスロット マシンになります。この関数のパラメーターによって、スロット マシンの数、実行する繰り返し回数、Epsilon の値を制御できます。 

図 2 強化学習コード

def multi_armed_bandit(arms, iterations, epsilon):
  total_reward, optimal_action = [], []
  estimated_payout_odds = np.zeros(arms)
  count = np.zeros(arms)
  for i in range(0, iterations):
    epsilon_random = np.random.uniform(0, 1)
    if epsilon_random > epsilon :
      # exploit
      action = np.argmax(estimated_payout_odds)
    else :
      # explore
      action = np.random.choice(np.arange(arms))
    reward = play_machine(action)
    estimated_payout_odds[action] = estimated_payout_odds[action] +
      (1/(count[action]+1)) *
      (reward - estimated_payout_odds[action])
    total_reward.append(reward)
    optimal_action.append(action == np.argmax(estimated_payout_odds))
    count[action] += 1
  return(estimated_payout_odds, total_reward)

RL コードを配置できたので、いよいよ Epsilon Greedy アルゴリズムをテストできます。図 3 のコードを空のセルに入力して、実行します。結果には、参照しやすいように図 1 からのグラフと、RL コードによって観察された確率が示されます。

図 3 実際のスロット マシンの確率をエージェントの観察内容と比較するためのコード

print ("Actual Odds")
plt.bar(np.arange(len(JPs)),JPs)
plt.show()
print (JPs)
print("----------------------------------")
iterations = 1000
print("\n----------------------------------")
print ("Learned Odds with epsilon of .1")
print("----------------------------------")
learned_payout_odds, reward = multi_armed_bandit(number_of_slot_machines, iterations, .1)
plt.bar(np.arange(len(learned_payout_odds)),learned_payout_odds)
plt.show()
print (learned_payout_odds)
print ("Reward: ", sum(reward))

図 4 に示されているように、このアルゴリズムは優れていることを示しました。最も高い確率のスロット マシンを判別するだけではなく、他の 4 台のスロット マシンの極めて正確な払戻確率も求めています。グラフも適切に配置されています。ただし、5 番目のスロット マシンは例外で、エージェントの観察では払戻確率がかなり低い負のスコアになっています。

Epsilon 値 0.1 の結果
図 4 Epsilon 値 0.1 の結果

これで、ベースラインが確立されましたので、次にさらに実験してみましょう。Epsilon がゼロに設定されると、つまり、このアルゴリズムで探索をまったく行わないとどうなりますか?新しいセルに次のコードを入力して実行し、この実験を行います。

print("\n----------------------------------")
print ("Learned Odds with epsilon of 0")
print("----------------------------------")
learned_payout_odds, reward =
  multi_armed_bandit(number_of_slot_machines, iterations, 0)
plt.bar(np.arange(len(learned_payout_odds)),learned_payout_odds)
plt.show()
print (learned_payout_odds)
print ("Reward: ", sum(reward))

結果として得られるグラフには、0 より大きい 1 つの値が示されます。1 台のマシンが他のマシンよりも優勢になります。エージェントが 1 台のマシンを見つけて、そのマシンにこだわったことが明確です。ただし、コードを複数回実行すると、興味深いパターンが時折生じることに気づく場合があります。1 台以上のマシンが負の値で、1 台のマシンがゼロより大きい値になることがあります。このような場合は、エージェントは特定のマシンで負け、別のマシンで勝ったことになります。エージェントが勝つマシンをいったん見つけると、そのマシンを使い続けます。argmax 関数が選択するマシンです。Epsilon がゼロに設定されている場合、エージェントはまだ探索を行いますが、計画的ではありません。そのため、観察されるスロット マシンの確率は実際の確率と近くなりません。また、Epsilon が 0.1 に設定された場合よりも “Greedy” メソッドが低い報酬スコアを生み出すことにも注目できます。貪欲、少なくとも絶対貪欲の場合には、逆効果になります。

Epsilon を 1 に設定し、常時エージェントが探索のみを行い、活用はまったく行わない場合をどうなるのでしょうか。以下のコードを新しいセルに入力して実行します。

print("\n----------------------------------")
print ("Learned Odds with epsilon of 1")
print("----------------------------------")
learned_payout_odds, reward  = multi_armed_bandit(number_of_slot_machines, iterations, 1)
plt.bar(np.arange(len(learned_payout_odds)),learned_payout_odds)
plt.show()
print (learned_payout_odds)
print ("Reward: ", sum(reward))

優れた結果が得られ、エージェントで観察された確率は実際の確率に似ていて、グラフの並びは図 1 と非常に近くなっています。実際、Epsilon を 1 に設定して得られる結果は、値が 0.1 の場合と非常に似ています。ただし報酬値に注目すると、大きな違いがあります。Epsilon が 0.1 に設定されたときの報酬値は、1 に設定されている場合よりもほぼ必ず高くなります。エージェントが探索にのみ設定されると、すべての繰り返し時にマシンはランダムに探索を試みます。観察から学習できますが、学習に基づいて行動してはいません。

まとめ

RL は、人工知能の最も魅力的な一面です。この記事では、エージェントが直面する探索と活用 (explore-or-exploit) のジレンマに重点を置いた、従来の “多腕バンディット” 問題における Epsilon Greedy アルゴリズムについて説明しました。さまざまな Epsilon 値と大量のスロット マシンを使用して実験を行い、トレードオフについてさらに調べることをお勧めします。


Frank La Vigne 氏は、Microsoft で AI テクノロジ ソリューションの専門家として働き、企業が分析と AI によってデータからより多くの価値を引き出してより多くの事柄を実現するのを支援しています。また、DataDriven ポッドキャストの共同ホストも努めています。FranksWorld.com (英語) に定期的にブログ記事を投稿し、Frank’s World TV (FranksWorld.TV (英語)) という YouTube チャンネルを主催しています。

この記事のレビューに協力してくれた技術スタッフのAndy Leonard に心より感謝いたします。


この記事について MSDN マガジン フォーラムで議論する