見出し画像

ChatGPTとVectorを使ってキャラクター再現

1. はじめに

本記事では、OpenAIのChatGPTを利用して、特定のキャラクターの再現を行う方法について学びます。ここでの「キャラクター再現」とは、特定のキャラクターの性格や特性を元にした会話モデルを作成することを指します。

実際のデモコードは下記です。
このコードについて解説をしていきます。

※使い方。APIキーを入れて1から順に▶連打。以上

2 Vector(ベクトル)とは?

Vector(ベクトル)は、情報やデータを数値の一連の配列として表現したものです。この技術を使うと、複雑なテキスト情報もコンピュータが理解しやすい数値の形に変えられます。

2.1 テキストのVector化とは?

テキストの「Vector化」とは、単語や文章を数値の配列、つまりベクトルに変換することです。これにより、コンピュータはテキストの意味や関連性を数値として捉えられるようになります。

2.2 埋め込みベクトルとは?

埋め込みベクトルはテキストのVector化の手法の一つ。似た意味の単語や文章は、このベクトル上で近くに位置します。例えば、単語「犬」と「猫」は動物や生き物、ペットという共通点があるため、ベクトル上では近い位置になります。

3. 準備

PIPインストールとAPIキーのセット:

まず、Colab上で必要なライブラリのインストールとOpenAIのAPIキーをセットアップします。このキーはあなたのOpenAIアカウントで取得できます。
openai.api_key = ""
の部分にセットしてください。
その後、「最初に起動:PIPinstallとimport。openai.api_keyにAPIキーを入れてください。」のコードを実行してください。

# @title 最初に起動:PIPinstallimport。openai.api_keyにAPIキーを入れてください。
!pip install openai
!pip install tiktoken

import tiktoken
from tiktoken.core import Encoding
import json
import numpy as np
import openai
openai.api_key = ""
GptModel = "gpt-4"
MaxTokens = 3500
encoding: Encoding = tiktoken.encoding_for_model(GptModel)

4. Vectorの作成

Vectorは、テキストの意味や内容を数値の配列として表現したものです。この技術を使うことで、テキストの情報やその中の意味を、コンピュータが扱いやすい形で保存・分析できます。
2番目に起動:vectorデータ作成。ada002を使用を実行することで、ロキシーに関する簡単なデータを用意できます。
※入力されていない情報は知らないです。GPT4だと事前学習の内容からある程度回答してくれます。

ここにキャラクターの情報とセリフ集をセットしてから実行してください。

4.1tone_example

これはキャラクターのセリフです。
回答時にこのセリフデータの中から似ているセリフを抽出し、同じような口調で返答を行います。

4.2phenomenon

これはキャラクターの知っている情報です。
Informationでいい気がしますがなんとなくphenomenonってつけちゃったテヘペロ。
キャラクターの知っているであろう情報をぶっ込んでください。
wikiとかから取捨選択し手入れたり、自分で書いたりしてください。

5. Vectorの活用

Vectorを活用することで、テキスト間の関連性や類似性を計算し、特定の情報を効率よく検索することができます。
3番目に起動:関数。の中でやっていることです。

この部分では、ユーザーの入力をベクトル化し、similarity関数を使って類似検索を行います。検索結果から最も関連性の高いphenomenontone_exampleを抽出し、それらの情報を用いてコンテキストを作成しています。

また、キャラクターのメインの情報、システムプロンプトもここでセットしています。キャラクターに合わせて改変してください。

6. ChatGPTデモ

実際にVectorを利用して、キャラクターとしてのGPTとの会話を行うデモを試してみましょう。4番目のコードを実行すると、ロキシーというキャラクターとの会話ができます。

7. まとめ

この記事を通して、VectorとChatGPTを利用して、特定のキャラクターの再現を行う方法を学びました。この知識を応用して、様々なキャラクターの再現や、さらに高度な会話モデルの開発にチャレンジしてみてください。

おまけ1:vectorを非同期で高速化

下記の記事の非同期並列処理を使用することでvectorの作成が高速化されます。

おまけ2:web版でキャラクター召喚

下記の記事でWeb版のChatGPTにキャラクターとしてロールプレイしてもらうことができます。

おまけ3:今回のコード詳細解説 for ChatGPT

# 最初に起動:PIPinstallとimport。openai.api_keyにAPIキーを入れてください。

# openai ライブラリをインストールします。このライブラリを使用することで、OpenAIのAPIを簡単に利用できるようになります。
!pip install openai

# tiktokenライブラリをインストールします。このライブラリは、テキストのトークン数を計算するのに役立ちます。
!pip install tiktoken

# tiktokenから必要な関数やクラスをインポートします。
import tiktoken
from tiktoken.core import Encoding

# 他の必要なライブラリやモジュールをインポートします。
import json           # JSON形式のデータを操作するためのモジュール
import numpy as np    # 数学的な計算や配列操作のためのライブラリ
import openai         # OpenAIのAPIを利用するためのライブラリ

# OpenAIのAPIを利用するためのAPIキーを設定します。このキーは個別に取得する必要があります。
openai.api_key = ""

# 使用するモデルの名前を定義します。ここでは"gpt-4"という名前のモデルを使用することを示しています。
GptModel = "gpt-4"

# モデルが処理できる最大トークン数を定義します。トークンとは、テキストを分割した単位のことを指します。
MaxTokens = 3500

# 使用するモデルに合わせたエンコーディング方式を取得します。これはテキストのトークン数を計算するのに必要です。
encoding: Encoding = tiktoken.encoding_for_model(GptModel)
# Vector化という処理を行う部分です。

# textを引数として受け取り、そのテキストをベクトル化する関数です。
def vectorize(text):
    # OpenAIのEmbedding APIを利用して、テキストをベクトル化します。
    # このAPIは、テキストを数値のリスト(ベクトル)に変換するものです。
    res = openai.Embedding.create(
        model='text-embedding-ada-002',  # 使用するモデルの名前を指定します。
        input=[text]                     # ベクトル化するテキストを指定します。
    )
    # APIのレスポンスからベクトルのデータを取り出します。
    embedding = res['data'][0]['embedding']
    # ベクトルを文字列の形式で返すために、json.dumpsを使用してJSON形式に変換します。
    return json.dumps(embedding)

# tone_examplesとphenomenaという2つのテキストリストを受け取り、それらをベクトル化してJSONファイルに保存する関数です。
def create_and_save_index_json(tone_examples, phenomena):
    # ベクトル化したデータを保存するためのリストを初期化します。
    index_data = []

    # tone_exampleの各テキストをベクトル化して、index_dataリストに追加します。
    for text in tone_examples:
        vector = vectorize(text)  # テキストをベクトル化
        index_data.append({
            "Data": text,                               # 元のテキストデータ
            "Vector": json.loads(vector),               # ベクトルデータ(JSON形式の文字列をPythonのリストに変換)
            "MetaData": "tone_example"                  # メタデータ(ここでは'tone_example'を指定)
        })

    # phenomenaの各テキストをベクトル化して、index_dataリストに追加します。
    for text in phenomena:
        vector = vectorize(text)  # テキストをベクトル化
        index_data.append({
            "Data": text,                               # 元のテキストデータ
            "Vector": json.loads(vector),               # ベクトルデータ(JSON形式の文字列をPythonのリストに変換)
            "MetaData": "phenomenon"                    # メタデータ(ここでは'phenomenon'を指定)
        })

    # index_dataリストをJSONファイルとして保存します。
    with open("./INDEX.json", "w") as outfile:
        json.dump(index_data, outfile)

    # 保存したファイルのパスを返します。
    return "./INDEX.json"
# @title 3番目に起動:関数。
global MessageLog  # グローバル変数MessageLogを宣言

# ピクシブ百科事典からの引用文を保存するための変数
SystemPrompt = """"""

# GPTモデルにメッセージを送信し、レスポンスを受け取る関数
def get_gpt_response(messages, gptmodel = GptModel) -> str:
    try:
        # GPTモデルにメッセージを送信してレスポンスを取得
        response_json = openai.ChatCompletion.create(
                            model=gptmodel,
                            messages=messages
                        )
        # レスポンスが存在する場合、返答テキストを取り出して返す
        if response_json is not None:
            responseText = response_json['choices'][0]['message']['content'].strip()
            return responseText
        # レスポンスがない場合、エラーメッセージを表示
        print("ChatGPTからの返答がありません")
    except Exception as e:
        # 何らかのエラーが発生した場合、エラーメッセージを表示
        print(f"エラーが発生しました。{str(e)}")

# メッセージを生成する関数
def create_message(systemPrompt, messageList=[], userMessage=None):
    """
    :param systemPrompt: GPTのプロンプト
    :param messages: messageリスト
    :return:
    """
    # messageListの参照を切り離すための新しいリストを作成
    messageList = messageList[:]
    
    # システムメッセージを作成
    systemMessage = {'role': 'system', 'content': systemPrompt}
    messages = [systemMessage]  # システムメッセージをリストに追加

    # ユーザーメッセージが存在する場合、それをリストに追加
    if userMessage is not None and userMessage != "":
        messageList.append({'role': 'user', 'content': userMessage})

    # ChatGPTのトークンの上限が4000なので、全体のトークン数がMaxTokensを超えないように古いメッセージを削除
    total_chars = len(encoding.encode(systemPrompt)) + sum([len(encoding.encode(msg['content'])) for msg in messageList])
    while total_chars > MaxTokens and len(messageList) > 0:
        messageList.pop(0)
        total_chars = len(encoding.encode(systemPrompt)) + sum([len(encoding.encode(msg['content'])) for msg in messageList])

    # 最終的なメッセージリストを作成
    messages.extend(messageList)

    return messages

# コサイン類似度を更新する関数
def updated_cosine_similarity(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

# テキストをベクトル化する関数
def vectorize(text):
    # OpenAIのEmbedding APIを使用してテキストをベクトル化
    res = openai.Embedding.create(
        model='text-embedding-ada-002',
        input=[text]
    )
    # ベクトルデータを取得
    embedding = res['data'][0]['embedding']
    return embedding

# ベクトルの類似度を計算して取得する関数
def similarity(query, INDEX, meta_data=None, top=10):
    if meta_data:
        # 特定のMetaDataに関連するデータのみをフィルタリング
        INDEX = [entry for entry in INDEX if entry["MetaData"] == meta_data]
        
    # 各データとの類似度を計算
    results = map(
        lambda i: {
            'Data': i['Data'],
            'MetaData': i['MetaData'],
            'similarity': updated_cosine_similarity(i['Vector'], query)  # ここでコサイン類似度を計算
        },
        INDEX
    )
    # 類似度の高い順にソート
    results = sorted(results, key=lambda i: i['similarity'], reverse=True)
    # 上位の結果を返す
    return results[:top] if len(results) > top else results

# VectorDataをJSON形式に変換する関数
def VectorDataToJson(VectorData):
    """
    :param VectorData: VectorData
    :return: JSON
    """
    VectorDataJson = []
    for Vector in VectorData:
        VectorDataJson.append({"Data":Vector.Data,"Vector":Vector.Vector,"MetaData":Vector.MetaData})
    return VectorDataJson

# INDEX.jsonからデータを読み取る関数
def load_index_from_json(file_path):
    with open(file_path, "r") as infile:
        data = json.load(infile)
    return data

# GPTモデルとのチャットを行う関数
def chat_with_gpt(file_path="./INDEX.json"):
    MessageLog = []

    # INDEX.jsonからデータを読み取る
    INDEX = load_index_from_json(file_path)

    while True:
        # ユーザーからの入力を取得
        user_input = input("あなた: ")

        # 入力が"終了"の場合、ループを終了
        if user_input.lower() == "終了":
            break

        # MessageLogから最新の'role': 'assistant'のメッセージを取得
        latest_assistant_message = next((message for message in reversed(MessageLog) if message['role'] == 'assistant'), None)
        if latest_assistant_message:
            combined_input = latest_assistant_message['content'] + "\n" + user_input
        else:
            combined_input = user_input

        # 入力したテキストをベクトル化して、INDEXデータとの類似度を計算
        query_vector = vectorize(user_input)
        phenomenon_results = similarity(query_vector, INDEX, meta_data="phenomenon")
        tone_example_results = similarity(query_vector, INDEX, meta_data="tone_example")

        # 最も似ているphenomenonとtone_exampleを取得
        phenomenon = phenomenon_results[:2]
        tone_examples = tone_example_results[:5]

        # GPTへの入力文を作成
        context = ""
        for item in phenomenon:
            context += "====あなたの知っている情報====\n" + item["Data"] + "\n"
        for item in tone_examples:
            context += "====あなたの口調例====\n" + item["Data"] + "\n"

        # メッセージを作成してGPTに送信
        mesage = create_message(SystemPrompt + context, MessageLog, "Chatなので完結に返答してください。ロキシーとして返答してください。\n入力文\n" + user_input)
        gpt_response = get_gpt_response(mesage)

        # メッセージログを更新
        MessageLog.append({'role': 'user', 'content': user_input})
        MessageLog.append({'role': 'assistant', 'content': gpt_response})

        # GPTからの返答を表示
        print(f"ロキシー: {gpt_response}")


この記事が気に入ったらサポートをしてみませんか?