見出し画像

MLAskをつかったテキストからの感情分析

【2023/3/7追記】
ただいま開発中のアプリ、『VRM_AI』と連携する前提で記事を改稿しました。基本的にやっていることは変わりません。

AI美少女、作っていますか?私は作っています。
AI美少女を作っていると感情分析やりたくなると思うんですよ。
表情つけたいから。というわけでPythonで動く感情分析ライブラリを導入してみました。
まぁ今回も自分向けの覚書きなのでついてこれる人だけついてこい。
そして間違っていることがあったら指摘してください(急に弱気)。

基本的には以下の記事に全てが書いてあります。

この通りに導入すればなんとかなるので導入方法はそちらを参照してもらうとして、じゃあそれをどう応用して、AI美少女に組み込んでいくかですよな。というわけで、前回の記事を読んでいる前提で、自分の環境の覚書をば。


① MeCabのインストール(全ては上の記事に書いてあります。割愛)

②convogpt環境を作る。
https://github.com/harubaru/convogptからconvogpt-main.zipをDLして解凍。
convogpt-mainの内容をC:\convogptに丸ごとコピー
各種ライブラリをインストール。

cd C:\convogpt_API\models
mkdir rinna
cd rinna
git lfs install
git clone https://huggingface.co/rinna/japanese-gpt-1b
cd C:\convogpt_API

python -m pip install -r requirements.txt
python -m pip install --upgrade pip setuptools
pip install transformers
pip install sentencepiece
pip install pandas
pip install ja-sentence-segmenter

https://note.com/tori29umai/n/n3cd8c5b32f7a
の①の手順を実行した後、以下を実行。(GPUを使う場合。)

python -m pip install -U pip setuptools 
pip3 install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu117

③感情推測に必要なライブラリをインストール

pip install mecab-python3
pip install mecab
pip install pymlask
pip install unidic
python -m unidic download

④以下のサンプルスクリプトを実行
"C:\convogpt_API\Unity_scripts\test.py"

from mlask import MLAsk
 
def emotion_analyz(text):
    emotion_analyzer = MLAsk('-d "C:/Program Files/MeCab/dic/mecab-ipadic-neologd"')
    result = emotion_analyzer.analyze(text)
    if result['emotion'] == None:
        emo = "None"
        print(emo)
    else:
        emo = result["representative"][0]
        print(emo)

emotion_analyz('彼のことは嫌いではない!(;´Д`)')

こんな感じに表示されたら成功です。というわけで応用していきましょう

"C:\convogpt_API\Unity_scripts\emotion_analyz.py"を作成。

from mlask import MLAsk
import random
 
def emotion_analyz(text):
    emotion_analyzer = MLAsk('-d "C:/Program Files/MeCab/dic/mecab-ipadic-neologd"')
    result = emotion_analyzer.analyze(text)
    if result['emotion'] == None:
        emolist = ["happy","relaxed"]
        emoweight = round(random.uniform(0.1, 0.5), 1)
        emo1 = random.choice(emolist)
        emo = emo1 + ","+ str(emoweight)
        print(emo)
    else:
        emo = result["representative"][0]
        if emo == "yorokobi" or "suki" or "takaburi":
            emo = "happy,1"
        elif emo == "ikari":
            emo = "angry,1"
        elif emo == "aware" or "iya":
            emo = "sad,1"
        elif emo == "haji" or "yasuragi":
            emo = "relaxed,1"
        elif emo == "odoroki":
            emo = "surprised,1"
        print(emo)
    return emo

"C:\convogpt_API\Unity_scripts\Local_API.py"を作成

from flask import Flask, request
import local_llm
import Textformat
import emotion_analyz
import configparser

llm = local_llm.GPTGenerator()
app = Flask("__init__")

@app.route("/", methods=['POST'])
def post():
    inputtext = request.form['inputtext']

    if inputtext != "":
        res1 = llm.generate(inputtext)
        if res1 == "":
            res = "sad,1,すみません。文章を生成できませんでした"
            res = res.encode('utf-8')
        else:
            res2 = Textformat.Textformat(res1)
            emo = emotion_analyz.emotion_analyz(res2)
            res2 = emo +","+  res2 
            res = res2.encode('utf-8')
    else:
        res = "sad,1,文章の入力がありません"
        res = res.encode('utf-8')

    return res

CONFIG_PATH = 'config.ini'
CONFIG_PARSER = configparser.ConfigParser()
CONFIG_PARSER.read(CONFIG_PATH,'utf-8')
CONFIG = CONFIG_PARSER['DEFAULT']
host = CONFIG['host']
port = CONFIG['port']

app.run(host, port)

"C:\convogpt_API\Unity_scripts\local_llm.py"を作成

from transformers import T5Tokenizer, AutoModelForCausalLM
import configparser #import  torch   ///#GPU利用時のみコメントアウト外す
 #torch .set_default_tensor_type('torch.cuda.FloatTensor')   ///#GPU利用時のみ

class GPTGenerator:
    def __init__(self):

        CONFIG_PATH = 'config.ini'
        CONFIG_PARSER = configparser.ConfigParser()
        CONFIG_PARSER.read(CONFIG_PATH,'utf-8')
        CONFIG = CONFIG_PARSER['DEFAULT']
        tokenizer_set = CONFIG['tokenizer']
        model_set = CONFIG['model']

        self.tokenizer = T5Tokenizer.from_pretrained(tokenizer_set)
        self.model = AutoModelForCausalLM.from_pretrained(model_set).eval()
        self.tokenizer.padding_side = "left"
        #if  torch.cuda.is_available():   ///#GPU利用時のみ
        #    self.model = self.model.to("cuda")   ///#GPU利用時のみ


    def generate(self, prompt: str):
        output = {}

        input_ids = self.tokenizer.encode(
            prompt,
            return_tensors='pt'
        )

        output = self.model.generate(
            input_ids=input_ids,
            do_sample=True,
            max_length=30,
            min_length=20,
            top_k=500,
            top_p=0.95,
            pad_token_id=self.tokenizer.pad_token_id,
        )[:, input_ids.shape[1]:]

        output = self.tokenizer.batch_decode(
            output,
            skip_special_tokens=True
        )
        return output


if __name__ == "__main__": 
    generator = GPTGenerator()
    text = "動作確認中。デバックです"
    print(generator.generate(text))

"C:\convogpt_API\Unity_scripts\Textformat.py"を作成

import functools
from ja_sentence_segmenter.common.pipeline import make_pipeline
from ja_sentence_segmenter.concatenate.simple_concatenator import concatenate_matching
from ja_sentence_segmenter.normalize.neologd_normalizer import normalize
from ja_sentence_segmenter.split.simple_splitter import split_newline, split_punctuation

split_punc2 = functools.partial(split_punctuation, punctuations=r"。!?")
concat_tail_te = functools.partial(concatenate_matching, former_matching_rule=r"^(?P<result>.+)(て)$", remove_former_matched=False)
segmenter = make_pipeline(normalize, split_newline, concat_tail_te, split_punc2)

def Textformat(text):
    res1 = "\n".join(text)
    res2 = list(segmenter(res1))
    res3 = res2[0]
    res = ''.join(res3)
    return res

if __name__ == "__main__": 
	print(Textformat("こんにちは"))

"C:\convogpt_API\Unity_scripts\config.ini"を作成。

[DEFAULT]
tokenizer = C:/convogpt_API/models/rinna/japanese-gpt-1b
model = C:/convogpt_API/models/rinna/japanese-gpt-1b
host = 127.0.0.1
port = 5000

"C:\convogpt_API\Unity_scripts\Local_API_Unity.bat"を作成。

call ..\env\Scripts\activate.bat & python "C:/convogpt_API/Unity_scripts/Local_API.py"
pause

こんな感じのファイル構成になると思います。

ここまでてきたらUnityと連携するだけです。『VRM_AI』を使う人はここまででOK。Local_API_Unity.batを実行して下さい。

自分でUnityと連携したい場合は以下参照(わかる人だけ読んでね)
EditorRunTerminal.cs

using UnityEngine;
using TMPro;
using UnityEngine.Networking;
using System.Collections;

public class EditorRunTerminal : MonoBehaviour
{
    private const string URL = "http://127.0.0.1:5000/";
    public TMP_InputField inputField;
    public static string Message;
    public static string Emo;
    public static float Emo_Weight;

    public void RunTerminal()
    {
        UImanager.thinking = true;
        StartCoroutine("OnSend", URL);
    }

    IEnumerator OnSend(string url)
    {
        WWWForm form = new WWWForm();
        form.AddField("generate_check", "on");
        form.AddField("inputtext", inputField.text);

        using UnityWebRequest webRequest = UnityWebRequest.Post(url, form);
        webRequest.downloadHandler = new DownloadHandlerBuffer();
        yield return webRequest.SendWebRequest();
        UnityEngine.Debug.Log(webRequest.downloadHandler.text);
        var responce = webRequest.downloadHandler.text;
        string[] res = responce.Split(',');
        Message = res[0];
        Emo = res[1];
        Emo_Weight = float.Parse(res[2]);
        LoadModel.CallVoice.Speak();
    }
}

上記はMLAskで分析した感情をVRM1.0のデフォルトExpression(表情セット)に置き換えるコードです。
MLAskの性質上、None率が結構高いので、ランダムでゆるく表情を変えています。

上記のコードを使えばUnity側で
Mesage:台詞
Emo:感情の種類
Emo_Weight:感情の重み

というデータを受け取れるので、これらをベースにUnityで表情制御します。
(そちらのコードは割愛)

ただ個人的なメモとして以下のことだけ覚書き。
VRM1.0をランタイムロードする時に自動で表情のオーバーライド設定を変えたいときは、Vrm10Importer.csを以下のように改変すればよい。
これでまばたきや口パクと表情の干渉が抑えられます。便利!

そんなこんなでこんな感じになりました。
うむ、性癖を詰め込んだ創作キャラは体にいい。


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