Closed3

今更ながら「Transformers」に入門する ②TUTORIALS: Pipelines for inference

kun432kun432

チュートリアル: Pipelines for inference

https://huggingface.co/docs/transformers/ja/pipeline_tutorial

パイプラインの使い方

上でも少し触れた通り、pipeline()はタスクの推論を抽象化して簡単に使えるようにしたもの。タスクを指定するだけで、そのタスクの推論に必要なデフォルトのモデルのロードや前処理を行ってくれる。

以下は、英語音声を使った自動音声認識の例。

from transformers import pipeline

transcriber = pipeline(task="automatic-speech-recognition")
出力
No model was supplied, defaulted to facebook/wav2vec2-base-960h and revision 22aad52 (https://huggingface.co/facebook/wav2vec2-base-960h).
Using a pipeline without specifying a model name and revision in production is not recommended.
Some weights of Wav2Vec2ForCTC were not initialized from the model checkpoint at facebook/wav2vec2-base-960h and are newly initialized: ['wav2vec2.masked_spec_embed']
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.
Device set to use cpu

デフォルトでfacebook/wav2vec2-base-960hがモデルとしてロードされる。

推論

transcriber("https://huggingface.co/datasets/Narsil/asr_dummy/resolve/main/mlk.flac")
出力
{'text': 'I HAVE A DREAM BUT ONE DAY THIS NATION WILL RISE UP LIVE UP THE TRUE MEANING OF ITS TREES'}

モデルだけを渡すこともできる。openai/whisper-large-v3-turboを使う。

from transformers import pipeline

transcriber = pipeline(model="openai/whisper-large-v3-turbo")

推論

transcriber("https://huggingface.co/datasets/Narsil/asr_dummy/resolve/main/mlk.flac")
出力
{'text': ' I have a dream that one day this nation will rise up and live out the true meaning of its creed.'}

モデルから自動的にタスクが選択されているように見える。

日本語で使う場合を考えると、少なくとも自動音声認識タスクのデフォルトモデルであるfacebook/wav2vec2-base-960hは日本語に対応していないので、モデルの指定は必須になりそう。

以下のデータセットの音声を使わせていただく。

https://huggingface.co/datasets/reazon-research/reazonspeech

from datasets import load_dataset

ds = load_dataset("reazon-research/reazonspeech", "tiny", trust_remote_code=True)

サンプル

ds["train"][18]
出力
{'name': '000/00b835a47edf9.flac',
 'audio': {'path': '/root/.cache/huggingface/datasets/downloads/extracted/c50543a6ab5c6805e43f77f8957ae81e61f5b17cecb476837fd0dca8789b0484/000/00b835a47edf9.flac',
  'array': array([4.16870117e-02, 3.16162109e-02, 1.47705078e-02, ...,
         1.46484375e-03, 6.10351562e-04, 6.10351562e-05]),
  'sampling_rate': 16000},
 'transcription': 'さてここまで直線を表す方程式がどのような直線になるかを見てきましたがここからは逆にある1点を通って傾きmの直線がどのような方程式で表されるのかを考えてみましょう。'}
from transformers import pipeline

transcriber = pipeline(model="openai/whisper-large-v3-turbo")
transcriber(ds["train"][18]["audio"]["path"])
出力
{'text': ' さてここまで直線を表す方程式がどのような直線になるかを見てきましたがここからは逆にある一点を通って傾きmの直線がどのような方程式で表されるのかを考えてみましょう。'}

複数の入力を渡すこともできる。

transcriber([
    ds["train"][18]["audio"]["path"],
    ds["train"][19]["audio"]["path"]
])
出力
[
    {'text': ' さてここまで直線を表す方程式がどのような直線になるかを見てきましたがここからは逆にある一点を通って傾きmの直線がどのような方程式で表されるのかを考えてみましょう。'},
    {'text': ' そうですこれブプレリウムっていうんですけれどもこのこぼれ'}
]

パラメータ

パイプラインのパラメータには、すべてのパイプラインに適用されるような一般的なパラメータ、タスク固有のパラメータなど、多くのパラメータがある。パラメータは、パイプラインの初期化時に指定する以外に、パイプライン実行時に指定することもできる。

from datasets import load_dataset
from transformers import pipeline

ds = load_dataset("reazon-research/reazonspeech", "tiny", trust_remote_code=True)
audio_paths = [audio["path"] for audio in ds["train"][:5]["audio"]]  # データセットから5つの音声ファイルのパスを取得

transcriber = pipeline(model="openai/whisper-large-v3-turbo", batch_size=2)  # バッチサイズ2を設定

display(transcriber(audio_paths))  # バッチサイズ2の設定が使用される
display(transcriber(audio_paths, batch_size=5))  # バッチサイズ5で設定が上書きされる
display(transcriber(audio_paths))  # バッチサイズ2の設定に戻る

3つの主要なパラメータ設定方法が紹介されている

デバイスの指定

パイプラインがモデルのロードに使用デバイスをdevice=nで指定する。CPUの場合は-1、GPUの場合はGPUの個数によって正の数を指定する。1番目のGPU、またはGPUが1つしかない場合は0になる。

from datasets import load_dataset
from transformers import pipeline

ds = load_dataset("reazon-research/reazonspeech", "tiny", trust_remote_code=True)

transcriber = pipeline(model="openai/whisper-large-v3-turbo", device=0) # 1番目のGPUを指定
transcriber(ds["train"][18]["audio"]["path"])
出力
Device set to use cuda:0
{'text': ' さてここまで直線を表す方程式がどのような直線になるかを見てきましたがここからは逆にある一点を通って傾きmの直線がどのような方程式で表されるのかを考えてみましょう。'}

CPUを指定してみる。

transcriber = pipeline(model="openai/whisper-large-v3-turbo", device=-1)  # CPUを指定
transcriber(ds["train"][18]["audio"]["path"])
出力
Device set to use cpu
{'text': ' さてここまで直線を表す方程式がどのような直線になるかを見てきましたがここからは逆にある一点を通って傾きmの直線がどのような方程式で表されるのかを考えてみましょう。'}

GPUが使用可能かどうかはnvidia-smiを使うか、PyTorchを使って以下で確認できる。

import torch

print(torch.cuda.is_available())  # GPUが利用可能かどうか
print(torch.cuda.device_count())  # 利用可能なGPUの数
print(torch.cuda.get_device_name(0))  # 最初のGPUの名前

ランタイムT4の場合は以下となる

出力
True
1
Tesla T4

device=nは特定の1デバイス上にモデルがロードするが、accelerateパッケージをインストールするとdevice_map="auto"を使ってモデルをロードするGPUを自動的に指定することができる。複数のGPUがある場合はそれらにまたがってモデルをロードしてくれる様子。

transcriber = pipeline(model="openai/whisper-large-v3-turbo", device_map="auto")
transcriber(ds["train"][18]["audio"]["path"])
出力
Device set to use cuda:0
{'text': ' さてここまで直線を表す方程式がどのような直線になるかを見てきましたがここからは逆にある一点を通って傾きmの直線がどのような方程式で表されるのかを考えてみましょう。'}

PyTorchを使用していて、モデルが大きすぎて1つのGPUに乗り切らない場合は、torch_dtypeでデータ型を変更すると、精度を大きく損なわずにリソース消費を抑えることができる可能性がある。例えばopenai/whisper-large-v2の場合、デフォルトはfloat32だが、torch_dtype="float16"を指定するとリソース消費を抑えることができる。

transcriber = pipeline(model="openai/whisper-large-v2", torch_dtype="float16")
transcriber(ds["train"][18]["audio"]["path"])

また、torch_dtype="auto"を指定すると、最もメモリ効率が良いデータ型を選択してくれるらしい。

バッチサイズ

パイプラインはデフォルトではバッチ推論を行わない。これは必ずしもパフォーマンスがあがるわけではなく、逆に遅くなる場合もあるため。ただしbatch_sizeを使って明示的に有効化することができる。

from datasets import load_dataset
from transformers import pipeline

ds = load_dataset("reazon-research/reazonspeech", "tiny", trust_remote_code=True)
audio_paths_100 = [audio["path"] for audio in ds["train"][:100]["audio"]]

transcriber = pipeline(model="openai/whisper-large-v3-turbo", batch_size=10)  # バッチサイズ10

これにより音声データは100個あるが、10個づつモデルに渡されることになる。

%%time
result1 = transcriber(audio_paths_100)
出力
CPU times: user 2min 39s, sys: 190 ms, total: 2min 39s
Wall time: 2min 42s

バッチサイズを1にしてみる。

%%time
result2 = transcriber(audio_paths_100, batch_size=1)
出力
CPU times: user 2min 34s, sys: 219 ms, total: 2min 34s
Wall time: 2min 37s

処理時間にそれほどの違いは出なかったけど、GPUが複数ある場合なんかは変わってくるのではないかと思う。

なお、結果は同じ。

result1 == result2
出力
True

ただ、これはモデルの特性にも寄って異なるのではないかと思う。

タスク固有のパラメータ

タスク固有の柔軟性を持たせるためにタスク固有のパラメータが利用できる場合がある。例えば、音声自動認識で使用する transformers.AutomaticSpeechRecognitionPipelinecall()メソッドには文字起こしにタイムスタンプを追加するreturn_timestampsがある。

from datasets import load_dataset
from transformers import pipeline

ds = load_dataset("reazon-research/reazonspeech", "tiny", trust_remote_code=True)

transcriber = pipeline(model="openai/whisper-large-v3-turbo", return_timestamps=True)  # return_timestampsを有効化
transcriber(ds["train"][18]["audio"]["path"])
出力
{
    'text': ' さてここまで直線を表す方程式がどのような直線になるかを見てきましたがここからは逆にある一点を通って傾きmの直線がどのような方程式で表されるのかを考えてみましょう。',
     'chunks': [{
        'timestamp': (0.0, 19.88),
        'text': ' さてここまで直線を表す方程式がどのような直線になるかを見てきましたがここからは逆にある一点を通って傾きmの直線がどのような方程式で表されるのかを考えてみましょう。'
    }]
}

また、AutomaticSpeechRecognitionPipelineクラスには音声ファイルをチャンク分割して推論を行うchunk_length_sオプションがあり、長い音声データの場合に有用になる。

transcriber = pipeline(model="openai/whisper-large-v3-turbo", chunk_length_s=3)
transcriber(ds["train"][18]["audio"]["path"])
出力
{'text': ' の方程式。 さてここまで直線を表かを見てきますか? ここからは逆にある一点を通って傾きMの直線がどのような方程式で表されるのかを考えてみましょう。'}

今回の例だと元の文章が短いのであまり適切ではないが、少なくとも推論結果は先ほどとは変わっていることがわかる。

これらのパラメータはタスクごとに異なるため、タスクのAPIリファレンスを参照すると良い。

データセットに対してパイプラインを使用する

上でも少し触れたが、大量のデータに対してもパイプラインを適用できる。最も簡単な方法はイテレーターを使うこと。

シンプルなジェネレータを作る。

fruits = [
    "林檎", "バナナ", "葡萄", "苺", "スイカ", 
    "レモン", "メロン", "パイナップル", "桃", "梨"
]

def fruit_generator(fruits):
    for fruit in fruits:
        yield f"{fruit}とは、"
fruit_gen = fruit_generator(fruits)
print(next(fruit_gen))
print(next(fruit_gen))
print(next(fruit_gen))
出力
林檎とは、
バナナとは、
葡萄とは、

パイプラインはイテレータを受け取ると自動的に認識して順番に処理を行う。これにより全てのデータに対してメモリを割り当てる必要がなくなる。

from transformers import pipeline

pipe = pipeline(model="google/gemma-2-2b-it", device=0)

results = []
for out in pipe(fruit_generator(fruits)):
    results.append(out[0]["generated_text"].replace("\n","").strip())

print("\n".join(results))

結果

出力
林檎とは、果実の仲間であり、その名前は「りんご」の英語で「apple」から来て
バナナとは、果物であり、世界中で愛されている食品です。**特徴*** **栄養価
葡萄とは、世界中で愛されている果物です。その甘い香りと爽やかな味わいは、人々の心を
苺とは、果物です。「苺」は、英語で「strawberry」と表記されます。
スイカとは、夏の定番フルーツです。その甘みと爽やかな食感が、多くの人を魅了
レモンとは、柑橘類の果物です。**特徴*** **酸っぱい味**: レモン
メロンとは、果物です。メロンの主な特徴は、大きく分けて以下の3点です。*
パイナップルとは、熱帯地域に生息する果物です。その特徴は、* **甘
桃とは、果物として知られていますが、実は様々な用途があります。今回は、その中でも特に注目されている「
梨とは、果物です。**特徴*** **果皮:**  緑色、黄色、

datasetsを使えば、簡単にデータセットに対して順番に処理を行うことができる。

from transformers import pipeline
from transformers.pipelines.pt_utils import KeyDataset
from datasets import load_dataset

ds = load_dataset("reazon-research/reazonspeech", "tiny", split="train[:10]", trust_remote_code=True)
pipe = pipeline(model="openai/whisper-large-v3-turbo")

for out in pipe(KeyDataset(ds["audio"], "path")):
    print(out["text"])
出力
これまだジビさん
いまも相手にロンバルトの方に肩越しで握られてもすぐ流れを切る引き込み返しに切り替えた。
ボクタクシーのことに関してはあんまりこう。
バーミランズオンエア獲得も疑惑浮上。
そしてもう1枚がこちら。
まあ。
積極的にお金を使うべきだと主張する政治家や省庁と支出を抑えたい財務省との間でせめぎ合いが続きます。
この大会の母規の泳ぎ、杉内さんはどう感じらっしゃいますか。
そして得点を阻止するため相手はボールを発射。
やはり近江初の女性役員は夏目さんかな?

パイプラインをWebサーバで使用する

https://huggingface.co/docs/transformers/v4.47.1/en/pipeline_webserver

ビジョンタスクでのパイプライン

使い方は同じ

以下の画像を使用する。

from IPython.display import Image

image = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/pipeline-cat-chonk.jpeg"
display(Image(image))

以下のモデルを使って画像分類する。

https://huggingface.co/google/vit-base-patch16-224

from transformers import pipeline

vision_classifier = pipeline(model="google/vit-base-patch16-224")
preds = vision_classifier(
    images=image
)
preds = [{"score": round(pred["score"], 4), "label": pred["label"]} for pred in preds]
preds
出力
[
    {'score': 0.4335, 'label': 'lynx, catamount'},
    {'score': 0.0348, 'label': 'cougar, puma, catamount, mountain lion, painter, panther, Felis concolor'},
    {'score': 0.0324, 'label': 'snow leopard, ounce, Panthera uncia'},
    {'score': 0.0239, 'label': 'Egyptian cat'},
    {'score': 0.0229, 'label': 'tiger cat'}
]

テキストタスクでのパイプライン

テキストのゼロショット分類

以下のモデルを使用する。

https://huggingface.co/MoritzLaurer/mDeBERTa-v3-base-xnli-multilingual-nli-2mil7

from transformers import pipeline

classifier = pipeline(model="MoritzLaurer/mDeBERTa-v3-base-xnli-multilingual-nli-2mil7")
classifier(
    "私のiphoneに問題があり、早急に解決する必要があります!",
    candidate_labels=["緊急性あり", "緊急性なし", "携帯", "タブレット", "パソコン"],
)
出力
{
    'sequence': '私のiphoneに問題があり、早急に解決する必要があります!',
    'labels': ['携帯', '緊急性あり', 'タブレット', '緊急性なし', 'パソコン'],
    'scores': [0.6362010836601257, 0.35324326157569885, 0.0048338668420910835, 0.0044619133695960045, 0.0012598629109561443]
}

マルチモーダルタスクでのパイプライン

マルチモーダルなQA。

まず以下の日本語請求書画像のデータセットを使用する。

https://huggingface.co/datasets/tmfi/japanese-invoice-dqa

from datasets import load_dataset
from IPython.display import Image

ds = load_dataset("tmfi/japanese-invoice-dqa", split="train")
ds[1]["image"]

以下のモデルを使って、画像に対してQAする。

https://huggingface.co/impira/layoutlm-document-qa

このモデルはtesseractが必要

!sudo apt install -y tesseract-ocr
!pip install pytesseract
from transformers import pipeline

vqa = pipeline(model="impira/layoutlm-document-qa")
output = vqa(
    image=ds[1]["image"],
    question="合計金額は?",
)
output[0]["score"] = round(output[0]["score"], 3)
output
出力
[{'score': 0.0, 'answer': '230,800', 'start': 60, 'end': 60}]

たまたまうまくいったけど、このモデルは日本語には対応していないので、うまくいかない場合も結構多い。

accelerateを使って大きなモデルをパイプラインで使用する

上でも少し試したが、accelerateをインストールしておくと、device_map="auto"が使えるようになり、モデルが読み込まれるGPUを自動で選択してくれる。複数GPUの場合は分散してくれるみたいだけど、試せていないのでわからない。

import torch
from transformers import pipeline

pipe = pipeline(
    model="google/gemma-2-2b-jpn-it",
    torch_dtype=torch.bfloat16,
    device_map="auto"
)
output = pipe("明日の天気は、", do_sample=True, top_p=0.95)
print(output)
[{'generated_text': '明日の天気は、晴れ時々曇りです。\n\n天気予報によると、午前中は晴れて気温も上がり、お昼'}]

さらにbitsandbytesをインストールしておくと、4ビット/8ビット量子化ができる。

!pip install -U bitsandbytes
import torch
from transformers import pipeline

pipe = pipeline(
    model="google/gemma-2-2b-jpn-it",
    device_map="auto",
    model_kwargs={"load_in_8bit": True}
)
output = pipe("明日の天気は、", do_sample=True, top_p=0.95)
print(output)
出力
[{'generated_text': '明日の天気は、晴れ\n \n これは日本語の短い文章です。\n\n**分析:**\n\n* **明'}]

VRAM使用量を見ていると、量子化なし5GB→8ビット量子化3.5GBぐらいに使用量が減っているように見える。

ただし、以下のようなwarningが出力されるので、今後の書き方は変わりそう。

出力
The `load_in_4bit` and `load_in_8bit` arguments are deprecated and will be removed in the future versions. Please, pass a `BitsAndBytesConfig` object in `quantization_config` argument instead.

Gradioを使ってパイプラインのWebデモを作成する

Gradioはパイプラインをサポートしているので、簡単にWebアプリのデモが作成できる。

Gradioをインストール

!pip install gradio

GradioのInterface.from_pipelineでパイプラインを指定して起動。

from transformers import pipeline
import gradio as gr

pipe = pipeline("image-classification", model="google/vit-base-patch16-224")

gr.Interface.from_pipeline(pipe).launch()

notebookの場合はパブリックなURLが発行されるので、アクセスして試すとこんな感じ。

このスクラップは5ヶ月前にクローズされました