Slide 1

Slide 1 text

スマートに「関連する記事」を表⽰する仕組みを作る OpenAI Embedding API + Supabase + pgvectorを利⽤した類似度検索の実装 2024-05-02 DS集会@VRChat 越境(みちのそら) / @contradiction29

Slide 2

Slide 2 text

この世界はWebサイトであふれている ● 例 ○ 旦那デスノート (https://danna-shine.com/) ○ ガールズちゃんねる (https://girlschannel.net/) ○ 発⾔⼩町 (https://komachi.yomiuri.co.jp/) ● どんなWebサイトであろうと、Webサイト管理者がやりたいことは本質的に 不変である。それは...

Slide 3

Slide 3 text

ユーザーエンゲージメントを⾼めたい

Slide 4

Slide 4 text

Webサイト運営者はユーザーエンゲージメントを⾼めたい ● 「ユーザーエンゲージメント」 ○ ユーザーがどの程度「エンゲージ」しているかを表す ○ 「エンゲージ」が具体的に何を指すかはサイトによって違う ● 何で計測する? ○ サイトによって計測⽅法は様々 ■ 1ユーザー当たりのページビュー数 ■ ユーザーのサイト内滞在時間の平均値 ■ 旦那デスノートの場合:「〇ねばイイね!」の数 ● ほとんどのサイト運営者はユーザーエンゲージメントの上昇を⽬指している ○ 広告収益を稼いだり、コンバージョン数を上げて利益を得るため ■ コンバージョン数:「Webサイトで獲得する最終的な成果」のこと ■ 商品購⼊数、有料プランの導⼊数、アフィリエイトの獲得数など ○ (ほかに⽬的がある場合もある)

Slide 5

Slide 5 text

いかにしてユーザーエンゲージメントを⾼めるか? ● ⾃分がWebサイトを持っているとする。いかにしてユーザーエンゲージメン トを⾼めるか? ○ ⽅法はいろいろありそう ● ほかのサイトをヒントに考えてみる ○ ピクシブ百科事典 ○ ニコニコ⼤百科 ○ Wikipedia ● 若かったあの頃、将来への不安を抱えながら読み漁ったサイトたちを思い出 してほしい ● あなたを夢中にさせた機能は何?

Slide 6

Slide 6 text

「関連する記事」の表⽰機能 ピクシブ百科事典 「メリーバッドエンド」 ニコニコ⼤百科 「フロム脳」 Wikipedia 「テセウスの船」 これで無限に時間を消費した

Slide 7

Slide 7 text

⾃分が運営するサイトで「関連する記事」を表⽰する機能を実装してみた 今⽇はその話をします

Slide 8

Slide 8 text

今⽇話すこと ● 対象サイト:「健常者エミュレータ事例集」の説明 ● 技術選定:「関連する記事」の実装⽅法の選定 ● 技術実装:実装⽅法の詳細 ● 効果測定 近年のLLMとそのエコシステムの発展により、⼿軽に実装できる⽔準まで難易度 が下がっていることを伝えたい

Slide 9

Slide 9 text

⾃⼰紹介 ● ⾃⼰紹介に興味がない⽅のために、 左半分は柴⽝の画像にしています ● 関⼼領域 ○ データエンジニアリング、データマネジメ ント(本業) ○ TypeScript, React, Remix, Supabaseなど ● VRChatプレイ時間数 : 67.4時間 ○ まだUser、伸び盛りがある ○ はやくKnownになりたい https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB:Shiba_inu_taiki.jpg

Slide 10

Slide 10 text

今⽇話すこと ● 対象サイト:「健常者エミュレータ事例集」の簡単な説明 ● 技術選定:「関連する記事」の実装⽅法の選定 ● 技術実装:実装⽅法の詳細 ● 効果測定

Slide 11

Slide 11 text

「健常者エミュレータ事例集」について ● healthy-person-emulator.org ● コンセプト「個⼈の属性によらず、誰もが暗黙 知にアクセスできる世界を⽬指す」 ● ユーザーは投稿フォームから暗黙知を投稿し、 評価し、コメントできる ● 統計(2024-04時点) ○ 投稿数:9020件 ○ コメント数:20643件 ○ PV数:5万 ~ 30万/⽉

Slide 12

Slide 12 text

ユーザーは記事に「いいね」「よくな いね」をできる 5W1H+Thenのフレームワークに従い、 前提条件を整理する 事実に基づく反省(Reflection) 反実仮想 書ききれなかったことの補⾜ タイトル=教訓の要約 投稿フォームを通じ、フレームワークに基づく内省から記事を作成する

Slide 13

Slide 13 text

ユーザーは投稿フォームから記事の作成が可能

Slide 14

Slide 14 text

「やってよかったこと」のパターンもある

Slide 15

Slide 15 text

このサイトに「関連する記事」の表⽰機能をつけたい

Slide 16

Slide 16 text

今⽇話すこと ● 対象サイト:「健常者エミュレータ事例集」の簡単な説明 ● 技術選定:「関連する記事」の実装⽅法の選定 ● 技術実装:実装⽅法の詳細 ● 効果測定

Slide 17

Slide 17 text

最終的なアウトプット像を固める 「横浜のデートで北朝鮮の⼯作船を⾒に⾏こうとして はいけない」の関連記事 「異常⾏為でも⾏動の健常化に繋がることがある」の 関連記事

Slide 18

Slide 18 text

実装⽅法の検討 タグを利⽤する⽅法 内容に基づく⽅法 実装内容 - 同じタグを持つ記事を関連す る記事として表⽰する - 記事⾃体をベクトル化して、ドッ ト積から類似度を算出する ⻑所 - 実装が簡単 - 内容に基づいて関連記事を出せる - タグがなくても関連記事を出せる 短所 - タグがない記事が多い - 同じタグが付いているから関 連しているとも⾔えない - 関連している記事でも同じタ グが付いていない場合が多い - 実装が⾯倒 - ちょっと⾦がかかる リニューアル前までは採⽤ ● 短所が無視できない⼤きさ サイトリニューアルに伴い こちらを採⽤

Slide 19

Slide 19 text

全体的な流れの説明 1. 記事をEmbeddingしてベクトルを算出 ○ embedding = 「似ているインプットを似ているベクトルとして返すブラックボック ス」 ○ 似ている記事は似ているベクトルになる ○ (これ以上は踏み込めない) 2. ベクトルをデータベースに格納 3. ドット積を算出し、類似した記事を出⼒ ○ ドット積:ベクトル同⼠の積の出し⽅の⼀つ ○ ドット積の値でソートすれば類似度順にソートしたことと等価になる 4. サーバーサイドで類似記事をフェッチし、クライアントサイドに渡す 5. クライアントサイドで表⽰する

Slide 20

Slide 20 text

全体的な流れ https://zenn.dev/contradiction29/articles/b110f4f4ab03da

Slide 21

Slide 21 text

技術選定 ● embeddingの⼿段としてはOpenAI Embedding APIを採⽤ ○ text-embedding-3-smallモデルを利⽤ ○ 費⽤対効果に優れていたため ○ 取り込み可能なトークン数が8192と⼤きい ● ベクトルを格納するデータベースとしてはSupabaseを利⽤ ○ ベクターDBとしての格納〜サーバーサイドでの類似記事のフェッチまで⼀貫してできるため ○ 認証機能もこれで実装しており、既存の技術選定の拡張でできる ● 補⾜:Supabaseについて ○ Open-source alternative for firebase ○ PostgreSQLだけでなく、認証‧認可の機能やedge functionsの機能もある ○ いわゆるBackend as a Service

Slide 22

Slide 22 text

今⽇話すこと ● 対象サイト:「健常者エミュレータ事例集」の簡単な説明 ● 技術選定:「関連する記事」の実装⽅法の選定 ● 技術実装:実装⽅法の詳細 ● 効果測定

Slide 23

Slide 23 text

細かい実装はGitHubにあります https://github.com/sora32127/healthy-person-emulator-dotorg

Slide 24

Slide 24 text

記事データをembeddingしてsupabaseに格納する ● 処理の流れ ○ 投稿フォーム側からpostId、投稿内容を受け取る ■ postId : 投稿に対して⼀意に振られるID ■ 投稿内容:HTML ○ OpenAI Embedding APIに投稿内容を投⼊し、ベ クトルに変換 ○ 変換したベクトルをPostgreSQL (on Supabase) に格納 ● かなり簡単 ○ 50⾏程度 https://github.com/sora32127/healthy-person-emulator-dotorg/blob/main/app/m odules/embedding.server.ts

Slide 25

Slide 25 text

supabase内部で類似度検索関数を作成 ● argument ○ post_id : 類似している記事を探したい対象 ○ match_threshold : 類似度の閾値 ○ match_count : 受け取りたい数 ● return ○ post_id : 類似記事のid ○ post_title : 類似記事のタイトル ○ similarity : 類似度

Slide 26

Slide 26 text

類似した記事をフェッチして表⽰ ● supabaseのremote procedure call機能を 使って関数を呼び出し ○ さっき作った関数を呼び出している ● 呼び出した結果を返し、フロント側に渡して いる

Slide 27

Slide 27 text

今⽇話すこと ● 対象サイト:「健常者エミュレータ事例集」の簡単な説明 ● 技術選定:「関連する記事」の実装⽅法の選定 ● 技術実装:実装⽅法の詳細 ● 効果測定

Slide 28

Slide 28 text

効果測定1:関連記事経由平均ページビュー数は1.85倍に増加 04/10 機能実装 計測ぶっ壊れゾーン ● 「関連する記事」経由でのユーザー当たり平均 ページビュー数(PV数)で効果を計測する ○ データ定義: ■ リファラー = https://healthy-person-emulator.or g/archives/* ■ event_name = page_view ■ PV数をuser_pseudo_idごとに集計 ■ 集計後、⽇時で平均値を算出 ● 平均PV数は1.85倍に上昇 ○ 03/02 ~ 04/09平均 : 8.26 ○ 04/21 ~ 04/29平均 : 15.32 ● 注意: ○ 計測ぶっ壊れゾーンは除外している ■ 03/01, 04/11 ~ 04/20 ○ 「次のページ」「前のページ」での遷移と 区別はできていない ○ ⼤規模サイトリニューアルと同時に実装し ているため、因果関係は明確ではない

Slide 29

Slide 29 text

効果測定2 : 平均エンゲージメント時間は1.47倍に増加 ● 「平均エンゲージメント時間」の定義 ○ ユーザーがサイトを実際に閲覧していた時 間のこと ○ タブを開いていない状態やバックグランド にある状態はエンゲージメント時間として カウントされない ● 平均エンゲージメント時間は1.47倍に上昇 ○ 03/02 ~ 04/09平均 : 191.4秒 ○ 04/21 ~ 04/29平均 : 282.1秒 ● 注意: ○ 計測ぶっ壊れゾーンは除外している ○ 関連記事経由平均PV数よりほかの要因が絡 んでいる可能性が⾼いので、参考程度

Slide 30

Slide 30 text

補⾜:コストについて ● 前提 ○ 1記事当たりの平均トークン数は642.8 ○ サイト全体では5,808,792トークン ○ text-embedding-3-smallのコストは $0.02/1M tokens ■ https://openai.com/pricing ● サイト全体のembeddingにかかった合計コスト ○ 5.8 * $0.02 = $0.116 = 17.4円 ○ $1 = 150円換算 ○ 実際は記事の編集時にもembeddingを⾏うのでもう少し⾼くなる ○ それでも安い

Slide 31

Slide 31 text

まとめ ● Embeddingを利⽤すれば関連する記事を表⽰する機能が⼿軽に作れる ○ 「安い」:エコシステムの発展によりembeddingのコストは下がっている ○ 「(実装が)早い」:APIをたたくだけなので実装は簡単 ○ 「効果が⾼い」:既存の仕組みよりはマシなものが作れる可能性が⾼い ● よかったら使ってみてね