3.2 LLM のトークン化による推薦システム

最初に: 「トークン化」には 3 つの意味がある

この節でいう LLM のトークン化は、単に文章を BPE や WordPiece で細かく分割することではない。推薦で混同しやすいので、次の 3 層を分けて考える。

  1. 言語モデル用のサブワード分割: 文章を LLM が読める ID 列へ変換する。たとえば 防水で軽量な腕時計 を複数のサブワード ID にする。
  2. 意味属性の抽出と正規化: LLM が説明文・レビュー・画像の説明から 防水軽量アウトドア向け のような、推薦で再利用できる属性を取り出す。
  3. 推薦イベントの離散トークン化: ユーザー、作品、行動、時間帯、価格帯などを、順序を持つイベント列にする。たとえば [USER_U12, PLAY_25, ITEM_481, TIME_EVENING] のように表す。

通常の LLM トークナイザーだけが「軽量」や「頑丈」という意味を自動で作るわけではない。サブワード分割は文字列をモデル入力へ変える処理であり、意味属性を作るのは LLM による抽出・要約・分類、または人手で設計した語彙である。この区別をしないと、「トークン化すれば意味理解が得られる」という誤解につながる。

以下では、3 層を組み合わせて、非構造データを推薦モデルが扱いやすい表現へ変換する方法を説明する。

なぜ従来の特徴量だけでは足りないのか

従来の推薦で使う genre=SFprice=20000user_id=U12 は有用であり、LLM を使っても不要にはならない。一方で、固定的な列だけでは次の情報を落としやすい。

  • 同じ SF でも、「宇宙探査が好き」なのか「人間ドラマとしての SF が好き」なのか
  • 同じ 価格帯=中 でも、「旅行用なので軽さを優先」なのか「故障しにくさを優先」なのか
  • 評価が 4 点でも、「配送は不満だが製品には満足」なのか「期待ほどではなかった」のか
  • 新作で行動ログがないとき、既存アイテムのどれと似ているか

たとえば、ノート PC のカタログに次の列しかないとする。

item_id category price rating
L1 laptop 180,000 4.5
L2 laptop 175,000 4.6

この表だけでは L1 と L2 の違いをモデルは分からない。しかし説明文とレビューを読むと、L1 は「1.1 kg、14 時間駆動、出張向け」、L2 は「高性能 GPU、冷却重視、ゲーム向け」かもしれない。LLM に属性を抽出させて次のような列を追加すれば、履歴と接続できる。

item_id 意味属性トークン 根拠
L1 USE_CASE_TRAVEL, WEIGHT_LIGHT, BATTERY_LONG 説明文の重量・駆動時間
L2 USE_CASE_GAMING, GPU_HIGH, COOLING_ENHANCED 説明文・レビュー

ここで初めて、「最近 USE_CASE_TRAVEL の商品を見たユーザーには L1 を上げる」という、ID をまたいだ一般化が可能になる。

コールドスタートをどう緩和するか

コールドスタートとは、新規アイテムや新規ユーザーに十分な相互作用ログがなく、協調フィルタリングが働きにくい状態である。LLM による意味属性は、この問題を完全に解決するものではないが、ID 以外の共有情報を増やす。

ワークド例として、ユーザー U12 の履歴が次の 2 件しかないとする。

閲覧: 軽量バックパック
購入: 防水トラベルポーチ

LLM またはルールで、履歴を次の共通語彙へ正規化する。

USER_INTENT_TRAVEL
PREF_LIGHTWEIGHT
PREF_WATER_RESISTANT

発売直後のバッグ B99 には購入履歴が 0 件でも、商品説明から次を得られる。

ITEM_B99
CATEGORY_BAG
USE_CASE_TRAVEL
WEIGHT_LIGHT
WATER_RESISTANT
MATERIAL_VEGAN_LEATHER

ID ベースの協調フィルタリングでは B99 は他ユーザーとの共起がなく推薦しにくい。一方、属性ベースの特徴では U12 と B99 の TRAVELLIGHTWEIGHTWATER_RESISTANT が一致するため、候補検索やランカーへ信号を渡せる。

ただし「意味が近い」ことと「購入される」ことは別である。価格、在庫、ブランドへの信頼、サイズ、地域、表示位置なども購買に効く。LLM 属性はログを置き換えるものではなく、ログが少ない領域で一般化するための補助特徴量として扱う。

全体ワークフロー

実装では、自由な LLM 出力をそのままトークンにするより、正規化済みの制御語彙に落とす方が安全である。

生データ
  ├─ 商品説明・レビュー・問い合わせ
  ├─ カテゴリ、価格、在庫などの構造化データ
  ├─ 商品画像・動画
  └─ 表示、クリック、購入、視聴などのイベント
        ↓
正規化・品質チェック
        ↓
LLM / 専用モデルによる意味属性抽出
        ↓
制御語彙・バケット・ID への正規化
        ↓
イベント列または特徴量行の生成
        ↓
候補検索モデル / ランカー / シーケンスモデル

1. スキーマと制御語彙を決める

まず、何でも自由文で出力させず、推薦に使う属性を決める。バッグなら次のようなスキーマがある。

{
  "use_cases": ["travel", "commute", "outdoor"],
  "weight_class": "light | medium | heavy",
  "water_resistance": "none | water_resistant | waterproof",
  "material": ["vegan_leather", "nylon", "leather"],
  "evidence": ["説明文中の根拠短文"]
}

軽め軽量持ち運びしやすい を別々の語彙として保存すると、同じ概念が分散する。抽出結果を WEIGHT_LIGHT のような正規トークンに寄せることで、学習データの密度と監査可能性を上げる。

2. 属性抽出の品質を確認する

LLM の出力には幻覚、曖昧な根拠、表記ゆれがある。抽出時には次を守る。

  • 入力に書かれていない属性は unknown にする
  • 属性ごとに根拠スパンを保存する
  • JSON Schema などで値域を制約する
  • 少量の人手正解セットで適合率・再現率を測る
  • モデル・プロンプト・語彙のバージョンを記録する

たとえば説明文が「小雨に対応する撥水加工」であれば、WATER_RESISTANT は妥当でも、WATERPROOF は過剰な抽出である。推薦品質以前に、属性抽出の誤りを評価しなければならない。

3. 学習用の表現を作る

抽出した属性は、主に次の 2 通りで使える。

  • 特徴量として使う: GBDT や DNN の入力列に、weight_class、ユーザーとアイテムの属性一致数などを加える。
  • トークン列として使う: Transformer に、ユーザーの行動時系列をイベント単位で入力する。

多くの実サービスでは両方を併用する。特徴量モデルは低レイテンシで安定し、シーケンスモデルは「直近で何に興味を持ったか」という順序を取り込みやすい。

イベント列へのトークン化

ユーザー uu が時刻 tt にアイテム ii へ行った行動を、次のようなイベントとして表す。

et=[USERu, ACTIONat, ITEMi, TIMEb(t), CONTEXTct]e_t = [\mathrm{USER}_u,\ \mathrm{ACTION}_{a_t},\ \mathrm{ITEM}_i,\ \mathrm{TIME}_{b(t)},\ \mathrm{CONTEXT}_{c_t}]

b(t)b(t) は時刻をバケットにする関数であり、たとえば 20:35 を TIME_EVENING に変換する。ctc_t には DEVICE_MOBILESURFACE_HOMEPRICE_MID のような文脈トークンを入れられる。各トークンを埋め込みベクトル mathbfE[]mathbf{E}[\cdot] に変換し、位置 tt の入力を足し合わせるなら、

xt=E[ITEMi]+E[ACTIONat]+E[TIMEb(t)]+E[CONTEXTct]+pt\mathbf{x}_t = \mathbf{E}[\mathrm{ITEM}_i] +\mathbf{E}[\mathrm{ACTION}_{a_t}] +\mathbf{E}[\mathrm{TIME}_{b(t)}] +\mathbf{E}[\mathrm{CONTEXT}_{c_t}] +\mathbf{p}_t

となる。pt\mathbf{p}_t は「これは何番目の行動か」を表す位置埋め込みである。Transformer は x1,,xT\mathbf{x}_1,\ldots,\mathbf{x}_T を見て、次の行動や次に興味を持ちそうなアイテムを予測する。

ワークド例: ある動画サービスで、夜に SF 映画を長く視聴し、翌日にその監督の別作品を検索したとする。

[ITEM_INTERSTELLAR, PLAY_75, TIME_EVENING, DEVICE_TV]
[ITEM_INTERSTELLAR, SEARCH_DIRECTOR_NOLAN, TIME_MORNING, DEVICE_MOBILE]

作品 ID だけでなく、GENRE_SFTHEME_SPACE, CREATOR_NOLAN をアイテムの補助トークンとして付ければ、未視聴の新作でも「SF」「宇宙」「ノーラン系」といった共有概念で候補にできる。ただし、1 イベントにすべての属性を無制限に連結すると系列長が膨らむ。よく使う属性だけをトークン列に入れ、残りはランカーの特徴量や item embedding に回す設計が実用的である。

テキストデータ: サブワードと意味トークンの役割

サブワードトークン化

商品説明文を LLM に読ませるには、モデル固有のトークナイザーを使う。日本語では形態素境界と一致しない分割も起こるため、耐久性のあるノートパソコン が「意味単位そのもの」の列になるとは限らない。BPE や WordPiece の目的は、未知語を含む文字列を有限の語彙 ID へ変換することにある。

したがって、lap##top のような例は英語の WordPiece の分割例であって、日本語の製品属性を表す推奨トークンではない。日本語の推薦では、説明文をそのまま埋め込みモデルへ与えるか、LLM に属性を抽出させて正規語彙にする方が、下流で解釈しやすいことが多い。

意味トークンへの変換

次の説明文を考える。

14 インチ、1.1 kg。MIL 規格に準拠し、出張時にも持ち運びやすい。

生テキストを埋め込みモデルに渡す方法に加え、LLM から次のような構造を得られる。

SCREEN_14_INCH
WEIGHT_LIGHT
DURABILITY_HIGH
USE_CASE_TRAVEL

前者は細かな文章の意味を保ちやすく、後者は集計・ルール・説明に強い。両者を競合させるのではなく、text_embedding_similarityWEIGHT_LIGHT のように併用するのが現実的である。

レビューにも同じ考え方を使える。軽いが、キーボードは打ちにくい を単純な正負感情 1 個へ潰すのではなく、次のように属性ごとに分ける。

ASPECT_WEIGHT: positive
ASPECT_KEYBOARD: negative

これにより、軽さを重視するユーザーには正の信号を使い、キーボード品質を重視するユーザーには注意を促せる。

カテゴリカル特徴量

カテゴリ値は、固有 ID と階層の両方を残すとよい。たとえばゲーミングノート PC なら、

ITEM_L2
CATEGORY_ELECTRONICS
CATEGORY_LAPTOP
CATEGORY_GAMING_LAPTOP
BRAND_X

という複数のトークンを持たせる。ITEM_L2 は個別アイテムの情報を、階層トークンは兄弟カテゴリ間で共有される情報を持つ。

新規アイテム L3 は ITEM_L3 の埋め込みが未学習でも、CATEGORY_GAMING_LAPTOPBRAND_X の埋め込みは既存データから学習済みである。これが階層トークンによるコールドスタート緩和の具体的な仕組みである。

一方、ユーザー ID をそのまま LLM の一般語彙に追加しても、ID に言語的な意味はない。大量の ID を扱う場合は、推薦専用の埋め込みテーブル、ハッシュ、または ID を item encoder で表す二塔モデルの方が、汎用 LLM の語彙を増やすより扱いやすいことが多い。ID トークンは「系列内で共起を学ぶための記号」であり、「言語として意味を理解する単語」ではない。

数値特徴量

数値を文字列にするだけでは、大小関係を必ずしもモデルに伝えられない。PRICE_19999PRICE_20000 は文字列としては別の記号であり、近い値だと保証されない。そのため、用途に応じて次を選ぶ。

バケット化

価格 pp を対数スケールのバケットにする例は次の通りである。

b(p)=log2(p1000)b(p)=\left\lfloor \log_2\left(\frac{p}{1000}\right)\right\rfloor

たとえば p=8,000p=8{,}000 円なら b(p)=3b(p)=3p=16,000p=16{,}000 円なら b(p)=4b(p)=4 となる。このバケットを PRICE_BUCKET_3PRICE_BUCKET_4 として扱う。価格分布が裾の長い EC では、等間隔バケットより対数バケットや分位点バケットの方がデータを分散させにくい。

ワークド例: バッグ価格が 2,980、3,200、29,800、32,000 円に集中しているなら、LOWMIDHIGH の 3 区分だけでは 2,980 と 3,200 の差も、29,800 と 32,000 の差も同じように失われる。分位点で 10 バケットにする、または PRICE_LOG を連続値特徴としてランカーに渡す、といった選択が必要である。

連続値の直接入力

GBDT や DNN のランカーでは、正規化した連続値をそのまま入力する方がよい場合が多い。たとえば、

zprice=log(1+p)μσz_{\mathrm{price}}=\frac{\log(1+p)-\mu}{\sigma}

のように標準化して使う。トークン列には PRICE_BUCKET_MID、ランカーには zpricez_{\mathrm{price}} の両方を渡せば、「順序のパターン」と「細かな価格差」を両立できる。

時間にも同様の考え方を使う。TIME_EVENING は夜に視聴する傾向を表すが、長期休暇、曜日、直近行動からの経過時間は別の意味を持つ。DAY_OF_WEEK_SATGAP_LT_1HSESSION_3RD_ITEM のように、仮説に応じて離散化する。

マルチモーダルデータ

画像や動画を「1 個の IMG_EMBED トークン」と書くのは概念図としてはよいが、実装ではもう少し選択肢がある。

  • 画像エンコーダの埋め込みを特徴量として使う: ViT などで画像をベクトルへ変え、候補検索やランカーの入力にする。
  • 画像パッチ列をマルチモーダル LLM に渡す: 見た目の詳細な比較が必要な少数候補の再ランキングで使う。
  • 画像を文章・属性へ変換する: COLOR_BLACKSTYLE_MINIMALHAS_SHOULDER_STRAP のように制御語彙へ落とし、表形式モデルにも渡す。

EC のバッグ画像なら、画像だけでは「撥水性」は確定できないが、色・形・ポケット数のような視覚的属性は補助信号になる。テキストに 防水 とある一方、画像では大きなロゴが目立つ場合、どちらをどの目的に使うかを分ける。仕様は商品マスタ、見た目は画像、実使用感はレビュー、と情報源ごとの信頼度を保つことが重要である。

CLIP のような対照学習済みモデルを用いると、画像ベクトル vimg\mathbf{v}_{\mathrm{img}} とテキストベクトル vtext\mathbf{v}_{\mathrm{text}} を比較できる。候補の見た目とユーザーが検索した 黒いミニマルな防水バッグ の近さは、たとえば cosine 類似度で表せる。

sim(q,i)=vqTvivqvi\operatorname{sim}(q,i) = \frac{\mathbf{v}_q^{\mathsf{T}}\mathbf{v}_i} {\|\mathbf{v}_q\|\|\mathbf{v}_i\|}

この値が高いことは「見た目・文章上の意味が近い」ことを表すが、購入確率そのものではない。最終ランカーでは価格、配送、在庫、ユーザー履歴と組み合わせる。

何を予測するのか

トークン列を作っても、目的関数を決めなければ推薦器にはならない。代表的なタスクは次の通りである。

タスク 入力 正解
次アイテム予測 過去のイベント列 次にクリック・視聴・購入したアイテム
次行動予測 過去のイベント列 CLICKPURCHASE、離脱など
マスク復元 一部を隠したイベント列 隠したアイテム・属性
ペアワイズ順位学習 ユーザー、正例、負例 正例を上位に置く

次アイテム予測なら、履歴 hth_t に対する次アイテム it+1i_{t+1} の確率を softmax で表せる。

P(it+1=iht)=exp(ztTei)jIexp(ztTej)P(i_{t+1}=i\mid h_t) = \frac{\exp(\mathbf{z}_t^{\mathsf{T}}\mathbf{e}_i)} {\sum_{j\in\mathcal{I}}\exp(\mathbf{z}_t^{\mathsf{T}}\mathbf{e}_j)}

zt\mathbf{z}_t は Transformer が履歴から作ったユーザー状態、ei\mathbf{e}_i はアイテム表現、I\mathcal{I} は全アイテム集合である。全カタログが数百万件あると分母の総和は重いため、実務ではサンプルド softmax、候補集合上の softmax、二塔検索などを使う。

ワークド例: ユーザーが「SF 映画を週末の夜に 75% 視聴」「同じ監督を検索」という履歴を持つとき、モデルは次アイテムの候補として CREATOR_NOLANGENRE_SFTIME_EVENING と相性のよい作品に高い内積を与える。ここで新作が同じ属性を持っていれば、過去にその新作自体を見たユーザーがいなくても候補に入り得る。

実務での推奨構成

すべてを 1 つの LLM に任せる必要はない。現実的には次の役割分担が扱いやすい。

  1. オフライン属性生成: LLM や画像モデルで、説明文・レビュー・画像から正規化属性と embedding を作る。
  2. 候補検索: ID 共起、意味 embedding、人気、新着、ビジネスルールの複数経路から数百〜数千件を集める。
  3. ランカー: 属性一致、価格、鮮度、在庫、ユーザー履歴、候補検索スコアを使い、クリック・視聴・購入確率を予測する。
  4. 再ランキング: 重複、在庫切れ、年齢制約、露出方針、多様性を調整する。

この構成なら、LLM のコストが高い意味抽出はアイテム更新時にオフラインで済ませられる。リクエスト時には、保存済みの属性・embedding と高速なランカーを使う。自由文プロンプトで全カタログを毎回並べる方式より、レイテンシ、再現性、監査性を管理しやすい。

よくある失敗

自由に生成した属性をそのまま語彙へ追加する

軽い軽量持ち運びに便利 がすべて別トークンになると、データが分散する。制御語彙、同義語辞書、根拠保存を用意し、未知の表現はレビュー後に語彙へ追加する。

属性を事実と推測で混ぜる

「1.1 kg」は商品仕様だが、「学生向け」は推測である。事実属性、モデル推定属性、ユーザー生成属性を分離し、根拠・信頼度・作成元を記録する。安全や規制に関わる属性は、LLM の推測だけで確定しない。

時間情報のリーク

購入後に書かれたレビューや、将来の人気度を、購入時点の特徴量として学習へ入れてはいけない。特徴量には available_at を持たせ、各イベント時点で取得可能だった情報だけを使う。

トークンを増やしすぎる

アイテムごとの固有語、全レビュー文、すべての画像パッチを 1 系列へ入れると、系列長・語彙・計算量が爆発する。候補検索は圧縮 embedding、ランカーは選択済み特徴量、LLM 再ランキングは上位少数件、というように役割を分ける。

オフラインの意味評価だけで採用する

属性抽出の F1 や embedding の類似度が高くても、推薦のクリック・視聴・購入が改善するとは限らない。少なくとも「既存特徴量のみ」と「LLM 属性を追加」の条件で、時間分割したオフライン評価とオンライン A/B テストを比較する。

まとめ

LLM を使った推薦のトークン化は、文字列をサブワードに分割するだけではない。非構造データから意味属性を抽出して制御語彙へ正規化し、ID・行動・時間・文脈とともにイベント列または特徴量へ変換する一連の設計である。

この表現は、新規アイテムを既存の概念へ接続し、レビューや説明文のニュアンスを取り込み、直近の行動文脈をシーケンスモデルに渡せる。ただし、意味属性は行動ログ・在庫・価格・表示位置を置き換えない。抽出品質、時間リーク、語彙の制御、オフラインとオンラインの因果的な評価まで含めて設計して初めて、推薦品質の改善につながる。