RankNetの再開発した際に,気づいた注意すべき点についてまとめます. 尚,RankNetは"From RankNet to LambdaRank to LambdaMART: An Overview"[1]に基づきます.
まず,RankNetがどのようなものかここで紹介します.
訓練データはクエリによって分割されている.RankNetは入力の特徴ベクトルをにマップします.
クエリが与えられた時,urlのペアとの特徴ベクトル, はモデルにより,スコアは計算されます.
はがよりクエリと関連度が高いことを意味します.モデルの2つの出力は,シグモイド関数によってがよりも上位にランク付けされるべきであるという学習済み確率にマッピングされます.
2つの出力はシグモイド関数によってにマッピングされる.が真の確率分布に近づくように交差エントロピーコスト関数を適用し学習します.コスト関数は以下の通り,
クエリが与えられた時,,の場合, の場合,の場合はと定義します.そうすると,となり,コスト関数は,
となる.このコスト関数は対称的(との符号変えたら不変)であり,の時.
の時,
と表されます.また,の時はです.したがって,
と表され,重み(モデルのパラメータ)は,SGDでコスト関数を最小化することで更新される.
ここでは正の学習係数.
RankNetは,クエリについてurlのペアについて処理を行うが,実際にペア一つずつ計算すると計算量が膨大になってしまいます.計算量の問題を解決するためにバッチ処理を行います.
今回利用したデータセットMQ2007[2]は,一つのクエリについて,約40個の文書があります.40個の文書には文書のから2個の文書ペアを選択する組み合わせは780通り存在します.この780通り全て計算すると計算量が膨大になるので,バッチ処理を行います.
コスト関数を求めるためにを求める必要があるので,まず40個の文書について全てのスコアを計算します.計算できたら,の行列を作ります.
この行列は重複を含むための上三角部分を抽出します.
s_batch = self.model(batch_ranking) #スコア計算
pred_diff = s_batch - s_batch.view(1, -1) #s_i - s_j 行列
#対角成分削除
row_inds, col_inds = np.triu_indices(batch_ranking.size()[0], k=1)
si_sj = pred_diff[row_inds, col_inds] #上三角s_i - s_j 行列
si_sjを用いてコスト関数を計算します.
今回の実装ではPyTorchを用いました.PyTorchでモデルを定義する方法はいくつかあります.
nn.Moduleを継承して,構成要素をinitに定義して順方向をforwardに記載します.モデルに入力を渡せば自動的にforwardを実行してくれます.
最もシンプルな方法は,
xxxxxxxxxx
class RankNet(nn.Module):
def __init__(self, input_dim, D_in, H1, H2):
super(RankNet, self).__init__()
self.l1 = nn.Linear(input_dim, D_in) #128
self.l2 = nn.Linear(D_in, H1) #128, 64
self.l3 = nn.Linear(H1, H2) #64, 32
self.l4 = nn.Linear(H2, 1) #32, 1
def forward(self, batch_ranking=None, batch_stds_labels=None, sigma=1.0):
s_batch = self.l4(F.relu(self.l3(F.relu(self.l2(F.relu(self.l1(batch_ranking)))))))
です.活性化関数もinitに書くことができます.
次にSequential.必要な処理をひたすらnn.Sequentialに順番に渡していくだけなので簡単です.
xxxxxxxxxx
class RankNet(nn.Module):
def __init__(self, input_dim, D_in, H1, H2):
super(RankNet, self).__init__()
self.model = nn.Sequential(
nn.Linear(input_dim, D_in),
nn.ReLU(),
nn.Linear(D_in, H1),
nn.ReLU(),
nn.Linear(H1, H2),
nn.ReLU(),
nn.Linear(H2, 1)
def forward(self, batch_ranking=None, batch_stds_labels=None, sigma=1.0):
s_batch = self.model(batch_ranking)
となります.
layerを先にリストにまとめておいてnn.Sequentialに渡す方法もあります.
xlayers = [nn.Linear(input_dim, D_in),
nn.ReLU(),
nn.Linear(D_in, H1),
nn.ReLU(),
nn.Linear(H1, H2),
nn.ReLU(),
nn.Linear(H2, 1)]
class RankNet(nn.Module):
def __init__(self, layers):
super(RankNet, self).__init__()
self.model = nn.Sequential(*layers)
def forward(self, batch_ranking=None, batch_stds_labels=None, sigma=1.0):
s_batch = self.model(batch_ranking)
この方法を使うとモデルの構造を変えることが容易になります.
[1] Christopher J.C. Burges . (2010) From RankNet to LambdaRank to LambdaMART: An Overview. Microsoft Research Technical Report MSR-TR-2010-82
[2] Tao Qin and Tie-Yan Liu. (2013) Introducing LETOR 4.0 datasets. arXiv:1306.2597.
間違いの指摘やコメントお待ちしています