【話者分離】pyannote動作環境をEC2に構築してみた

# はじめに

「pyannote」を利用して、音声ファイルの話者分離できる環境を作ります。実際の文字起こしは「whisper」を利用します。

AWS環境にpyannoteの実行環境を構築するのに非常に苦戦したため、備忘録として記載します。

### 使用技術

話者分離：pyannote/speaker-diarization-3.0

文字起こし：whisper-1 (openAIのAPI利用)

実行環境サーバー：AWS EC2

### 前提条件

・openAIのAPIアクセストークンが取得済みであること。
・HuggingFaceのアカウントが取得済みであること

# 目次

1.事前準備

2.環境構築

3.ソースのデプロイ

4.コード実行

5.まとめ

それでは、実際の環境構築に進んでいきましょう！

# 1.事前準備

## HaggingFaceのトークン取得
- [huggingface](https://huggingface.co/)にログインし「Setting」->「AccessTokens」にアクセス
- 「New token」ボタンからアクセストークンを追加し取得（あとから参照不可のためこのタイミングでメモしておく）
- 権限は「READ」でOK

## 利用モデルの利用規約を許諾する

![スクリーンショット 2024-07-05 130101.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3831490/14ab1c70-1fb8-dc4c-6a5d-10537fa239e0.png)
「pyannote/speaker-diarization-3.0」モデルのページから抜粋
「2.」「3.」の手順を実施する

- 「pyannote/segmentation-3.0」のページへアクセスし、ユーザー利用条件の受け入れ（Accept）を実施
- 「pyannote/speaker-diarization-3.0」のページへアクセスし、ユーザー利用条件の受け入れ（Accept）を実施

# 2.環境構築

## EC2インスタンスの起動
- AWSコンソールから「EC2」サービスへアクセス
- 下図を参考にインスタンス起動
![スクリーンショット 2024-07-05 144355.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3831490/3ef6dde7-5b71-a423-0250-669f89144aec.png)

![スクリーンショット 2024-07-05 145116.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3831490/b4654291-dd69-6771-a6a9-03500e47ad0c.png)

- AMI:GPUは利用しないが「Deep Learning OSS Nvidia Driver AMI GPU PyTorch 2.3」を選択
- インスタンスタイプ：t2.large(メモリ8GBほしいので)
- ボリューム：70GiB(諸々のインストールで結構容量食う)

### 作成したインスタンスを選択し「インスタンスを開始」

SSHクライアントからインスタンスへ接続し下記のコマンドを実行

```bash:command
# Anacondaのインストール（未インストールの場合）
wget https://repo.anaconda.com/archive/Anaconda3-2023.09-0-Linux-x86_64.sh
bash Anaconda3-2023.09-0-Linux-x86_64.sh

# Condaの初期化
conda init
source ~/.bashrc
```

### 「environment.yaml」「requirements.txt」のアップロード
- FTPクライアントなどでインスタンスに接続し、
「environment.yaml」「requirements.txt」を、任意のディレクトリに配置
```yaml:environment.yaml
name: pyannote-audio
channels:
  - pytorch
  - conda-forge
  - defaults
dependencies:
  - python=3.9
  - pip
  - pytorch=2.0.0
  - torchaudio=2.0.0
  - torchvision=0.15.0
  - cudatoolkit=11.7
  - pip:
    - -r requirements.txt
```

```text:requirements.txt
asteroid-filterbanks >=0.4
einops >=0.6.0
huggingface_hub >= 0.13.0
lightning >= 2.0.1
omegaconf >=2.1,<3.0
pyannote.core >= 5.0.0
pyannote.database >= 5.0.1
pyannote.metrics >= 3.2
pyannote.pipeline >= 3.0.1
pytorch_metric_learning >= 2.1.0
rich >= 12.0.0
semver >= 3.0.0
soundfile >= 0.12.1
speechbrain >= 1.0.0
tensorboardX >= 2.6
torch >= 2.0.0
torch_audiomentations >= 0.11.0
torchaudio >= 2.2.0
torchmetrics >= 0.11.0
matplotlib==3.5.3
numpy==1.23.5
```
- 配置したディレクトリに移動

### 仮想環境の作成
```bash:command
conda env create -f environment.yaml
```

### 仮想環境を有効化
```bash:command
conda activate pyannote-audio
```
### pyannote.audioのインストール
```bash:command
pip install pyannote.audio==3.3.1
```
### コード実行に必要なその他のパッケージをインストール
```
pip install pydub flask python-dotenv polars openai tqdm onnxruntime
```
# 3.ソースのデプロイ

### 「app.py」と「.env」ファイルを任意のディレクトリにデプロイする

```python:app.py

from pyannote.audio import Pipeline
from pyannote.core import Segment, notebook, Annotation

from openai import OpenAI
from pydub import AudioSegment
from dotenv import load_dotenv
from huggingface_hub import login
from tqdm import tqdm

import os
import shutil
import polars as pl

load_dotenv()  # 環境変数をロード

token = os.getenv("HF_TOKEN")
file_name = "pyannote_test.wav"

if token:
    # トークンを設定
    login(token)
    print("Successfully logged in to Hugging Face")
else:
    print("HF_TOKEN environment variable not set")

print("話者分離処理_開始")
pipeline = Pipeline.from_pretrained("pyannote/speaker-diarization-3.0",
                                    use_auth_token=token)
diarization = pipeline(file_name, num_speakers=2)
print("話者分離処理_終了")

# Annotationオブジェクトをリストに変換
diarization_list = [
    {"start": segment.start, "end": segment.end, "label": track}
    for segment, _, track in diarization.itertracks(yield_label=True)
]

df = pl.DataFrame(diarization_list)

audio_segment: AudioSegment = AudioSegment.from_file(file_name, format="wav")

# OpenAIクライアントを作成
client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))

transcript_text = ""

# tempフォルダ内のファイルを一括削除
os.makedirs("temp", exist_ok=True)
shutil.rmtree("temp")

counter = 1  # 連番用カウンタ

# tqdmを使用して進捗バーを表示
for seg in tqdm(df.iter_rows(named=True), total=len(df)):

    # pyannote.audioの結果は秒、Pydubはミリ秒で範囲を指定するので、1000倍する
    start = seg["start"] * 1000
    end = seg["end"] * 1000

    # 開始から終了までの秒数が1秒未満の場合はスキップ
    if (end - start) < 1000:
        continue

    audio_file = audio_segment[start:end]

    # 一時ファイルに保存
    temp_audio_file_path = f"temp/{counter:04d}_segment.wav"
    audio_file.export(temp_audio_file_path, format="wav")

    # セグメントごとに文字起こしを行う
    transcription = client.audio.transcriptions.create(
        model="whisper-1",
        file=open(temp_audio_file_path, "rb"),
        response_format="verbose_json",
        timestamp_granularities=["word"]
    )
    transcript_text += seg["label"] + ":" + transcription.text + "\n"  # 'text'属性を使ってテキストを取得

    counter += 1  # カウンタをインクリメント

print("文字起こし処理_終了")

with open("transcript.txt", "w", encoding="utf-8") as f:
    f.write(transcript_text)
```
※変数「file_name」に、ご自身の環境で利用する音声ファイルの名称を指定してください

```text:.env
OPENAI_API_KEY="sk-proj-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
HF_TOKEN="hf_XXXXXXXXXXXXXXXXXXXXXXX"
```
※"XXXX…"の部分は自身の環境で利用するAPI_KEYの値を代入してください

### 「app.py」の処理概要
- tokenを使ってHuggingFaceへログイン
- pyannoteを利用して音声ファイルを解析し、話者分離情報（diarization）を生成
- polarsによりDataFlameに変換・話者分離情報を繰り返し処理し、切り出した音声ファイルをwhisper（openAI API）で文字起こし
- 「transcript.txt」に話者＋文字起こし内容を書き込む

# 4.コード実行

デプロイしたディレクトリに移動して、app.pyの実行
```bash:command
python app.py
```

いかがでしたでしょうか？話者分離＆文字起こしはできたでしょうか？

こちらの手順ではGPUではなくCPUを利用する想定のため、比較的処理に時間がかかります。
1時間の音声ファイルを処理するのに、1時間～2時間ほどかかるため、ご注意ください。

# 5.まとめ

EC2インスタンスにAIモデルをデプロイして利用するという体験自体初めてだったため、めちゃくちゃハマりました。とにかくパッケージ依存関係のエラーが解決できずに、数日頭を抱えていました。最終的には、公式のREADMEを参照したり、Claude(Sonnet 3.5)にエラー内容を投げ込んで得られた回答を試してみたりと……久しぶりに試行錯誤しながら今回の手順にたどり着きました。

今回得た対処ポイントは下記の通り・依存関係エラーはよく観察する・パッケージのインストールごとに依存関係を解決しようとするのではなく、一通りインストールしてから最後に「pip check」で確認し、それを解決する・各パッケージの公式ドキュメントも確認してみる・AIへの質問と実施、公式情報の参照を常に行ったり来たりする

今後もいろいろなAIモデルの利用環境構築に挑戦していきます！

# 6.参考記事
https://dev.classmethod.jp/articles/pyannote-intro/