ミツモア Tech blog

「ミツモア」を運営する株式会社ミツモアの技術ブログです

LLMを使ってSEO記事を整理する試み

初めまして、ミツモアのデータチームの増田です。11月からミツモアに入社して、データやAI、マッチングアルゴリズムに関連する様々な業務に携わっています。

まだ入社してそれほどの期間しか経っていませんが、LLMを用いたSEOチームとの協力プロジェクトがユニークだったので、今回はその成果をご紹介させていただきます。

SEOとは

SEO(Search Engine Optimization)は、ウェブサイトやウェブページが検索エンジンでより高い順位に表示されるように最適化するプロセスです。主な目的は、検索エンジンのアルゴリズムに適合し、特定のキーワードやフレーズで検索された際に、ウェブサイトがより上位に表示されることです。ミツモアでは提供しているサービスに関連した様々な内容で記事を制作し、ミツモアの利用者を増やすための導線としています。

SEOチームはこれらの記事の執筆、整理、リライトなどを通して、多くの良質な記事を制作し、ミツモア利用者を増やしています。

SEOチームの課題

SEOにおいて、サイト内の記事間での内容の重複は全体的なSEOの順位を下げる要因となることが知られています。完全に同じ内容の記事を複数公開することはもちろんのこと、記事内の一部で部分的な重複が生じている場合でも、重複コンテンツはランキングに悪影響を及ぼします。そのため、SEOチームはミツモアで公開している記事を精査し、他のページと内容が重複している部分は削除やリライトを通して内容を向上させています。しかし、ミツモアが提供しているサービスが膨大であるため、タイトルや見出しのレベルでの重複を探す作業にはかなりの時間がかかってしまっていました。

LLMによる解決方法

上記の課題において、類似コンテンツを探す作業にLLMを活用しました。

ChatGPTで一躍有名になったLLMは、内部ではテキストをベクトルに置き換えて演算を行い、テキスト理解や生成を実現しています。このテキストをベクトルに置き換える部分の処理をEmbedding(埋め込み)と呼びます。Embeddingの結果の数字を使用して類似度を計算し、二つの与えられたテキストが似ているか否かを判断できます。

類似度の計算にはいくつか選択肢があるのですが、今回はコサイン類似度を使用しました。これは二つのベクトルの”方向”が合っているかを計算するもので、具体的には、二つのベクトルの間の角度にcosineを適用した値を計算します。-1~1の間の値を取り、全く同じテキスト間だと1、全く逆の意味を持つテキスト間だと-1という結果になります。

また、文章全部にLLMを適用すると、料金に懸念があったため、記事のタイトルや見出し(H1, H2, H3タグ)に限定して比較を行う方針で今回は行いました。

類似度計算のサンプルコード

以下は類似度計算のサンプルコードです。

from openai import OpenAI
import numpy as np

client = OpenAI(api_key="XXXXXXX")  # ここにAPIキーを入れる

def get_embedding(text, model="text-embedding-ada-002"):
    text = text.replace("\n", " ")
    output = client.embeddings.create(input = [text], model=model).data[0].embedding
    return output

def cosine_similarity(a, b):
    return np.dot(a, b.T) / np.outer(np.linalg.norm(a, axis=1), np.linalg.norm(b.T, axis=0))

最新の技術ではありますが、コード自体は至ってシンプルになっています。

実際の動作例を以下に示します。

# 文章A
a = get_embedding("今日はいい天気ですね。")

# 文章B
b = get_embedding("今日は晴れです。")

# 文章C
c = get_embedding("ミツモアは、くらしからビジネスまでなんでもプロに見積もりを依頼できるプラットフォームです。")

X = np.c_[np.array(a), np.array(b), np.array(c)].T
cosine_similarity(X, X)

結果は以下の通りです。

array([[1.        , 0.95186858, 0.77982163],
       [0.95186858, 1.        , 0.78353387],
       [0.77982163, 0.78353387, 1.        ]])

各要素が文章間の類似度を表しています。行列の行と列は共に文章A, B, Cに対応しており、結果として以下のような類似度が計算されていることが分かります。

  • 文章Aと文章Bの関連度: 0.952
  • 文章Aと文章Cの関連度: 0.780
  • 文章Bと文章Cの関連度: 0.784

文章Aと文章Bは類似度が高く、文章Aと文章C、文章Bと文章Cは類似度が低いことが確認できます。

アウトプット

具体的な成果物として、Redashを使用して以下のような情報を提供しました。

SEO記事の重複検知の取り組みで作成したRedash

結果を見ると、「賃貸物件で水漏れが発生したときの対処法」と「賃貸物件で水漏れしたときの対処法」が高い類似度と評価されています。これにより、単なる一致だけでなく、文章の意味を理解して類似性を判定し、類似したH2タグの結果を上位に表示できていることが確認できます。

アウトプットの説明

各記事のH2タグの全組み合わせに対して類似度を計算しています。上記のスクリーンショットでは、permlink_Aとh2_tag_Aが記事Aのリンクと対象H2タグ、permlink_Bとh2_tag_Bが記事Bのリンクと対象H2タグに対応し、similarity列がこれらのH2タグの類似度を示しています。(SQLがわかる人向けにいうと、permlinkとh2_tagの組よりなるテーブルをcross joinしています。)ただし、Redashの類似度は見やすさのために、コサイン類似度をそのまま使用せず、線形変換による補正が行われています。

H2タグは全部で約3万件あり、これを全て掛け合わせると3万2 = 9億レコードになり、データ量的に処理が難しくなります。そのため、コサイン類似度が0.92以下のH2タグの組み合わせは結果に含めないようにしています。この0.92という値は、SEOチームからのフィードバックに基づき、この値を下回ると類似性が不十分と判断されるという意見を反映しています。

また、完全に一致するH2タグ(例: 定型文)を除外する、類似度の上限と下限でフィルタリングを行う、指定されたサービスの記事のみを含むようにするなど、SEOチームである程度柔軟に結果を見れるようにするため、Redashにいくつかのパラメータを設定しています。

フィードバック

SEOチームからのフィードバックは非常に良好でした。これまで数時間かかっていた作業が一瞬で行えるようになった、微妙な表記の揺れなども検知してくれる、スコアで順位付けされることで優先的に対応すべき項目が明確になった、などのフィードバックをいただくことができました。

また、コサイン類似度だと0.92くらいから類似しているとは感じられなくなるとの感想も教えていただき、コサイン類似度の実感を持つことができました。

雑感

入社してまもなく行ったこのプロジェクトは非常に効果的であり、SEOチームが重複の発見に費やしていた時間が大幅に削減され、その分記事のリライトやブラッシュアップに注力できるようになりました。この効果により、ミツモアの更なる成長が期待されます。

また、SEOとLLMの組み合わせが非常に相性が良いことが実感でき、データチームとSEOチームで今後の展開や新たな取り組みについても議論を行なっています。今後の発展が非常に楽しみです。