関連記事の処理を OpenAI の Embedding に切り替えてみた

これまでの関連記事の処理

エビスコムのサイトでは、著者Note の記事に関しては関連記事を表示しています。これまでの処理の流れはこんな感じです。

  1. WordPress から記事を取得
  2. 形態素解析で分かち書き&キーワードの抽出
  3. 形態素解析の結果を BM25Vectorizer に渡してベクトル化
  4. similarity(コサイン類似度)による比較で類似記事の抽出

類似記事の抽出の際に、記事の鮮度を考慮して調整しています。それなりに満足できるレベルの関連記事が抽出できていたと思います。(詳しくはこちらの記事にまとめています:『Jamstack - GatsbyJS & WordPressで関連記事を表示させる』)

OpenAI の Embedding

世の中の流れに応じて、OpenAI の API で遊び始めました。ChatGPT と DALL-E の API は Next.js を使って自前の環境を作ったりして便利に使っています。

ただ、それ以外にも気になったものがありました。それが、Embedding のモデルです。

OpenAI の Embedding のモデル
https://openai.com/blog/new-and-improved-embedding-model

このモデルを使うことで、文章をベクトル化してくれます。形態素解析などの処理も必要なく、テキストを渡すだけでシンプルにベクトル化してくれるんです。

大規模言語モデルによる Embedding もいろいろと公開されていますが、実際に自分で試してみようと思うとハードルが高くて手を出せませんでした。しかし、OpenAI の Embedding のモデルは ChatGPT や DALL-E と同じように使えますので、JavaScript でも簡単に使えます。

そこで、さっそく組み込んで試してみました。

使い方はシンプル

使い方はシンプルです。コンテンツをテキストにしたうえで、API へ渡してやるだけです。

import { Configuration, OpenAIApi } from 'openai';
const textContent = 'コンテンツをここに';
const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);
const response = await openai.createEmbedding({
model: 'text-embedding-ada-002',
input: textContent,
});
const vector = response.data.data[0].embedding;

これで、コンテンツが 1536 次元のベクトルになって返ってきます。

そこで、各記事のベクトルをコサイン類似度を使って比較します。wink-nlp のパッケージを使えば、簡単です。

wink-nlp
https://winkjs.org/wink-nlp/similarity.html

import similarity from 'wink-nlp/utilities/similarity.js';
const vectorSimilarity = similarity.bow.cosine(vectorA, vectorB);

各記事の組み合わせでコサイン類似度を計算し、それをもとに関連記事を抽出します。類似度は 0〜1 の間の数値として返ってきますので、ソートするだけです。

非常にシンプルな構成で実現できます。

BM25Vectorizer と OpenAI Embedding の比較

エビスコムのサイトでは、各記事で上位10記事を関連記事として採用しています(その中から 5 記事を表示しています)。その処理結果はこんな感じに変化しました。

⚡️「カスケードレイヤー @layer を導入した話 -- はがしやすく、負の遺産にしない CSS」の関連記事の場合

BM25Vectorizer の処理結果:

[
"『Astro v2とTinaCMSでシンプルに作るブログサイト』について",
"『作って学ぶ Next.js/React Webサイト構築』とReactの必要性を感じるようになってきたことについて",
"『作って学ぶ WordPress ブロックテーマ』について",
"Astroのスタイリング",
"Create Block Theme プラグインを使って快適なノーコード環境でブロックテーマを作成する",
"GatsbyJSの間違った使い方?『作って学ぶ HTML&CSSモダンコーディング』のサンプル制作について",
"GatsbyJS と GutenbergのCSS(WordPress)",
"ページ内で使用したブロックのCSSのみを読み込む - WordPress 5.8",
":has()で軽くなるCSSの外側とスクロールスパイ",
"2カラムレイアウト と これからのHTML&CSS",
"モダンコーディングとXDとFigmaについて"
]

OpenAI Embedding の処理結果:

[
"『Astro v2とTinaCMSでシンプルに作るブログサイト』について",
"Astroのスタイリング",
"『作って学ぶ Next.js/React Webサイト構築』とReactの必要性を感じるようになってきたことについて",
"Gatsby・Next・WordPressから見た「いまどきのレスポンシブイメージ」",
"Astroの画像まわりを調べてみた",
"HTML&CSS コーディング・プラクティスブックで何が学べるのか?",
":has()で軽くなるCSSの外側とスクロールスパイ",
"モダンコーディングとXDとFigmaについて",
"『作って学ぶ WordPress ブロックテーマ』について",
"Flexbox(reverse)で並べた子要素の重なり順がChrome 108以降で変わっている話",
"Gatsby v4とWordPressでハマった話"
]

⚡️「すぐに用意できる WordPress のオンライン開発環境: InstaWP」の関連記事の場合

BM25Vectorizer の処理結果:

[
"Create Block Theme プラグインを使って快適なノーコード環境でブロックテーマを作成する",
"WordPress 6.2 のエディターのインターフェースまわりの変更点",
"WordPressの公式テーマ Twenty Twenty-Twoをみて思うこと",
"『作って学ぶ WordPress ブロックテーマ』について",
"GatsbyJS と GutenbergのCSS(WordPress)",
"カスケードレイヤー @layer を導入した話 -- はがしやすく、負の遺産にしないCSS",
"テーマのスタイルをブロックごとのCSSに付加して読み込む - WordPress 5.8",
"Jamstack - GatsbyJS & WordPressで関連記事を表示させる",
"『作って学ぶ Next.js/React Webサイト構築』で作成したプロジェクトでWordPressのデータを扱ってみる",
"microCMSと@imgix/gatsbyで使うGatsbyImage",
"Font Awesome 5.9以降を利用する方法",
];

OpenAI Embedding の処理結果:

[
"WordPress 6.2 のエディターのインターフェースまわりの変更点",
"Create Block Theme プラグインを使って快適なノーコード環境でブロックテーマを作成する",
"『作って学ぶ WordPress ブロックテーマ』について",
"『作って学ぶ Next.js/React Webサイト構築』で作成したプロジェクトでWordPressのデータを扱ってみる",
"Gatsby・Next・WordPressから見た「いまどきのレスポンシブイメージ」",
"Jamstack - GatsbyJS & WordPressで関連記事を表示させる",
"『Astro v2とTinaCMSでシンプルに作るブログサイト』について",
"HTML&CSS コーディング・プラクティスブックで何が学べるのか?",
"『作って学ぶ Next.js/React Webサイト構築』とReactの必要性を感じるようになってきたことについて",
"Gatsby v4とWordPressでハマった話",
"カスケードレイヤー @layer を導入した話 -- はがしやすく、負の遺産にしないCSS"
]

BM25Vectorizer での処理がそれなりに機能していたのか、大きな変化はありませんでした。ただ、細かい部分を見ていくと OpenAI の Embedding による結果の方が表面的なキーワードだけではなく中身をきちんと読んでるなというのを感じます。

この結果から、関連記事の処理は OpenAI の Embedding に切り替えることにしました。

制限とコスト

Embedding に使うモデルである、text-embedding-ada-002 に関する制限とコストを確認しておきます。

text-embedding-ada-002 のトークンの上限は 8191 となっていますので、一度に扱えるのは日本語で 8000 字程度までです。ただし、ChatGPT のように往復でトークンを消費することはありませんので、この上限いっぱいの文章を扱えます。

また、API の利用料は 1000トークンあたり $0.0004(約 0.06 円)です。
(gpt-3.5-turbo と比較すると 1/5 です)

ModelPrice per 1K tokens
gpt-3.5-turbo$0.002 / 1K tokens
Ada$0.0004 / 1K tokens

ちなみに、1つ前のポストである「WordPress 6.2 のエディターのインターフェースまわりの変更点」が 1090 トークンとして処理されています。

このあたりのコストをどう考えるかでしょう。

関連記事だけでなく、検索にも組み込みたいところですが、レスポンス速度とコストをどう納めるかが今後の課題です。