ゲストジャーニーデータに注目する理由
Airbnbで旅行を計画するプロセスは、1回のセッションで完結しません。サンフランシスコの宿泊先を探すゲストは、数日間にわたって数十件のリスティングを閲覧し、過去数年間の予約・レビュー・キャンセル履歴も蓄積されています。こうしたデータは、ゲストがどのような宿泊先を好むかを強く示唆します。
従来は、このような履歴を手動フィーチャー(例: 総予約数、平均価格)として集計し、検索ランキングに反映していました。しかし、フィーチャーが数百件に増えるにつれて、拡張性と表現力に限界が生じ、AirbnbはTransformerベースのシーケンスモデルを導入してゲストの全旅程をエンコードする方向へ舵を切りました。
本記事では、Airbnbエンジニアリングブログで公開されたゲストジャーニー学習による検索パーソナライズの事例をもとに、シーケンスモデリング設計の要点と実務適用時の考慮点を整理します。

中核設計: 長期シーケンスと短期シーケンスの分離
Airbnbが直面した最初の難関は、ゲストあたりのイベント数が数十万件に達するほど膨大であることでした。すべてのイベントを単一のシーケンスとしてモデル化することは、計算上ほぼ不可能です。
解決策として、ゲストジャーニーを2つの系列に分割しました。
- 長期シーケンス: 過去7年間の予約・レビュー・キャンセルなど、情報量は少ないが重要なイベントを最大80件まで
- 短期シーケンス: 直近21日間のリスティング閲覧イベントを最大200件まで
この分離により、モデルはゲストの深い予約履歴と最近のブラウジングコンテキストを同時に学習できます。イベントは、カーディナリティの高いリスティングIDやホストIDのための埋め込みテーブルと、階層的な地理IDを共有するフィーチャープールでエンコードされます。
学習・推論の効率化: 4倍改善の秘訣
Airbnbは3つの戦略により、学習スループットを約4倍向上させました。
-
検索バッチ処理: エンコーダーは因果マスクを使用するため、イベントシーケンス全体を一度だけ実行すれば、各検索時点の中間埋め込みを取得できます。複数の検索が単一のエンコーダーパスを共有します。
-
シーケンス長バケット化: パディングの無駄を減らすため、シーケンスを長さごとにグループ化して処理します。
-
パディングされた検索の排除: ランキングモデルでパディングされた検索を計算しないよう、スパース演算を適用します。
推論段階では、推論を2つの段階に分離しました。シーケンスエンコーダーは日次バッチジョブとして実行されゲスト埋め込みを生成し、リアルタイム検索時にはこの埋め込みを取得してランキングモデルに結合します。この設計により、低レイテンシを維持しながらゲストの全履歴を反映できます。
コードで見る中核コンセプト
実際のAirbnbコードは公開されていませんが、上記の設計をPythonで概念的に実装すると次のようになります。
import torch
import torch.nn as nn
class GuestSequenceEncoder(nn.Module):
"""
ゲストジャーニーシーケンスをエンコードするTransformerモデル
長期・短期シーケンスを分離して入力し、検索時点の埋め込みを返す。
"""
def __init__(self, d_model=128, nhead=8, num_layers=6):
super().__init__()
self.embedding = nn.Embedding(num_embeddings=10000, embedding_dim=d_model)
self.positional_encoding = nn.Embedding(num_embeddings=512, embedding_dim=d_model)
encoder_layer = nn.TransformerEncoderLayer(d_model=d_model, nhead=nhead)
self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)
self.output_proj = nn.Linear(d_model, d_model)
def forward(self, event_ids, event_times, search_positions):
# イベントIDを埋め込み、位置エンコーディングを追加
seq_len = event_ids.size(1)
positions = torch.arange(seq_len, device=event_ids.device).unsqueeze(0)
x = self.embedding(event_ids) + self.positional_encoding(positions)
# Transformerエンコーダーを通過(因果マスクを適用)
mask = torch.triu(torch.ones(seq_len, seq_len) * float('-inf'), diagonal=1)
output = self.transformer(x, mask=mask)
# 検索時点の埋め込みを抽出
search_embeddings = output[:, search_positions, :] # [batch, num_searches, d_model]
return self.output_proj(search_embeddings)
この例は、長期・短期シーケンスを1つにまとめて処理する簡略版ですが、因果マスクによる検索時点埋め込みの共有という中核アイデアを示しています。

注意点と深掘り分析
1. データのスパース性とノイズ
Airbnbはソーシャルメディアと異なり、エンゲージメントではなく予約コンバージョンに最適化されています。予約イベントは稀で慎重に発生する一方、リスティング閲覧は単なる暇つぶしの可能性もあります。このようなスパースでノイズの多いシグナルから一般化するには、シーケンス長キャッピング(80/200)のようなドメイン知識が不可欠です。
2. パディングとコスト最適化
シーケンスモデルは本質的に計算コストが高くなります。Airbnbは長さバケット化とスパース演算によりパディングの無駄を削減しましたが、これはインフラレベルの最適化が伴って初めて効果を発揮します。単にモデルを変更しただけでは学習は高速化しません。
3. オフライン指標とオンラインA/Bテストのギャップ
AirbnbはオフラインNDCG改善(+3.78%)がオンラインのビジネス指標(予約者数、閲覧数)につながることをA/Bテストで確認しました。これは、オフライン評価がオンライン成果と一致するよう慎重な実験設計が必要であることを示唆しています。
日本市場での適用文脈
国内のECサイトやOTA(オンライン旅行代理店)でも、同様のシーケンスモデリングを導入する事例が増えています。ただし、トラフィック規模が比較的小さい場合は、Airbnbのような複雑な長期・短期シーケンス分離をしなくても、セッションベースのアテンションやシンプルなLSTMで十分な効果が得られることがあります。インフラコストとデータのスパース性を考慮し、段階的にアプローチするのが良いでしょう。
また、レコメンドシステムのパイプライン最適化に関心があれば、NetflixがJDKベクトルAPIでレコメンドシステムを最適化した事例もご参照ください。
![]()
結論: シーケンスモデリングがもたらす変化
Airbnbは手動フィーチャー集計から学習されたシーケンス表現への移行により、ゲストが過去に何をしたかだけでなく、今何を探しているのかについての深い理解を得ました。このシグナルは検索ランキングだけでなくプロモーションメールにも一般化され、+5.04%のクリック率向上を達成しました。
この事例から得られる実務的な教訓は次の通りです。
- シーケンスデータを闇雲にモデル化せず、ドメイン知識で長期・短期に分離する
- 因果マスクを活用すれば、複数の検索時点を1つのパスで処理できる
- 推論レイテンシを低く保つため、バッチ埋め込みとリアルタイム取得を分離する
次のステップとしての学習方向
- Transformerの因果マスクとマルチヘッドアテンションの数学的背景を深く学ぶ
- レコメンドシステムにおける
setwise rankingとpointwise rankingの違いを理解する - 実際のユーザー行動ログをシーケンスデータに変換するETLパイプライン設計の演習
Airbnbの取り組みを通じて、単にモデルを変えるのではなく、データをどのように捉えるかについての洞察を得られたなら幸いです。