Mon Aug 24 2020

あんまり面白くない人間なので、流行りの(まだ流行ってるの!?)ディープラーニングよりもう少し古典的な手法がずっと浮きでいる。 業務でも初めの2年くらいはディープでGPUをぶんぶん回してたけど、今はもうすっかり行列を分解してばかりいる。

推薦システムを行列分解で構築する理論も応用もかなり古くからあるが、研究は今でも脈々と続いている。

分解方法にもバリエーションはあるが基本形は次のようなものだろう。 \(n \times m\) 行列 \(X\) が与えられる。 このとき次のような \(U,V\) を求めよ。 \[X = UV\] ここで右辺は行列の積である。 そしてその形は \(U\)\(n \times k\) で、\(V\)\(k \times m\) の形をしている。 ここで \(k\) は分解するときに自由に与えることが出来るハイパーパラメータである。 さて厳密に \(=\) を満たす \(U,V\) を見つけることは理論的には興味深いが、実は推薦システムを構築するときにはそれはむしろ避けたく、「適当な近似的に」イコールであることがむしろ望ましい性質となる。 ここがモデルを考える上で面白いところで、何が適当なのか答えは無いために、研究が今でも続いているわけになる。

なぜ適当さが必要かと言えば、やりたいことは結局 implicit なデータセットからの学習だからである。 つまり、どういうものを想定しているのかと言えば、ネット通販での買い物履歴なんかである。 ユーザー \(i\) がアイテム \(j\) を買った、というログがある。 例えばこれを \(i\)\(j\) に興味がある、と解釈するのは妥当だと思う。 興味がないものにお金を払ったりはしないだろう。 「良いものとして評価した」まで言ってしまうのはどうだろうか。 基本的には良いものと思って買うだろうから、雑に言えば9割方正解だと思う。 でも残りの1割はやっぱり、買ったあとに後悔していることもあるだろう。 今のは買った/買ってないという 0 か 1 かの話をしている。 もっと明示的に、レーティングを与えられることもある。 レビューと言って、例えば5段階評価をユーザーにさせることがある。 これを使えば、買った場合には単に 1 にするのではなくて、1 から 5 の整数で評価することができる。 しかし、それでも同じ問題は尚抱えていて、つまり、膨大に用意されているアイテムの内のほとんどはユーザーはその目にも触れずに何の評価も与えていないのである。 ユーザー \(i\) がアイテム \(j'\) を買っていない(目にもしていない)という状態を今は仮に \(P_i^{j'}=0\) とすることにするが、これは決して、悪い評価を下しているのではなくて、何も評価を下していない。

話を簡単にするために、0 か 1 かの問題設定に戻す。 \(P_i^j=1\)\(i\)\(j\) を(高い確率で)良いと評価し、\(P_i^j=0\) はただ単に何も評価していないことを表す。

推薦システムの目的はまだ評価していないアイテムの中で気に入りそうなアイテムを見つけることだ。 ユーザー \(i\) に対して、 ポジティブデータ \(D_i^+ = \{ j \mid P_i^j=1 \}\) と、未観測データ \(D_i^! = \{ j \mid P_i^j=0 \}\) から何かしらのモデルを獲得して、\(D_i^!\) からポジティブらしいアイテムを推論することと言える。

未観測は未観測です、じゃあ、何もできない。 そこで普通は、

  1. \(D_i^+\) の内のほとんどは実際にポジティブ
  2. \(D_i^!\) の内のほとんどは実際にネガティブ

ということにする。

行列分解 \(P=UV\) は、いわば一つのモデル化であり、分解を計算することがモデルの学習だと言えるのだが、 \(k\) を適当に小さくしておくのがポイントである。 大きすぎれば例えば \(P=P I\) (\(I\) は単位行列) といった厳密にイコールになる学習をしてしまうだろう。 しかしこれはただ、ポジティブデータ全てをポジティブに、未観測データ全てをネガティブとして学習しただけである。 \(k\) を適当に小さくして、情報を圧縮することで、似たアイテムには似た推論結果を出すようになる。 モデルの表現力をわざと抑えて、汎化性能を担保する作戦である。

ところで学習方法に踏み込んだ話を一つもしていなかった。 行列分解というモデルは、その学習方法すらもモデルの中身になると思う。

何も考えなければ、\(X_i^j\)\(1\) の成分は \(1\) に、\(0\) の成分は \(0\) に近づくような \(U\)\(V\) を探すだろう。 さっきも言ったように、\(0\) というのは単に未観測なだけを表していて、\(0\) というスコアを表しているわけではない。 \(0\) という値そのものをロスに使わないことで柔軟な学習ができる。 そこで pair-wise な学習が出てくる。 類似度学習 とかで散々出てくるやつだ。 すなわち、「ユーザー vs アイテム(一つ)」からスコアを算出しようとするから、\(0\) とか \(1\) という値が必要なのであって、そうでなくて、「ユーザーに対して、アイテム 1 vs アイテム 2」というアイテムのペアを与えてこれを比較するような学習をさせる。 \(D_i^+\) からアイテム \(i^+\)\(D_i^!\) からアイテム \(i^-\) を持ってくる。 それぞれは高い確率でポジティブデータとネガティブデータである。 このときにそれぞれにスコア \(s^+, s^-\) を算出して \(s^+ > s^-\) であるようにする。 スコアは結局、「ユーザー vs アイテム」として算出するのだが、直接学習するのはスコアの値じゃなくてあくまで大小比較 \(>\) のところ。 というようにするのがこないだ読んだ BPR.

正直、やってることは単純だし、これが2012年になってようやく出たのか、と思う。 全然進んでないなこの分野は。

たぶんまだ誰もやってない研究: