🐡

GRU4Rec

2024/01/01に公開

GRU4Recを用いて、ユーザーが次に見る映画を予測する。

GRU

  • h_tz_tの値によって更新する
h_t = (1 - z_t)h_{t-1} + z_t\tilde{h}_t
  • h_tを更新する割合を決めるゲートを作成する
    z_tが1つ前の隠れ状態と候補の隠れ状態をどの割合で保持するか決める
z_t = \sigma(W_z x_t + U_z h_{t-1})
  • h_tをリセットする割合を決めるゲートを作成する
    r_tがないと過去の隠れ状態をすべて同じように扱う
r_t = \sigma(W_r x_t + U_r h_{t-1})
  • h_tを更新する候補\tilde{h}_tを作成する
\tilde{h}_t = \tanh(W x_t + U (r_t \odot h_{t-1}))
変数
  • x_{t}: 入力 (ベクトル)
  • h_{t}: 出力 (ベクトル)
  • \tilde{h}_t: 候補隠れ状態 (ベクトル)
  • z_t: 更新ゲート (ベクトル)
  • r_t: リセットゲート (ベクトル)
  • W: パラメータ (行列)
  • U: パラメータ (行列)
活性化関数
  • \sigma: シグモイド関数
  • \tanh: tanh関数
数式
  • \odot: アダマール積

GRU4Rec

原著論文は下記である。
SESSION-BASED RECOMMENDATIONS WITH RECURRENT NEURAL NETWORKS

  • エンコーディング

We also experimented with adding an additional embedding layer, but the 1-of-N encoding always performed better.

1-of-Nエンコーディング(ワンホットエンコーディング)を行い、アイテムリストの中で実際に次に起こるアイテムを1に、それ以外を0にエンコーディングする。

  • フィードフォワード層

The core of the network is the GRU layer(s) and additional feedforward layers can be added between the last layer and the output. The output is the predicted preference of the items, i.e. the likelihood of being the next in the session for each item.

最後のGRU層と出力の間にフィードフォワード層を追加し、それぞれのアイテムの予測値を計算する。

  • ミニバッチ学習

Therefore we use session-parallel mini-batches. First, we create an order for the sessions. Then, we use the first event of the first X sessions to form the input of the first mini-batch (the desired output is the second events of our active sessions). The second mini-batch is formed from the second events and so on.

セッション-パラレル ミニバッチ法を使用し、計算を効率化する。

  • サンプリング戦略

Therefore we should sample items in proportion of their popularity. Instead of generating separate samples for each training example, we use the items from the other training examples of the mini-batch as negative examples.

ミニバッチ内の他のトレーニングから負のサンプルを作成する。これにより、人気の高いアイテムがサンプルとして使用される可能性が高くなる。人気度の高いアイテムが正でない場合、ユーザーは既に負としてそのアイテムを認知している可能性が高いので、この方法を使用する。

  • 損失関数

We found that pointwise ranking was unstable with this network (see Section 4 for more comments). Pairwise ranking losses on the other hand performed well.

ペアワイズの手法を用いたランキング損失(Bayesian Personalized Ranking, TOP1)を使用する

  • パラメータ探索

We optimized the hyperparameters by running 100 experiments at randomly selected points of the parameter space for each dataset and loss function. The best parametrization was further tuned by individually optimizing each parameter. The number of hidden units was set to 100 in all cases.

隠れ層の数は100層に設定する。

The best performing parametrizations are summarized in table 2. Weight matrices were initialized by random numbers drawn uniformly from [−x, x] where x depends on the number of rows and columns of the matrix. We experimented with both rmsprop (Dauphin et al., 2015) and adagrad (Duchi et al., 2011). We found adagrad to give better results.

重みの値を[−x,x] の範囲内の一様分布からランダムに選んで初期化する。rmspropとadagradではadagradの方が精度が高い。

実装

  1. データをミニバッチ用に変換する
  2. GRU4RECのモデルを定義する
  3. 最適化手法を定義する
  4. 損失関数を定義する

1. データをミニバッチ用に変換する

  • エンコーディング
def onehot_encode(self, input):
    self.onehot_buffer.zero_()
    index = input.view(-1, 1)
    one_hot = self.onehot_buffer.scatter_(1, index, 1)
    return one_hot
  • ミニバッチ学習
class DataLoader():
    def __init__(self, dataset, batch_size=50, isTrain=True):
        ...

    def __iter__(self):
        while not finished:
            minlen = (end - start).min()
            idx_target = df.item_idx.values[start]

            for i in range(minlen - 1):
                idx_input = idx_target
                idx_target = df.item_idx.values[start + i + 1]
                input = torch.LongTensor(idx_input)
                target = torch.LongTensor(idx_target)
		...
                    yield input, target, negative_samples, mask

            start = start + (minlen - 1)
            mask = np.arange(len(iters))[(end - start) <= 1]
	    
            for idx in mask:
                maxiter += 1
                if maxiter >= len(click_offsets) - 1:
                    finished = True
                    break
                iters[idx] = maxiter
                start[idx] = click_offsets[session_idx_arr[maxiter]]
                end[idx] = click_offsets[session_idx_arr[maxiter] + 1]
  • サンプリング戦略
    def generate_negative_samples(self, batch_items, num_samples):
        try:
            all_items = set(range(self.total_items))
            available_items = list(all_items - batch_items)
            if not available_items:
                available_items = list(all_items)
            weights = self.item_probabilities[available_items].values
            idx_negative_samples = random.choices(available_items, weights=weights, k=num_samples)
            
            return idx_negative_samples

2. GRU4RECのモデルを定義する

  • フィードフォワード層
class GRU4REC(nn.Module):
    def __init__(self, input_size, hidden_size, output_size, num_layers=1, final_act='tanh',dropout_hidden=.5, dropout_input=0, batch_size=50, embedding_dim=-1, use_cuda=False):
        super(GRU4REC, self).__init__()
	self.h2o = nn.Linear(hidden_size, output_size)
	...
	
    def forward(self, input, hidden):
	...
	logit = self.final_activation(self.h2o(output))
	...

3. 最適化手法を定義する

  • パラメータ探索
self.optimizer = optim.Adagrad(params, lr=lr, weight_decay=weight_decay)

4. 損失関数を定義する

  • 損失関数
class BPRLoss(nn.Module):
    def __init__(self):
        super(BPRLoss, self).__init__()

    def forward(self, pos_logits, neg_logits):
        pos_logits_expanded = pos_logits.unsqueeze(2)
        neg_logits_expanded = neg_logits.unsqueeze(1)

        diff = pos_logits_expanded - neg_logits_expanded
        loss = -torch.mean(F.logsigmoid(diff))
        return loss

参考

Discussion