YoichiTakenaka/deverta-v3-japanese-large-Anticipationでテキストの感情を判定する

開発環境

環境構築

以下で環境構築をして、必要なライブラリをインストールします

uv venv
.\.venv\Scripts\activate
uv pip install torch --index-url https://download.pytorch.org/whl/cu124
uv pip install transformers pandas deberta-emotion-predictor

ラベル判定

以下のように引数から指定したテキストを判定するコードを作成します

import argparse
import torch
from deberta_emotion_predictor import DeBERTaEmotionPredictor
import warnings
import os
import sys

class EmotionAnalyzer:
    """
    日本語テキストの感情分析を行うための本番用クラス。
    モデルのロードを一度だけ行い、複数のテキストを効率的に処理します。
    """
    def __init__(self, device: str = None, verbose: bool = True):
        """
        アナライザーを初期化し、モデルをメモリにロードします。
        
        Args:
            device (str, optional): 使用するデバイス ('cuda' or 'cpu')。Noneの場合は自動検出。
            verbose (bool, optional): モデルロード時にメッセージを表示するかどうか。
        """
        self.verbose = verbose
        if self.verbose:
            print("--- 感情分析モデルの準備を開始 ---")
            
        try:
            if device is None:
                self.device = "cuda" if torch.cuda.is_available() else "cpu"
            else:
                self.device = device
            
            if self.verbose:
                print(f"使用デバイス: {self.device}")
                print("モデルをロードしています...(初回はダウンロードに時間がかかります)")
                
            self.predictor = DeBERTaEmotionPredictor(device=self.device)
            
            # 感情ラベルと確率カラム名をクラス変数として定義
            self.emotion_labels = ['Joy', 'Sadness', 'Anticipation', 'Surprise', 'Anger', 'Fear', 'Disgust', 'Trust']
            self.prob_columns = [f"{label}_positive_probability" for label in self.emotion_labels]

            if self.verbose:
                print("--- モデル準備完了 ---")

        except Exception as e:
            print(f"モデルの初期化中に致命的なエラーが発生しました: {e}", file=sys.stderr)
            # モデルロードの失敗は致命的なので、プログラムを終了させる
            raise

    def analyze(self, text: str) -> list[tuple[str, float]]:
        """
        単一のテキストから感情を分析し、結果を確率の高い順に返します。

        Args:
            text (str): 分析したい日本語の文章。

        Returns:
            list[tuple[str, float]]: (感情ラベル, 確率) のタプルが格納されたリスト。
                                      例: [('Joy', 0.98), ('Trust', 0.01), ...]
        """
        if not isinstance(text, str) or not text.strip():
            return []
            
        try:
            results_df = self.predictor.predict_emotions(text)
            
            if results_df.empty:
                return []
            
            result_series = results_df.iloc[0]
            
            # 扱いやすいように、簡単な感情名と確率のペアを作成
            emotion_probabilities = {}
            for label, col_name in zip(self.emotion_labels, self.prob_columns):
                emotion_probabilities[label] = result_series[col_name]

            # 確率の高い順にソートしてリストとして返す
            sorted_results = sorted(emotion_probabilities.items(), key=lambda item: item[1], reverse=True)
            return sorted_results

        except Exception as e:
            print(f"テキスト「{text[:30]}...」の推論中にエラーが発生しました: {e}", file=sys.stderr)
            return []

def main():
    """
    コマンドラインからテキストを受け取り、感情分析を実行して結果を表示するメイン関数。
    """
    parser = argparse.ArgumentParser(
        description="日本語テキストから8つの感情を分析します(本番用コード)。",
        formatter_class=argparse.RawTextHelpFormatter # ヘルプの改行を保持
    )
    parser.add_argument(
        "text",
        nargs='*', # 複数のテキスト引数を受け取れるようにする
        default=[],
        help="分析したい日本語の文章。複数指定可能。\n例: \"これは嬉しい\" \"なんてことだ\""
    )
    parser.add_argument(
        "-f", "--file",
        type=str,
        help="テキストが1行ずつ書かれたファイルへのパス。\nファイル内の各行を個別に分析します。"
    )

    args = parser.parse_args()
    
    # テキストが一つも指定されなかった場合は使い方を表示して終了
    if not args.text and not args.file:
        parser.print_help()
        print("\nエラー: 分析対象のテキストを引数で指定するか、--fileオプションでファイルを指定してください。")
        return

    try:
        analyzer = EmotionAnalyzer()
    except Exception:
        print("アナライザーの起動に失敗したため、処理を終了します。", file=sys.stderr)
        return

    texts_to_process = args.text
    if args.file:
        try:
            with open(args.file, 'r', encoding='utf-8') as f:
                # ファイルから読み込んだ各行の改行文字などを除去し、空行は無視する
                texts_to_process.extend([line.strip() for line in f if line.strip()])
        except FileNotFoundError:
            print(f"エラー: 指定されたファイルが見つかりません: {args.file}", file=sys.stderr)
            return
    
    # 2. テキストごとに感情を分析し、結果を表示
    for i, text in enumerate(texts_to_process):
        print("\n" + "#"*50)
        print(f"分析対象 {i+1}: 「{text}」")
        print("#"*50)
        
        results = analyzer.analyze(text)
        
        if results:
            top_label, top_score = results[0]
            print(f"  最も可能性の高い感情: {top_label} ({top_score:.2%})")
            
            print("\n  --- 各感情の確率 ---")
            for label, score in results:
                print(f"  - {label:<15}: {score:.2%}")
        else:
            print("  感情を判定できませんでした。")
        print("#"*50)

if __name__ == '__main__':
    main()

以下のように実行します

python .\text_emotion_recognition_koala.py "ストラッ
トフォード・オン・エイヴォンは、シェイクスピアの生まれたところですが、毎年多くの観光客が訪れます。"

結果は以下のようになります

##################################################
分析対象 1: 「ストラットフォード・オン・エイヴォンは、シェイクスピアの生まれたところですが、毎年多くの観光客が訪れます。」
##################################################
感情:Joy の推論を開始 (1/1)
感情:Sadness の推論を開始 (1/1)
感情:Anticipation の推論を開始 (1/1)
感情:Surprise の推論を開始 (1/1)
感情:Anger の推論を開始 (1/1)
感情:Fear の推論を開始 (1/1)
感情:Disgust の推論を開始 (1/1)
感情:Trust の推論を開始 (1/1)
  最も可能性の高い感情: Surprise (59.25%)

  --- 各感情の確率 ---
  - Surprise       : 59.25%
  - Anticipation   : 2.84%
  - Joy            : 0.57%
  - Fear           : 0.43%
  - Sadness        : 0.30%
  - Disgust        : 0.23%
  - Trust          : 0.12%
  - Anger          : 0.01%
##################################################