生成AIを使ってトピックモデリングをやってみる
はじめに
トピックモデルとしてLDA(Latent Dirchlet Allocation)モデルを使って文章からトピックを特定しようと試みたことがありました。
しかし、LDAモデルの出力である各トピックを構成する単語群が、前処理やモデルのパラメータによってはトピックを推測しづらいものになることがあることやトピックのラベルまでは付けてくれないことから、他の手法を試してみたくなりました。
調べてみると、生成AIを使ってトピックモデリングをやっている論文があり、それを参考に、文章から各トピックを構成する単語群を抽出することと単語群にトピックラベルを付けることをやってみました。
プロンプトは、基本上記の論文に記載されているものを日本語に翻訳し、さらにトピックラベルをつけることを加えています。
仮のストーリー
飲食店を経営していてカスタマーレビュー上で特に話題となっていることを抽出して経営に活かしたいとします。
架空のオムライス屋さんの良い点、悪い点を数名に書いてもらったと仮定して、それらを1つの文章にした以下のテキストファイルをGoogle Geminiを使って作り、本記事で使用することにしました。
何度食べても飽きない、絶品のオムライスです。
ふわふわの卵が口の中でとろけるような食感で、幸せな気分になります。
チキンライスも上品な味付けで、卵との相性も抜群です。
デミグラスソースは濃厚で、ご飯によく合います。
大盛りにしてもペロリと食べられてしまうほど、美味しいです。
店内は落ち着いた雰囲気で、ゆっくり食事を楽しめます。
何度でも通いたくなる、そんなお店です。
「カクウノオムライスヤ」のオムライスは、ソースが決め手です。
デミグラスソースは、コクがありながらも上品な味わいで、ご飯との相性も抜群です。
チキンライスも、しっとりとした食感で、味付けもちょうど良いです。
卵はふわふわで、口の中でとろけるような食感がたまりません。
ボリュームも満点なので、お腹いっぱいになります。
何度食べても飽きない、そんな一品です。
子供の頃から慣れ親しんだ「カクウノオムライスヤ」の味。
変わらない美味しさが嬉しいです。
ふわふわの卵と、チキンライスとのハーモニーがたまりません。
デミグラスソースも、昔ながらの味で、懐かしさを感じます。
店内は昔ながらの喫茶店の雰囲気で、落ち着いて食事を楽しめます。
何度食べても飽きない、そんな大好きな一品です。
街の中にある隠れ家のようなお店です。
店内は落ち着いた雰囲気で、ゆっくり食事を楽しめます。
オムライスは、ふわふわの卵と、チキンライスとのハーモニーが絶妙です。
デミグラスソースも、コクがありながらも上品な味わいで、ご飯によく合います。
ボリュームも満点なので、お腹いっぱいになります。
何度食べても飽きない、そんな一品です。
「カクウノオムライスヤ」は、コスパの良さも魅力の一つです。
ボリューム満点のオムライスが、リーズナブルな価格で楽しめます。
ふわふわの卵と、チキンライスとのハーモニーがたまりません。
デミグラスソースも、コクがありながらも上品な味わいで、ご飯によく合います。
店内は落ち着いた雰囲気で、ゆっくり食事を楽しめます。
「カクウノオムライスヤ」は、何度食べても飽きない、そんな魅力的なお店です。
ふわふわの卵と、チキンライスとのハーモニーがたまりません。
デミグラスソースも、コクがありながらも上品な味わいで、ご飯によく合います。
ボリュームも満点なので、お腹いっぱいになります。
店内は落ち着いた雰囲気で、ゆっくり食事を楽しめます。
老舗の洋食屋さんならではの、本格的なオムライスが楽しめます。
ふわふわの卵と、チキンライスとのハーモニーが絶妙です。
デミグラスソースも、コクがありながらも上品な味わいで、ご飯によく合います。
店内は昔ながらの喫茶店の雰囲気で、落ち着いて食事を楽しめます。
何度食べても飽きない、そんな大好きな一品です。
ランチタイムはいつも賑わっている人気店です。
ふわふわの卵と、チキンライスとのハーモニーが絶妙です。
デミグラスソースも、コクがありながらも上品な味わいで、ご飯によく合います。
ボリュームも満点なので、お腹いっぱいになります。
店内は落ち着いた雰囲気で、ゆっくり食事を楽しめます。
「カクウノオムライスヤ」は、デートにもおすすめです。
店内は落ち着いた雰囲気で、ゆっくりと二人の時間を過ごすことができます。
ふわふわの卵と、チキンライスとのハーモニーが絶妙です。
デミグラスソースも、コクがありながらも上品な味わいで、ご飯によく合います。
一度食べたら忘れられない、そんな絶品のオムライスです。
ふわふわの卵と、チキンライスとのハーモニーがたまりません。
デミグラスソースも、コクがありながらも上品な味わいで、ご飯によく合います。
ボリュームも満点なので、お腹いっぱいになります。
店内は落ち着いた雰囲気で、ゆっくり食事を楽しめます。
リピーターが多いのも納得の味です。
期待して行ったのですが、正直がっかりでした。
オムライスの卵はパサついていて、全然とろけるような食感ではありませんでした。
チキンライスも味が薄く、物足りなさを感じました。
デミグラスソースもコクがなく、全体的に味がぼやけていました。
値段も少し高めなので、コスパも良くありません。
以前はもっと美味しく感じていたのですが、最近は味が落ちたように感じます。
オムライスの卵が硬く、パサついているのが気になりました。
チキンライスも味が薄く、物足りないです。
デミグラスソースもコクがなく、昔の方が濃厚で美味しかったと思います。
SNSで話題になっていたので行ってみたのですが、期待していた味とは違いました。
オムライスの卵はパサついていて、もう少しふわふわしている方が好みです。
チキンライスも味が薄く、物足りなさを感じました。
デミグラスソースも、もっとコクがある方が良かったです。
オムライスのボリュームが少なかったです。男性には物足りないかもしれません。
もう少しご飯の量が多いと嬉しいのですが。
味は普通でしたが、特に感動するような美味しさではありませんでした。
お店の雰囲気は良かったのですが、店員さんの対応が少し残念でした。
注文を聞き間違えられたり、料理が出てくるのが遅かったりしました。
料理の味も普通だったので、もう二度と行くことはないと思います。
オムライス以外のメニューが少なく、選択肢が少ないのが残念です。
デザートの種類も少なかったです。
オムライス自体は普通でしたが、もう少しメニューが豊富だと嬉しいです。
値段が高い割に、味は普通でした。オムライスの卵はパサついていて、もう少しふわふわしている方が好みです。
チキンライスも味が薄く、物足りなさを感じました。
デミグラスソースも、もっとコクがある方が良かったです。
店内は狭く、テーブルとテーブルの間隔が狭いので、落ち着いて食事を楽しむことができませんでした。
また、換気があまり良くなく、油のにおいが気になりました。
休日に行ったので、とても混んでいて待ち時間が長かったです。
30分以上待ったのに、出てきたオムライスはパサついていて、期待はずれでした。
一度行けば十分です。オムライスの卵がパサついていて、美味しくありませんでした。
チキンライスも味が薄く、物足りないです。
デミグラスソースもコクがなく、全体的に味がぼやけていました。
環境
- python version: 3.12.3
- boto3 version: 1.34.145
- Amazon Bedrockを使用
- 基盤モデル: Anthropic claude 3 opus
ソースコード
import json
import boto3
class BedrockClient:
def __init__(self: "BedrockClient") -> None:
self.session = boto3.Session(profile_name="profile_name")
self.client = self.session.client(
service_name="bedrock-runtime",
region_name="us-west-2",
)
def fetch_response(self: "BedrockClient", content: str) -> str:
body: str = json.dumps(
{
"anthropic_version": "bedrock-2023-05-31",
# トークン数の最大値
"max_tokens": 4096,
# システムプロンプト->生成する回答に対する指示。省略可能。
"system": "日本語で回答してください。",
# role->user(ユーザーからの質問) or assistant(モデルからの回答)
"messages": [
{
"role": "user",
"content": content,
},
],
},
)
try:
response = self.client.invoke_model(
body=body,
modelId="anthropic.claude-3-opus-20240229-v1:0",
accept="application/json",
contentType="application/json",
)
response_body = json.loads(response.get("body").read())
return str(response_body["content"][0]["text"])
except Exception as e:
raise e
ノートブックの実行
from pathlib import Path
import pandas as pd
from repo_name.bedrock_client import BedrockClient
# srcディレクトリまでのパスを取得する
script_dir: Path = Path(Path().resolve())
src_dir: Path = script_dir.parents[0]
# テキストファイルを読み込む
with open(Path(src_dir / "repo_name" / "sample_text.txt"), ) as f:
texts: list[str] = f.read().splitlines()
# テキストを結合する
text_joined = "".join(texts)
# プロンプトを作成する
def gen_prompt(comments: str) -> str:
topic_num: int = 5 # 抽出するトピック数
top_word_num: int = 8 # トピックごとに抽出する単語数
prompt: str = f"""
あなたは、与えられたテキスト中の潜在的なトピックを単語の共起によって発見するトピックモデルです。
以下のcommentsから、{topic_num}個の潜在トピックを発見し、その意味をcommentsから抜き出した{top_word_num}個の単語で正確に示してください。
出力は、常にoutput formatの形式でなければならず、output formatタグを含めてはいけません。
潜在トピックの数が{topic_num}であることを確認してください。
全ての潜在トピックの単語の数が{top_word_num}個であることを確認してください。
最後に、全ての潜在トピックに正確なラベルを付けてください。
<comments>
{comments}
</comments>
<output format>
ラベル1: 単語1, 単語2, 単語3, 単語4, 単語5, 単語6, 単語7, 単語8
ラベル2: 単語1, 単語2, 単語3, 単語4, 単語5, 単語6, 単語7, 単語8
ラベル3: 単語1, 単語2, 単語3, 単語4, 単語5, 単語6, 単語7, 単語8
ラベル4: 単語1, 単語2, 単語3, 単語4, 単語5, 単語6, 単語7, 単語8
ラベル5: 単語1, 単語2, 単語3, 単語4, 単語5, 単語6, 単語7, 単語8
</output format>
"""
return prompt
prompt: str = gen_prompt(comments=text_joined)
bedrock_client = BedrockClient()
res = bedrock_client.fetch_response(
content=prompt,
)
print(res)
オムライスの美味しさ: ふわふわ, 卵, とろける, 食感, チキンライス, デミグラスソース, 濃厚, ご飯
店の雰囲気: 落ち着いた, 雰囲気, ゆっくり, 食事, 楽しめる, 何度, 通いたく, お店
コストパフォーマンス: コスパ, 良さ, 魅力, ボリューム, 満点, リーズナブル, 価格, 楽しめる
オムライスの残念な点: パサついて, 硬く, 味, 薄く, 物足りない, コク, なく, 期待外れ
店の問題点: 値段, 高い, 狭い, 換気, 悪い, 油, におい, 待ち時間
実行結果を見ると、お店の良い点として、「オムライスの卵がふわふわであること」や「お店の雰囲気が良いということ」、「コスパが良いこと」がわかり、もっとこの部分を宣伝して新規顧客獲得を図るということができそうです。
また、お店の悪い点として、「オムライスの卵がパサついている・硬い・味が薄い等、オムライス自体に悪い印象を持っている人がいること」や「換気・匂い等、店内に対して悪い印象を持っている人がいること」がわかります。レシピの改良や店内の雰囲気をよりよくするための参考になりそうです。
以上です。ここまでご覧いただき、ありがとうござました。
参考
![NCDCエンジニアブログ](https://storage.googleapis.com/zenn-user-upload/avatar/3a86a409d3.jpeg)
NCDC株式会社( ncdc.co.jp/ )のエンジニアチームです。 募集中のポジションや、採用している技術スタックの紹介などはこちらをご覧ください! github.com/ncdcdev/recruitment
Discussion