「ニュースレコメンデーションのための大規模な言語モデルとベクトルデータベース」

「美容とファッションのエキスパートが語る、ニュースレコメンデーションのための大規模な言語モデルとベクトルデータベース」

UnsplashでRoman Kraftによる写真

センテンストランスフォーマーとキューラントを使用してLLMをプロダクション環境にする

最近のChat-GPT、Bardなどの生成AIツールのリリースにより、大規模言語モデル(LLM)は機械学習コミュニティで大きな話題となりました。これらのソリューションの中心にあるのは、テキストや画像などの非構造化データの数値表現を計算し、これらの表現間の類似性を見つけるというアイデアです。

しかし、これらの概念をプロダクション環境に取り込むには、機械学習エンジニアリングの課題があります:

  • これらの表現を速く生成する方法は?
  • 適切なデータベースにそれらを格納する方法は?
  • プロダクション環境での類似性の高速計算方法は?

本記事では、これらの質問に対する2つのオープンソースのソリューションを紹介します:

これらのツールは、学術界がレコメンデーションアルゴリズムを開発するために作成されたNPR [2]、ニュースポータル推奨データセット(Kaggleで公開されている)に適用されます。本記事の最後には、次の方法を紹介します:

  • センテンストランスフォーマーを使用してニュースの埋め込みを生成する
  • キューラントを使用して埋め込みを格納する
  • 埋め込みをクエリしてニュース記事を推奨する

本記事のすべてのコードはGithubで利用できます。

1. センテンストランスフォーマーで埋め込みを生成する

まず最初に、入力データをベクトルに変換する方法を見つける必要があります。ここではそれを埋め込みと呼びます(埋め込みの概念について詳しく知りたい場合は、Boykisの記事What Are Embeddings? [3]をおすすめします)。

では、NPRデータセットでどのようなデータを扱えるか見てみましょう:

import pandas as pddf = pd.read_parquet("articles.parquet")df.tail()
NPRからのサンプルデータ(作成者が生成した画像)

NPRには記事のタイトルや本文などの興味深いテキストデータがあります。これらを次の画像のように埋め込み生成プロセスで使用できます:

埋め込み生成プロセス(作成者の画像)

したがって、入力データからテキスト特徴を定義したら、数値表現を生成する埋め込みモデルを確立する必要があります。幸いなことに、HuggingFaceのようなWebサイトでは、特定の言語やタスクに適した事前学習済みモデルを探すことができます。この例では、ブラジルポルトガル語向けにトレーニングされたneuralmind/bert-base-portuguese-casedモデルを使用します。

  • 名前付きエンティティ認識
  • 文のテキストの類似性
  • テキストの意味関係認識

コードの面では、埋め込み生成プロセスを以下のように翻訳します:

from sentence_transformers import SentenceTransformermodel_name = "neuralmind/bert-base-portuguese-cased"encoder = SentenceTransformer(model_name_or_path=model_name)title = """  Paraguaios vão às urnas neste domingo (30) para escolher novo presidente"""sentence = titlesentence_embedding = encoder.encode(sentence)print (sentence_embedding)# output: np.array([-0.2875876, 0.0356041, 0.31462672, 0.06252239, ...])

したがって、入力データの例がある場合、タイトルとタグの内容を単一のテキストに連結してエンコーダに渡し、テキストの埋め込みを生成することができます。

NPRデータセットの他のすべての記事に対しても同じ手順を適用することができます:

def generate_item_sentence(item: pd.Series, text_columns=["title"]) -> str:    return ' '.join([item[column] for column in text_columns])df["sentence"] = df.apply(generate_item_sentence, axis=1)df["sentence_embedding"] = df["sentence"].apply(encoder.encode)

注意:このプロセスは、マシンの処理能力によっては多少時間がかかることがあります。

すべてのニュース記事の埋め込みが得られたら、それらを保存するための戦略を定義しましょう。

2. 埋め込みの保存

埋め込みを生成することはコストがかかる場合があるため、異なる戦略に基づいてクエリを実行するためにベクトルデータベースを使用することができます。

このタスクを達成するためのいくつかのベクトルデータベースソフトウェアがありますが、この記事ではオープンソースの解決策であるQdrantを使用します。このソフトウェアには、Python、Go、およびTypescriptなどの主要なプログラミング言語向けのAPIが利用可能です。これらのベクトルデータベースのより詳細な比較については、この記事 [4]を参照してください。

Qdrantの設定

すべてのQdrantの操作を扱うために、ベクトルデータベースを指すクライアントオブジェクトを作成する必要があります。Qdrantでは、データベースへのリモート接続をテストするために、無料のティアサービスを作成することができますが、簡単のためにデータベースをローカルに作成して永続化します:

from qdrant_client import QdrantClientclient = QdrantClient(path="./qdrant_data")

この接続が確立されると、データベースにニュース記事の埋め込みを保存するコレクションを作成することができます:

from qdrant_client import modelsfrom qdrant_client.http.models import Distance, VectorParamsclient.create_collection(    collection_name = "news-articles",    vectors_config = models.VectorParams(        size = encoder.get_sentence_embedding_dimension(),        distance = models.Distance.COSINE,    ),)print (client.get_collections())# output: CollectionsResponse(collections=[CollectionDescription(name='news-articles')])

ベクトル構成パラメータを使用してコレクションを作成します。これらのパラメータは、ベクトルのサイズやベクトルの比較時に使用される距離メトリックなど、ベクトルのプロパティをQdrantに伝えるものです(ここではコサイン類似度を使用しますが、他の戦略として内積またはユークリッド距離を使用することもできます)。

ベクトルポイントの生成

最後に、データベースに適切なオブジェクトをアップロードするための準備をします。Qdrantでは、PointStructクラスを使用してベクトルを格納することができます。このクラスを使用して、次のプロパティを定義できます:

  • id: ベクトルのID(NPRの場合、ニュースID)
  • vector: ベクトルを表す1次元配列(埋め込みモデルによって生成される)
  • payload: コレクション内のベクトルをクエリするために後で使用できるその他の関連メタデータを含む辞書(NPRの場合、記事のタイトル、本文、およびタグ)
from qdrant_client.http.models import PointStructmetadata_columns = df.drop(["newsId", "sentence", "sentence_embedding"], axis=1).columnsdef create_vector_point(item:pd.Series) -> PointStruct:    """ベクトルをPointStructに変換する"""    return PointStruct(        id = item["newsId"],        vector = item["sentence_embedding"].tolist(),        payload = {            field: item[field]            for field in metadata_columns            if (str(item[field]) not in ['None', 'nan'])        }    )points = df.apply(create_vector_point, axis=1).tolist()

ベクトルのアップロード

最後に、すべてのアイテムがポイント構造に変換された後、データベースにチャンクごとにアップロードすることができます:

CHUNK_SIZE = 500n_chunks = np.ceil(len(points)/CHUNK_SIZE)for i, points_chunk in enumerate(np.array_split(points, n_chunks)):    client.upsert(        collection_name="news-articles",        wait=True,        points=points_chunk.tolist()    )

3. ベクトルのクエリ

ベクトルが最終的に集合に入力されているので、データベースのクエリを開始することができます。データベースに情報を入力する方法はさまざまありますが、非常に有用な2つの入力方法があります:

  • テキストを入力する
  • ベクトルIDを入力する

3.1 入力ベクトルを使用してベクトルをクエリする

このベクトルデータベースは、検索エンジンで使用するために構築されたものとします。この場合、ユーザーの入力はテキストであり、関連するアイテムを返す必要があります。

ベクトルデータベースでは、すべての操作がベクトルを用いて行われるため、ユーザーの入力テキストをベクトルに変換する必要があります。テキストデータを埋め込みにエンコードするためにSentence Transformersを使用したので、同じエンコーダーを使用してユーザーの入力テキストの数値表現を生成することができます。

NPRにはニュース記事が含まれていると仮定し、ユーザーが「Donald Trump」と入力した場合を考えてみましょう:

query_text = "Donald Trump"query_vector = encoder.encode(query_text).tolist()print (query_vector)# 出力: [-0.048, -0.120, 0.695, ...]

入力クエリベクトルが計算されると、コレクション内の最も近いベクトルを検索し、それらのベクトルからどのような出力を取得するかを定義することができます。例えば、newsId、title、topicsなどが挙げられます:

from qdrant_client.models import Filterfrom qdrant_client.http import modelsclient.search(    collection_name="news-articles",    query_vector=query_vector,    with_payload=["newsId", "title", "topics"],    query_filter=None)

注意:デフォルトでは、Qdrantは埋め込みを高速にスキャンするために近似最近傍探索を使用しますが、完全スキャンを行い正確な最近傍探索を行うこともできます。ただし、これは非常に高コストな操作です。

この操作を実行した結果、以下の出力タイトルが生成されました(理解しやすくするために英語に翻訳されています):

  • 入力文:Donald Trump
  • 出力 1:パラグアイの選挙で新しい大統領を選ぶため、日曜日(30日)に国民が投票します
  • 出力 2:投票者はバイデンとトランプが2024年に立候補すべきでないと言います、ロイター/イプソスの世論調査が示す
  • 出力 3:作家が1990年代にトランプから性的虐待を受けたと主張
  • 出力 4:トランプ前大統領の元副大統領のマイク・ペンスが法廷で証言し、元大統領を困難にする可能性があります

トランプ自体に関連するニュースをもたらすだけでなく、埋め込みモデルは大統領選挙に関連するトピックも表現することができたようです。最初の出力では、「Donald Trump」という入力語についての直接的な言及は大統領選挙以外にはありません。

また、query_filterパラメータを省略しました。これは、出力が特定の条件を満たす必要がある場合に非常に有用なツールです。例えば、ニュースポータルでは、最近の記事のみをフィルタリングすることがよくあります(過去7日間以降のものなど)。そのため、公開日時の最小値を満たすニュース記事をクエリできます。

注意:ニュース推薦の文脈では、公正性と多様性のような、考慮すべき問題が多数あります。これは議論の対象ですが、この分野に興味がある場合は、NORMalize Workshopの記事を参照してみてください。

3.2 入力ベクトルIDを使用してベクトルをクエリする

最後に、私たちはベクトルデータベースに対して、望ましいベクトルIDに近く、望ましくないベクトルIDからは遠いアイテムを「推薦」するように依頼することができます。望ましいIDと望ましくないIDは、それぞれ正例と負例と呼ばれ、推薦のためのシードと考えられます。

例えば、以下の正例IDがあるとしましょう:

seed_id = '8bc22460-532c-449b-ad71-28dd86790ca2'#タイトル(翻訳):「ジョー・バイデンが再選に立候補を発表した理由を学ぶ」

この例に類似したアイテムを求めることができます:

client.recommend(    collection_name="news-articles",    positive=[seed_id],    negative=None,    with_payload=["newsId", "title", "topics"])

この操作を実行すると、以下が翻訳された出力タイトルです:

  • 入力アイテム:ジョー・バイデンが再選に立候補した理由を学ぶ
  • 出力1:バイデンが再選に立候補すると発表
  • 出力2:米国:バイデンが再選に立候補するための4つの理由
  • 出力3:有権者はバイデンとトランプが2024年に立候補すべきでないと考えている、ロイター/イプソスの世論調査結果
  • 出力4:再選後の可能性について疑問を投げかけたバイデンの顧問の失言

結論

この記事では、LLMとベクトルデータベースを組み合わせて推薦を行う方法を示しています。特に、文ベースの変換器を使用して、NPRデータセットのテキストニュース記事から数値表現(埋め込み)を生成するためにSentence Transformersが使用されました。これらの埋め込みが計算されると、Qdrantのようなベクトルデータベースにベクトルを基にしたクエリが可能となります。

この記事の例の後、以下のような改善ができます:

  • 他の埋め込みモデルのテスト
  • 他の距離尺度のテスト
  • 他のベクトルデータベースのテスト
  • パフォーマンス向上のためにGoのようなコンパイルベースのプログラミング言語の使用
  • 推薦を提供するAPIの作成

言い換えれば、LLMを用いた推薦の機械学習エンジニアリングを改善するための多くのアイデアが出てくるかもしれません。ですので、これらの改善についてのアイデアをお持ちの場合は、ぜひこちらにメッセージをお送りください 🙂

私について

私はブラジルのメディアテック企業であるGloboのシニアデータサイエンティストです。私は会社の推薦チームで働いており、G1GEGloboplayなどのデジタル製品を通じて数百万人のユーザーにパーソナライズされたコンテンツを提供するために多くの努力をする素晴らしい才能あるチームに囲まれています。この記事は、彼らの欠かせない知識なしでは実現できませんでした。

参考文献

[1] N. reimers and I. Gurevych, Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks (2019), Association for Computational Linguistics.

[2] J. Pinho, J. Silva and L. Figueiredo, NPR: a News Portal Recommendations dataset (2023), ACM Conference on Recommender Systems

[3] V. Boykis, What are embeddings?, personal blog

[4] M. Ali, The Top 5 Vector Databases (2023), DataCamp blog

We will continue to update VoAGI; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more