litagin/anime_speech_emotion_classificationを使って音声の感情を判定する

初めに

以下でも音声ファイルの感情判定を行っていますが、こちらとは違うモデルを使って判定を行っていきます

ayousanz.hatenadiary.jp

開発環境

環境構築

以下で必要なライブラリをインストールします

uv pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124
uv pip install transformers huggingface_hub librosa soundfile

感情の判定

以下のコードをファイルを指定して実行します

import argparse
import os
import torch
import torchaudio
import librosa
import numpy as
import torch.nn.functional as F
from transformers import AutoModelForAudioClassification, AutoFeatureExtractor
from huggingface_hub import HfFolder
import warnings

warnings.filterwarnings("ignore")

def analyze_anime_emotion(audio_file_path: str):
    """
    litagin/anime_speech_emotion_classificationモデルを使用して音声ファイルの感情を分析する。
    (librosaによる音声読込強化版)
    """
    audio_file_path = os.path.abspath(audio_file_path)

    if not os.path.exists(audio_file_path):
        print(f"エラー: 指定されたファイルが見つかりません。")
        print(f"パスを確認してください: {audio_file_path}")
        return

    print("--- 環境・モデル情報 ---")
    print(f"ライブラリ: transformers (手動制御)")
    
    model_id = "litagin/anime_speech_emotion_classification"
    print(f"モデル: {model_id}")

    try:
        device = "cuda:0" if torch.cuda.is_available() else "cpu"
        print(f"使用デバイス: {device}")
        
        hf_token = HfFolder.get_token()
        if hf_token is None:
            print("\nエラー: Hugging Faceの認証トークンが見つかりません。`huggingface-cli login` を実行してください。")
            return

        print("モデルをロードしています...")
        feature_extractor = AutoFeatureExtractor.from_pretrained(model_id, token=hf_token, trust_remote_code=True)
        model = AutoModelForAudioClassification.from_pretrained(model_id, token=hf_token, trust_remote_code=True).to(device)
        model.eval() 
        
        print("モデルのロードが完了しました。")
        
    except Exception as e:
        print(f"モデルのロード中にエラーが発生しました: {e}")
        return


    print(f"\n音声ファイル '{audio_file_path}' を読み込んでいます...")
    try:
        waveform_np, original_sample_rate = librosa.load(audio_file_path, sr=None, mono=False)
        
        # NumPy配列をPyTorchテンソルに変換
        waveform = torch.from_numpy(waveform_np)

        # librosaがモノラルで1次元配列を返した場合、2次元に変換
        if waveform.ndim == 1:
            waveform = waveform.unsqueeze(0)
        
        # チャンネルが複数ある場合、モノラルに変換
        if waveform.shape[0] > 1:
            waveform = torch.mean(waveform, dim=0, keepdim=True)
        
        # モデルが要求するサンプリングレート(16kHz)に変換
        target_sample_rate = 16000 
        if original_sample_rate != target_sample_rate:
            # librosaはNumPy配列を扱うため、一度テンソルに変換してからリサンプル
            resampler = torchaudio.transforms.Resample(orig_freq=original_sample_rate, new_freq=target_sample_rate)
            waveform = resampler(waveform)
            
    except Exception as e:
        print(f"音声ファイルの読み込みまたは処理中にエラーが発生しました: {e}")
        return

    # 推論の実行
    print("音声の感情を判定しています...")
    try:
        inputs = feature_extractor(
            waveform.squeeze(0).numpy(), 
            sampling_rate=target_sample_rate,
            return_tensors="pt"
        ).to(device)

        with torch.no_grad():
            outputs = model(**inputs)
        
        logits = outputs.logits
        probabilities = F.softmax(logits, dim=1)[0]
        
        print("\n" + "="*40)
        print("  感情判定結果")
        print("="*40)
        
        sorted_probs, sorted_ids = torch.sort(probabilities, descending=True)
        
        top_prediction_id = sorted_ids[0].item()
        top_label = model.config.id2label[top_prediction_id]
        top_score = sorted_probs[0].item()

        print(f"  ファイル: {os.path.basename(audio_file_path)}")
        print(f"  最も可能性の高い感情: {top_label} ({top_score:.2%})")
        
        print("\n  --- 各感情の確率 ---")
        for i in range(len(sorted_probs)):
            label_id = sorted_ids[i].item()
            label_name = model.config.id2label[label_id]
            score = sorted_probs[i].item()
            print(f"  - {label_name:<12}: {score:.2%}")
        
        print("="*40)

    except Exception as e:
        print(f"\n推論中に予期せぬエラーが発生しました: {e}")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description="アニメ風音声の感情認識モデルを使用して、感情を分析します。"
    )
    parser.add_argument(
        "audio_file",
        type=str,
        help="分析したい音声ファイルのパス。"
    )
    
    args = parser.parse_args()
    
    analyze_anime_emotion(args.audio_file)

以下のように実行します

python .\anime_emotion_recognition.py '.\Kanjyou Ikari96.wav'

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

========================================
  感情判定結果
========================================
  ファイル: Kanjyou Ikari96.wav
  最も可能性の高い感情: Angry (38.79%)

  --- 各感情の確率 ---
  - Angry       : 38.79%
  - Happy       : 28.14%
  - Sexual1     : 8.57%
  - Surprised   : 8.50%
  - Embarrassed : 7.67%
  - Sad         : 3.16%
  - Neutral     : 3.14%
  - Disgusted   : 1.43%
  - Fearful     : 0.57%
  - Sexual2     : 0.02%
========================================