はじめに
昨年末に社内の先輩エンジニアから、
「忘年会でアプリを作って、簡単な発表会をしよう!」という素敵な提案がありました。
「社内の誰かが企画したものには全部乗っかる」をモットーに過ごすと決めていたので、
OpenAIのAPIを触れたことがなかったので、AIを使った簡単なゲームを作ることにしました。
実際の画面
ゲームの概要
- お題の単語を決める(例:テスト)
- お題の言葉が生成文に入るような質問文を考える(例:学校で受けるものといえば?)
- お題の言葉が生成文に含まれていたら勝ち
点数化できるものは何だろう?
勝敗判定以外に、質問文の長さで得点を決めるというルールにしました(短いほど高得点)。
ルールが1つだと物足りないので、「お題と質問文の関連性が低ければ高得点」という条件を追加することに。
「関連性って数値にできるの?」と思いつつ調べた結果、Embedding APIというもので実装できそうなので、使ってみました。
実際のコード(一部)
def calculate_relevance(target_word, user_question)
# お題の言葉を直接含む場合はペナルティを適用
if user_question.include?(target_word)
return 0
end
# Embedding APIを使って質問文とお題の埋め込みを取得
question_embedding = get_embedding(user_question)
target_embedding = get_embedding(target_word)
# コサイン類似度を計算
similarity_score = cosine_similarity(generated_embedding, target_embedding)
# 類似度スコアを逆転させて、関連性が高いほど低点となるようにする
relevance_score = (1 - similarity_score) * 100
relevance_score
end
# Embedding APIを使って埋め込みベクトルを取得
def get_embedding(text)
client = OpenAI::Client.new
response = client.embeddings(
parameters: {
model: "text-embedding-ada-002",
input: text
}
)
response["data"][0]["embedding"]
end
# コサイン類似度を計算するヘルパーメソッド
def cosine_similarity(vec1, vec2)
dot_product = vec1.zip(vec2).map { |x, y| x * y }.sum
magnitude1 = Math.sqrt(vec1.map { |x| x**2 }.sum)
magnitude2 = Math.sqrt(vec2.map { |x| x**2 }.sum)
(dot_product / (magnitude1 * magnitude2)).round(3)
end
end
結局何をしているの?
単語(お題)と文章(質問文)をベクトルに変換し、
「それぞれのベクトルがどれくらい同じ方向を向いているか」で関連性を表しています。
この原理は「マッチングアプリで趣味の近いユーザー同士を繋げる」機能などに応用ができるみたいですね。
ゲームとしての改善点
- 関連性の点数がどのような質問文で似たような点数になってしまう
- 同じ単語で文字の種類が異なるもの(りんご、リンゴ、林檎)を同じものとして扱いたい
- お題をランダム出力できるようにして点数を競うモード、タイムアタックモードの実装
まとめ
関連性を数値化することで、ゲーム性を少し向上させることができました。
初めてOpenAIに触れてみましたが、導入方法が簡単で、個人利用の範囲だと料金も高くありませんでした。
興味のある方はお試しあれ。