Amazon Titan Text Embeddings の埋め込み表現で、レコメンド機能やタグ付けを実装
note における 生成 AI 実装解説
Author : 漆山 和樹 (note株式会社)
近年、生成 AI が大きな注目を集めており、AWS の Amazon Bedrock を利用することで、さまざまな生成 AI モデルを活用することが可能になりました。特に Claude が注目され、活用事例も増えつつあります。
note ではユーザーが自由に画像や音声、文章などのコンテンツを投稿できるプラットフォームであり、月間の投稿数は 117 万件にも上ります。投稿されるコンテンツの多くはテキストデータであり LLM で処理することができます。しかし、公開コンテンツ数が 4259 万件もあるため、全てのコンテンツを Claude で処理するのはコストと実行時間の観点から現実的ではありません。そこで、Amazon Titan Text Embeddings を活用し、コンテンツを処理して汎用的に使い回す方法を採用しています。
本記事では、note における Amazon Titan Text Embeddings の活用事例について解説します。
Amazon Titan Text Embeddings とは
はじめに、Embedding (埋め込み表現) について簡単にご紹介します。埋め込み表現とは、画像や文章などの様々なデータを、性質が近いものが近くになるように数値に変換したデータのことです。埋め込み表現の良いところは人間がデータを見たときに解釈するのと同じように数値表現に変換してくれるところにあります。
埋め込み表現については、Nyantech であの猫のこんな写真を見つけたい に非常にわかりやすく解説されているのでそちらをご覧ください。
Amazon Titan Text Embeddings は Amazon Bedrock で提供されているモデルの一つで、テキストを埋め込み表現に変換することに特化したものです。
なぜ Amazon Titan Text Embeddings を使うのか
note には大量のテキストデータが蓄積されており、これらを効果的に活用するためには適切な処理方法が必要です。テキストデータの活用で Amazon Bedrock の活用という観点だと Claude のような LLM が注目されていますが、Amazon Titan Text Embeddings も活用の仕方を工夫することで多くのことが可能です。
埋め込み表現の利点として
- 性質が近いものが近いところに集まる
- 機械的な処理が容易な数値データ
という 2 つがあると考えています。
数値情報であれば、0.5 以上なら〇〇するなどプログラミングが容易になります。
LLM でも同じことができると思いますが、LLM の場合には出力を再度 LLM に入れて判断するということが必要で、どうしても処理が重たくなります。また結果のチェックのロジックが単なる数字よりも複雑になりがちです。
埋め込み表現として一度テキストデータを数値データに変換してしまえば、その後の処理はプログラム上で容易にできるようになりますし、自分たちで機械学習モデルを作る際にもそのデータを利用できます。
note では、Amazon Titan Text Embeddings を利用してテキスト情報を数値情報に変換し、様々な用途で再利用できるようにデータを保存しています。埋め込み表現を利用して、ある記事に似ている記事を推薦したり、投稿された記事に対して「ゲーム」などのカテゴリーに属するかを自動で判断したりしています。
また、API を呼び出すだけで簡単に埋め込み表現を生成できるため、モデルの学習やデプロイが不要で機械学習の専門的な知識がなくても利用できる点も大きな強みだと思います。
note における埋め込み表現の利用例
作成した埋め込み表現は、主に以下の用途で試行錯誤を繰り返しています。また、プラットフォームの公平性という観点から、記載した推薦ロジックについてはそのまま利用されていることを保証するものではなく、あくまで例示である点についてご留意ください。
- コンテンツの類似性を利用した推薦
- コンテンツに対するタグの付与
- 埋め込み表現ベースのユーザーの表現と推薦
1. コンテンツの類似性を利用した推薦
埋め込み表現はコンテンツの性質が似ているものが距離的に近くなる傾向にあります。その性質を利用して、あるコンテンツに似ているコンテンツを探すのにコンテンツ同士の距離を計算して、近いものをユーザーに提示するということをしています。
コンテンツの埋め込み表現の距離を計算することで、あるコンテンツに似たコンテンツを取得し、ユーザーに推薦しています。
2. コンテンツに対するタグの付与
note はユーザー投稿型のプラットフォームであり、ユーザーが自由に追加できるメタ情報としてハッシュタグがあります。ハッシュタグをそのまま活用しようとしても、表記揺れなどでそのままの利用は難しいです。例えば、ある記事にユーザーが「サッカー」や「Jリーグ」などハッシュタグを付与することはできますが、内部的にはスポーツ系の記事は「スポーツ」タグとして扱いたいなどもあると思います。
このようなケースでは内部で付与したいカテゴリーの情報についてのデータを集めて、人手で付与をして機械学習でスケールさせるというのが王道ですが、note に投稿されるコンテンツは多岐に渡るため王道のやり方だけでは不十分です。機械学習を利用するということは、タグごとにモデルを訓練して運用し続けることが不可欠でそれなりの運用負荷がかかります。またビジネスニーズに応じてタグを追加したいということもあります。
ユースケース: あるコンテンツがスポーツかどうかを判定する
- スポーツに関するコンテンツを取得
- 取得したコンテンツに似ているコンテンツを埋め込み表現の距離計算で取得
- 上記で取得したコンテンツの埋め込み表現を平均化し、スポーツの埋め込み表現として利用
- スポーツの埋め込み表現と他のコンテンツの埋め込み表現を比較し、一定以上の閾値であればスポーツタグを付与
機械学習モデルを訓練することなくコンテンツに対して任意のタグを付与するための方法の一つとして埋め込み表現が非常に役に立っています。
3. 埋め込み表現ベースのユーザーの表現と推薦
Amazon Titan Text Embeddings はあくまでもテキストデータを数値データに変換するモデルですが、発展形として閲覧履歴などを組み合わせて書き手としてユーザー表現や読み手としてのユーザー表現として利用できないかの検証も進めています。
例えば、以下のようにすることで、ユーザー表現の獲得を目指しています。
読み手の埋め込み表現生成
- 期間や読了率などで一定のフィルタリング処理をした、ユーザーの閲覧履歴を取得
- ユーザーごとに閲覧したコンテンツの埋め込み表現を平均化して、ユーザーの埋め込み表現を作成
書き手の埋め込み表現生成
- 自分が投稿したコンテンツから直近何件かを取得
- ユーザーごとに1で取得したコンテンツの埋め込み表現の平均を計算
ユーザーの埋め込み表現がコンテンツの埋め込み表現から作成できるようになると、ユーザーは note を使うにつれて以下のような恩恵が得られるようになります。
- 自分の好きなコンテンツを作っているユーザーを見つけやすくなり、フォローしたり応援したりできるようになる
- ユーザーが note を使うだけで、好きなコンテンツが投稿された直後に推薦されるようになる
また、特別な行動をとらなくても、普段使いしているだけで自分の好きなものがリアルタイムに見つかるような体験が提供できるように、ユーザーの埋め込み表現を作成するということを目指しています。
副次的なものとしては、ユーザーの性質が数値データとして表現できるようになるため、クラスター分析なども容易になったりコンテンツのタグ付けと同様にユーザーのタグ付けなどもしやすくなると期待しています。
埋め込み表現生成のためのアーキテクチャ
上記のことを実現するために、埋め込み表現の生成とタグの埋め込み表現生成とタグの付与をしているアーキテクチャについて紹介いたします。
埋め込み表現 生成アーキテクチャ
こちらは、コンテンツが投稿されてからニアリアルタイムに埋め込み表現を生成するワークフローです。
推薦で埋め込み表現を利用しようとすると、埋め込み表現同士を検索できるようにする必要があります。また、単に推薦結果を返せば良いというわけではなく、一定のフィルター条件を設ける必要があるケースもあり、フィルターをかけても高速なレスポンスが可能である必要があります。このようなユースケースを満たすために、弊社では Qdrant を AWS Marketplace 経由で契約し利用しています。
コンテンツが投稿されることをトリガーにして、以下のフローで Qdrant までコンテンツが保存されるようになっています。
- Amazon API Gateway 経由でコンテンツの埋め込み表現の生成用 AWS Lambda が起動する
- 埋め込み表現生成の AWS Lambda はコンテンツをシリアライズして、S3に保存する。ここで保存したデータは Amazon Bedrock へのリクエストが失敗した時の復旧用として利用する
- Amazon Bedrock の Titan Text Embeddings にコンテンツのデータを投げて、埋め込み表現を得る
- コンテンツの ID を PK にして、埋め込み表現を Amazon DynamoDB に保存する。
- Amazon DynamoDB の DynamoDB Streams を利用してQdrant に埋め込み表現を保存するための AWS Lambda を起動する
- Qdrant 保存用の AWS Lambda が Qdrant にコンテンツの埋め込み表現を保存する。
5. で一度 Amazon DynamoDB に保存していますが、リアルタイムな推薦をする際に高速に埋め込み表現のデータを取得するためです。
また、コンテンツ以外の埋め込み表現、例えば読み手や書き手などのユーザーの埋め込み表現を合わせると、膨大な数のデータになります。これらを高速に読み書きでき、かつスケールさせるとことを考えると Amazon DynamoDB に保存しておき、リアルタイムな推薦ではここからデータを読み込むのが一番よさそうと現時点では想定し、試行錯誤している段階です。
タグの埋め込み表現生成とタグ付与のアーキテクチャ
ここでは二つのワークフローが非同期で動いています。
- タグの埋め込み表現生成ワークフロー (図中緑枠)
- コンテンツにタグ付けを行うワークフロー (図中青枠)
この 2 つのワークフローはどちらも Netflix の OSS である Metaflow を利用しています。Metaflow は AWS との親和性が高く、Python で記載したワークフローを Amazon Step Functions 上に簡単にデプロイすることができます。AWS Step Functions で実行される各ステップは AWS Batch にジョブとして投げられています。
弊社では Snowflake をデータウェアハウスに格納していて、多くのデータが Snowflake に集約されています。コンテンツの埋め込み表現も Snowflake 上に貯まるようになっており、統一管理のために Snowflake にタグの埋め込み表現も格納するようにしています。
また、Snowflake にタグの埋め込み表現とコンテンツの埋め込み表現を保存しているので、全てのコンテンツデータでやり直す必要があったりワークフローが失敗していて後で復旧が必要になるようなケースを想定して、Snowflake の UDF を利用して後からタグの付与ができるようにしています。
タグの埋め込み表現の生成
このワークフロー (図中緑枠) では以下のことを行なっています
- 以下のことを並行で行う
- あるタグ (スポーツなど) に該当しそうなコンテンツを取得し、取得したコンテンツの埋め込み表現を計算して、タグとしての埋め込み表現を生成する
- タグを付与の自動化のための検証用のデータセットの取得する
- そのタグを付与するための閾値を計算する
- タグを付与する閾値をハイパーパラメータの最適化ライブラリの optuna と、sklearn で定義しているメトリクス関数を利用してタグとして判定する閾値を決定する
- 作成したタグの埋め込み表現と閾値を Snowflake に格納する
コンテンツに対して実際にタグを付与するためには、タグの埋め込み表現とコンテンツの埋め込み表現で距離や類似性を計算する必要があります。現行のフローではコサイン類似度をタグ判定のための計算に利用しており、一定以上であれば
図や言葉だけだとイメージがつきにくいと思うので、参考までに metaflow のコードで表現してみます。
以下のコード中の GenerateTagEmbedding クラスがワークフローを表していて、AWS Step Functions で管理されます。@batch, @step がデコレータとして利用されているメソッドが各処理になっていて、それぞれ独立した AWS Batch のバッチジョブとして実行されます。バッチジョブ間のデータの転送は Metaflow 側で管理されています。
from metaflow import FlowSpec, batch, step
class GenerateTagEmbedding(FlowSpec):
@batch
@step
def start(self):
"""
ワークフローの開始
"""
# 並行で実行する
self.next(self.get_validation_dataset, self.get_candidate_contents)
@batch
@step
def get_validation_dataset(self):
"""
検証用のデータセットを作成する
"""
import get_valid_data_from_snowflake
self.validation_df = get_valid_data_from_snowflake()
self.next(self.join)
@batch(memory=32000)
@step
def get_candidate_contents(self):
"""
事前にSnowflakeに保存してあった、タグとして該当しそうなコンテンツをSnowflakeから取得する。
"""
import get_candidate_contents
self.candidate_contents = get_candidate_contents()
self.next(self.calc_tag_embedding)
@batch(cpu=16, memory=32000)
@step
def calc_tag_embedding(self):
"""
タグとして該当しそうなコンテンツの埋め込み表現から、タグの埋め込み表現を計算する
"""
import calc_embedding
self.tag_embedding = calc_embedding(self.candidate_contents)
self.next(self.determine_threshold)
@batch
@step
def determine_threshold(self, inputs):
"""
タグの埋め込み表現と検証用のデータセットから、タグ判定をするための閾値を決定する。
タグの埋め込み表現とコンテンツの埋め込み表現を比較して閾値を超えていたら、タグが付与される
"""
import optimize_threshold
# 並行実行時にインスタンス変数で格納したものがMetaflow上だと参照できないので、参照できるようにする
self.merge_artifacts(inputs)
# 内部でコサイン類似度を計算して、評価関数にf1score、閾値を最適化するのにoptunaを利用する。
self.threshold = optimize_threshold(self.tag_embedding, self.validaiton_df)
self.next(self.load_snowflake)
@batch
@step
def load_snowflake(self):
"""
閾値とタグの埋め込み表現をSnowflakeに格納する
"""
import load_snowflake
load_snowflake(self.tag_embedding, self.threshold)
self.next(self.end)
@batch
@step
def end(self):
print("Done!")
if __name__ == "__main__":
GenerateTagEmbedding()
まとめ
本稿では、note における Amazon Titan Text Embeddings の活用事例を紹介しました。埋め込み表現を活用することで、推薦においては A/B テストで 20% 程度の CTR の改善が可能という示唆が得られました。また、コンテンツに対してタグを付与するということが以前に比べて高速に行えるようになったため、特定のコンテンツを見つけたいという社内のビジネスニーズを素早く満たせるようになりました。特定のコンテンツを見つけるというタスクにおいては、社内で定性的な評価で非常に満足できるという結果を得ており、このタグ付けシステムを拡張していく予定です。
Amazon Titan Text Embeddings は Amazon Bedrock の API を利用するだけで利用することができ、機械学習の専門的な知識がなくても事前学習不要で導入しやすいのが特徴です。また、埋め込み表現を基盤データとして活用することで、自前でモデルを学習することも含めて、汎用的な用途で利用可能です。
note では、埋め込み表現を一つの基盤として活用するために、推薦システムのアーキテクチャやアルゴリズムも改修しています。今後も埋め込み表現を活用し、さらなる可能性を探っていきます。
筆者プロフィール
漆山 和樹 (Urushiyama Kazuki)
note株式会社 開発/エンジニアリング1
好きなAWS ServiceはAmazon Bedrockの機械学習エンジニアで推薦周りの技術を開発。
アイコンはAmazon Titan Image Generator製。
趣味は着物とシーシャ。
AWS を無料でお試しいただけます