Takuto-Psych/人間がQ学習をして吐き出されるログで機械もQ学習をさせてみた

Created Thu, 30 Nov 2023 00:00:00 +0000 Modified Sat, 06 Jul 2024 15:53:35 +0900
6056 Words

はじめに

世の中にはさまざまなプラットフォームがありますよね。最近ではYouTubeなどの動画サイトがとても盛況なようです。動画を通したSNSとしては、TikTokなども存在します。また、Instagramなども動画を見れて暇つぶしができます。

こういった時間泥棒のプラットフォームがさまざまな人間の時間を奪えるのは、レコメンドシステムのおかげです。レコメンドシステムとは、ユーザーの行動のログをもとに、そのユーザーが好むコンテンツを提示するというものです。レコメンドシステムでは色々なアルゴリズムが採用されています。僕はあんまり数学できないので深層学習はわからないです。だから、心理学出だからこそ比較的理解がしやすいQ学習を例にとってみます。Q学習の詳細はこちらの記事に任せます。

このQ学習による行動の最適化はヒトもするし、なんならラットもすると考えられています。このQ学習のロジックはコンピュータサイエンスの中でも使われており、機械に動物っぽい行動をさせることにある程度成功しました。その結果、Q学習は今回のテーマであるレコメンドシステムにも組み込まれています。

今回は、機械がQ学習のアルゴリズムだけを使い人間に提供するコンテンツを取捨選択するというレコメンドシステムが採用されているプラットフォームで、人間もまたQ学習のアルゴリズムだけで自分の好きなコンテンツを視聴するという状態を考えてみたいと思います。Q学習の相互作用とでもいいましょうかね。

機械と人間のQ学習の相互作用の具体例

上では動画共有サイトを例に挙げてみました。僕は仕事中作業BGMを流します。具体的には、Apple Musicを使っています。あなたにオススメという欄から音楽を自動で再生しています。その結果、甲斐バンド等々の古臭い音楽が流れ続けるようになりました。おそらく、甲斐バンドなどの古臭い曲をオススメされるたびに僕が好んで聴いているので、アップル側がおすすめをするようになったのだと思います。そして、そのオススメによって僕がまた甲斐バンドを聴くというループが発生しています。

実際には、僕のような視聴履歴を持っている人(多分50代後半のおっさん)が他に聴いた曲を僕にレコメンドするというシステムや、さまざまな楽曲の音の成分を取り出して僕が好む曲に含まれている似ている成分(うっすらエフェクターが入っているエレキギターの音)があればレコメンドするというようなシステムをアップルは組み込んでいると思いますが、一旦そういうことは考えないでおきます。

もう少し単純化して数字で表現をする

今回は、機械があるアーティスト自体を人間にオススメするという状態を考えます。各楽曲をオススメするという考え方ではなく、アーティスト自体をオススメするという考え方です。

機械視点で得られるフィードバックの話

後でコード書いていくので必要なlibraryを使えるようにおまじないを書いておきます。

rm(list  = ls())
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.1     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

まずは、みなさんが好きなアーティストを友人にオススメするという状態を考えてみましょう。これは、サザンオールスターズを例に取ると、『勝手にシンドバッド』と『いとしのエリー』を全く同じものとして扱って、友人にサザンオールスターズのオススメをするという状態です。

この時、そのオススメを受け取る相手が桑田佳祐の声が好きだったとしても、アルバムごとにあるいは発表年代ごとに曲の雰囲気が違うため、曲ごとに好き嫌いが発生するかもしれません。

たとえばオススメする相手の友人はロック調の曲は好きですが、バラード調の曲は嫌いです。だから『勝手にシンドバッド』は好きだけど『いとしのエリー』は嫌いという状態になると仮定します。もし友人の好きな『勝手にシンドバッド』をみなさんがオススメできたとすると、友人は最後まで聞いてくれます。一方、友人が嫌いな『いとしのエリー』をみなさんがオススメしてしまったら、友人は半分程度聴いてスキップします。

サザンの全曲を流石に僕は把握していないのでわからないですが、全楽曲のうちの30%がロック調、残り70%がバラード調だと仮定します。その仮定で、サザンの楽曲100曲をみなさんが友人にオススメした場合、仮に友人が全曲再生はしたとすると、30曲は全部聴いてもらえ、70曲は途中でスキップされるという経験をみなさんはすることになります。オススメしたアーティスト(の曲)を全部聴いてくれるという状態は、みなさんの視点に立つと、そのアーティストをオススメするという行動Qが正解だったという経験なので報酬になります。

一方、オススメした曲を途中でスキップされるという経験は、みなさんの視点に立つとそのアーティストをオススメするという行動Qが不正解だったということなので罰(あるいは報酬なし)と捉えられるかと思います。

今、ご友人に対してサザンを勧めるという行動を心の中でみなさんにしてもらいました。きっと、100曲勧めたらサザンを勧めるのはやめておいて他のアーティストを勧めようと思ったでしょう(厳密には、サザンのロック調の曲を勧めるでしょうが、今回は、アーティスト単位で勧めるので曲調の違いは考慮しません)。

心の中で行っていた行動は、主体はみなさん一人一人で、その対象は友人でした。これがレコメンドシステムでは、主体を機械にその対象はみなさん一人一人として、行われています。

さて、機械が11人分のアーティストを勧めるとします。それぞれのアーティストを刺激と捉えると、S1 〜S11 の11個の刺激があると考えられます。このS1 〜S11 をある人にオススメするとスキップされず最後まで聴いてもらえる(報酬を得られる)確率が、S1 は100%、S2 は90% … S10 は10%、S11は0%だとします。

これは、機械にとってS1 〜S11の中からある一つの刺激Siを選んでオススメすることをQ_COMi と表現することができます。Q_COMというのは、COMputerにとっての、Si を選択する行動価値Qiという意味です。

仮に試行回数が10万回の、機械が人間にアーティストをオススメをしてそのアーティストを選ぶという状況があると各試行で機械が提示しうるアーティストが報酬(全部聴いてもらえる)を得られるかどうかは以下のようなRコードで表現ができます。

##刺激作り

stimulus_num <- 11
trials <- 100000


##報酬が出てくる確率を設定する。(1 - S_n$s_nが報酬なしになる)
STIMULUS <- seq(
  0,
  1,
  length.out = stimulus_num
) %>% t(
  
) %>% data.frame(
  
) %>% rename_with(
  \(x) 
  str_replace(x, "X", "s"),
  starts_with("X")
)


##その後、t試行分、実際の報酬と罰を設定する。報酬= 1 罰= 0
STIMULUS_REWARD <- STIMULUS  %>% lapply(
  .,
  function(x){
    ##上で設定した報酬確率*試行数だけ報酬を設定。
    c(
      rep(1, times =  round(x * trials, digits = 0)),
      rep(0, times = round((1 - x) * trials, digits = 0)
    )) %>% sample(., length(.))
  }
) %>% data.frame()

STIMULUS_REWARD <- STIMULUS_REWARD %>% mutate(
  .,
  trial_num = c(1:trials)
)

ちゃんと、各試行で各刺激が設定している報酬になっているかを確認します。

STIMULUS_REWARD %>% select(
  .,
  - trial_num
) %>% lapply(
  .,
  sum
)
## $s1
## [1] 0
## 
## $s2
## [1] 10000
## 
## $s3
## [1] 20000
## 
## $s4
## [1] 30000
## 
## $s5
## [1] 40000
## 
## $s6
## [1] 50000
## 
## $s7
## [1] 60000
## 
## $s8
## [1] 70000
## 
## $s9
## [1] 80000
## 
## $s10
## [1] 90000
## 
## $s11
## [1] 1e+05

問題なさそうですね。

ついでにQ_COMi を入れる箱も作っておきましょう。

Q_COM <- rep(
  0,
  stimulus_num
) %>% t(
  .
) %x% matrix(
  0,
  nrow = trials,
  ncol = 1
) %>% data.frame(
  .
)

Q_COM <- Q_COM %>% rename_with(
  \(x)
  str_replace(x, "X", "q_com"),
  starts_with("X")
)
Q_COM <- Q_COM %>% mutate(
  .,
  trial_num = 1:trials
)

さて、ここまでで機械側の学習の話をしてきました。次からは人間側の学習を考えてみましょう。

人間視点で得られるフィードバックの話

さて、レコメンドのシステムで機械からアーティストをオススメされた側の人間の行動を考えましょう。まずは、機械との相互作用で考えるのではなく、友人からアーティストの曲をオススメされたという状況を考えましょう。そうです、今度は先ほどの友人の視点になってみます。

さて、あなたは友人からサザンオールスターズを勧められました。桑田さんのしゃがれ声がロック調の曲ではいい足を出して好きだなと思ったとします。しかし、如何せんバラード調の曲が嫌いです。そんなとき、アーティストを勧めてくれる友人に対してあなたはどうするでしょうか?おそらくこの3択の行動があると思います(曲ごとの評価ではなく、アーティスト単位での評価をします。だからロック調のサザンが聴きたいとは言ってはいけません)。

  1. 友人にオススメされたサザンの曲を最後まで聴く(好きな曲の時の反応)

  2. 友人にオススメされたサザンの曲を聴いてみるけど途中で中断する(嫌いな曲の時の反応)

  3. サザンの曲を全く再生せずに、友人に「サザン以外の曲を勧めてよ!ほら、甲斐バンドとか名前だけ知ってるんだ」と言ってみる。

それぞれを行動選択と、その結果のフィードバックとして捉えると

まず

  1. 友人にオススメされたサザンの曲を最後まで聴く(好きな曲の時の反応)

は、(事前の経験でバラード曲が多くてサザンをあまり好きじゃないことがわかったけれど一定のランダムさで)試しに聴くという行動を選択した結果、報酬のフィードバックを得られたと捉えることができます。

次の

  1. 友人にオススメされたサザンの曲を聴いてみるけど途中で中断する(嫌いな曲の時の反応)

は、(事前の経験でバラード曲が多くてサザンをあまり好きじゃないことがわかったけれど)聴くという行動を試しに選択した結果、案の定報酬のフィードバックを得られなかったと捉えることができます。

最後の

  1. サザンの曲を全く再生せずに、友人に「サザン以外の曲を勧めてよ!ほら、甲斐バンドとか名前だけ知ってるんだ」と言ってみる。

は、(事前の経験でバラード曲が多くてサザンをあまり好きじゃないことがわかったので)その行動の選択をしなかった結果、何もフィードバックを得られなかったと捉えることができます。これは、2のような行動をした結果報酬を得られなかったのとは違いますね。

この時前回(t-1回目)までに友人にオススメされて聴いてみて持っているサザンという刺激を選択する行動価値Q_HUMt-1 を元に今回(t回目)の友人からのサザンのオススメに対して、実際にサザンという刺激を選択するかどうかを決めていると捉えることができます。もちろんこのt回目の経験によって、次のQ_HUMt を更新して次回(t+1回目)の行動選択を決めていきます。

このように考えると、このオススメされたアーティストを選択して聴くという行動は、そもそもそのアーティストを聴く選択をするかどうか、また、その結果得られたフィードバックが報酬かどうかの判定をしてアーティストの価値を更新するというQ学習で捉えることができます。

厳密には、

その結果得られたフィードバックが報酬かどうかの判定をして

の部分は、好きな曲だったら多分最後まで聴くし、嫌いな曲なら途中で離脱するだろうという外部からの判定で、報酬かどうかを決めていますが。

さて、ここまで抽象的に表現することができたら、オススメされる人のQ_HUMについても数式で表現することができそうです。

先ほどまでは、友人からアーティストをあなたが勧められた時の例で考えていましたが、友人を機械に、オススメを受け取る人をあなたに、当てはめて考えてみます。

アーティスト11人のうち1人が10万回勧められるという状態は、刺激S1 〜S11 の中からt時点にSi が提示された際に、そのSi に持っているt-1時点までのQ_HUMi に基づいてSi を選択するかどうか?と表現できます。

以下のように、10万回の試行分の各刺激についてのQ_HUMiの空っぽの箱を持っておきます。

##初期のQ_HUMの値は0で良いので、試行数分行列0のを作る。Q_COMを流用。
Q_HUM <- Q_COM %>% rename_with(
  \(x)
  str_replace(x, "q_com", "q_hum"),
  starts_with("q_com")
)

さて、t時点にQ_HUMi を選択した結果報酬の有無というフィードバックが得られる、次回t+1時点のQ_HUMi を更新していくと捉えられます。

しかし、ここから先の更新の学習のお話や行動選択のお話は、機械側のQ_COMiの更新と共に考えていきましょう。

機械と人間のQ学習それぞれの行動選択と価値更新について

行動選択(β)について

Q学習では、Q値、つまりある行動の価値(ほぼ刺激の価値と同義になるけれど)を内的に持った上で、実際にその行動を実行するかどうかをある程度のランダムさで選択していきます。

これは、プロマックス関数という以下のような式で表現できます。

\[P_i(t) = \frac{1}{1 + exp( -β · Q_i(t) )}\]

これは、t時点のSi の刺激についての行動価値Qi(t)をもとに、βというランダムさを調整するパラメータで修飾してあげて実際にSi を選択するかどうかの確率Pi(t)を0~1で表現したものと言えます。

具体的な値を入れてみるとわかりますが、β=0、Qi(t) = 0.9を代入すると、

\[P_i(t) = \frac{1}{1 + exp( -0 · 0.9 )}\]

\[= \frac{1}{1 + exp(0)}\]

\[= \frac{1}{1 + 1}\]

\[= \frac{1}2 = 50 \%\]

となります。一方、β=1、Qi(t) = 0.9を代入すると、\[P_i(t) = \frac{1}{1 + exp( -1 · 0.9 )}\]

\[= \frac{1}{1 + 0.40...}\]

\[= \frac{1}{1.40...}\]

\[≒ 71 \%\]

このように、βは0以上の値を取り、その値が大きくなればなるほど行動選択がQi(t)の値により忠実になると言えます。

今回の記事では、Q_HUMのβを2に、Q_COMのβを1にして分析をしてみます。レコメンドシステムがアーティストという刺激を選ぶときに行動価値を重視するもののある程度ランダムさを持っているのに対して、そのレコメンドされたコンテンツを人間が選択するときに、行動価値にはより忠実に行動選択を行なっているという仮定を置きます。

Q_COM_beta <- 2
Q_HUM_betq <- 3

価値の更新について

上では、機械と人間それぞれについて、これまでの経験から各アーティストに持っている行動価値を元に、実際の行動選択をする過程について書いてきました。

ただし、これには欠けている箇所があり、毎回の行動選択によって生じるフィードバックから学習する過程が抜けています。この学習過程を、Q学習と言い以下の式で表現します。

\[Q_i(t+1) =Q_i(t) + α[R(t) - Q_i(t)]\]

このR(t)というのは、t試行目に刺激Si を実際に選択して得られたフィードバックを意味します。プラスのフィードバックであれば+1をマイナスのフィードバックであれば0を符号化して与えます。

機械側のQ_COMの例であればオススメしたアーティストが実際に最後まで聞かれたらR(t) = 1を、途中でスキップされたらR(t) = 0を代入します。

一方、人間側のQ_HUMの例であれば、オススメされたアーティストの曲を実際に最後まで聴いたらR(t) = 1を、途中でスキップしたらR(t) = 0を代入します。

ここで重要なのは、どちらも同じ行動(最後まで聴くor途中でスキップする)でフィードバックが報酬だったかどうかを判断していることです。

このαの値が大きいと、