これは、なにをしたくて書いたもの?
Transformersでテキスト生成ができそうだったので、こちらをTransformersの足がかりとして試してみたいと思います。
Transformersでのテキスト生成
テキスト生成には、Transformersでできることに挙げられています。
Task Guides内でどこにあたるのかがちょっとわかりにくかったのですが、こちらのようです。
テキスト生成ができるモデルとしては、以下が挙がっています。
Some of the models that can generate text include GPT2, XLNet, OpenAI GPT, CTRL, TransformerXL, XLM, Bart, T5, GIT, Whisper.
今回は、GPT2を使うことにしましょう。
事前学習済みのモデルとしては、日本語に対応していそうな次の2つで試してみることにします。
abeja/gpt2-large-japanese · Hugging Face
rinna/japanese-gpt2-medium · Hugging Face
環境
今回の環境は、こちらです。
$ python3 --version Python 3.10.12 $ pip3 --version pip 22.0.2 from /usr/lib/python3/dist-packages/pip (python 3.10)
実行環境は、CPUのみの環境です。
abeja/gpt2-large-japaneseで試す
まずは、abeja/gpt2-large-japaneseで試してみたいと思います。
仮想環境の有効化。
$ python3 -m venv venv $ . venv/bin/activate
Transformersのインストールページとabeja/gpt2-large-japaneseのページに習って、ライブラリーのインストール。
$ pip3 install transformers[torch,sentencepiece]
abeja/gpt2-large-japanese · Hugging Face
なんか、衝撃のサイズになりました…。こういうものなんでしょうか…?
$ du -sh venv 4.7G venv
インストールされたライブラリー。
$ pip3 list Package Version ------------------------ ---------- accelerate 0.25.0 certifi 2023.11.17 charset-normalizer 3.3.2 filelock 3.13.1 fsspec 2023.12.2 huggingface-hub 0.20.1 idna 3.6 Jinja2 3.1.2 MarkupSafe 2.1.3 mpmath 1.3.0 networkx 3.2.1 numpy 1.26.2 nvidia-cublas-cu12 12.1.3.1 nvidia-cuda-cupti-cu12 12.1.105 nvidia-cuda-nvrtc-cu12 12.1.105 nvidia-cuda-runtime-cu12 12.1.105 nvidia-cudnn-cu12 8.9.2.26 nvidia-cufft-cu12 11.0.2.54 nvidia-curand-cu12 10.3.2.106 nvidia-cusolver-cu12 11.4.5.107 nvidia-cusparse-cu12 12.1.0.106 nvidia-nccl-cu12 2.18.1 nvidia-nvjitlink-cu12 12.3.101 nvidia-nvtx-cu12 12.1.105 packaging 23.2 pip 22.0.2 protobuf 4.25.1 psutil 5.9.7 PyYAML 6.0.1 regex 2023.12.25 requests 2.31.0 safetensors 0.4.1 sentencepiece 0.1.99 setuptools 59.6.0 sympy 1.12 tokenizers 0.15.0 torch 2.1.2 tqdm 4.66.1 transformers 4.36.2 triton 2.1.0 typing_extensions 4.9.0 urllib3 2.1.0
最初はpipelineを使って作成してみます。
以下のソースコードで、「日本の首都は」の続きを生成してもらいましょう。
text_generation_pipeline.py
import time from transformers import pipeline start_time = time.perf_counter() generator = pipeline("text-generation", model="abeja/gpt2-large-japanese") outputs = generator("日本の首都は") elapsed_time = time.perf_counter() - start_time print(outputs) print() print(f"elapsed time = {elapsed_time:.3f} sec")
実行結果はこちら。初回はモデルのダウンロードが行われるので、時間がかかります。このモデルは約3GBでした。
$ python3 text_generation_pipeline.py You are using the default legacy behaviour of the <class 'transformers.models.t5.tokenization_t5.T5Tokenizer'>. This is expected, and simply means that the `legacy` (previous) behavior will be used so nothing changes for you. If you want to use the new behaviour, set `legacy=False`. This should only be set if you understand what it means, and thouroughly read the reason why this was added as explained in https://github.com/huggingface/transformers/pull/24565 Setting `pad_token_id` to `eos_token_id`:2 for open-end generation. [{'generated_text': '日本の首都は東京であり、これを中心とした地域である。 この区分を、地方行政における「広域地方行政単位」に適用する。 広域地方行政単位は、複数の地方公共団体で構成される。 現在の日本の広域地方行政単位は、旧町村の'}] elapsed time = 43.904 sec
東京と出てきましたが、この結果は実行する度に変化します。東京ではない海外の都市を答えることの方がむしろ多かったです。
$ python3 text_generation_pipeline.py You are using the default legacy behaviour of the <class 'transformers.models.t5.tokenization_t5.T5Tokenizer'>. This is expected, and simply means that the `legacy` (previous) behavior will be used so nothing changes for you. If you want to use the new behaviour, set `legacy=False`. This should only be set if you understand what it means, and thouroughly read the reason why this was added as explained in https://github.com/huggingface/transformers/pull/24565 Setting `pad_token_id` to `eos_token_id`:2 for open-end generation. [{'generated_text': '日本の首都はローマ(正確には、アッシジの旧市街とパレルモの間)にあるのだが、この旧市街はローマ帝国や東方拡大とともに徐々に衰退したのである。それでも、ローマの旧市街は、 ローマの時代から、ヨーロッパでは非常に'}] elapsed time = 47.745 sec
続いては、Auto Classesを使ってみます。
text_generation_autoclasses.py
import time from transformers import AutoModelForCausalLM, AutoTokenizer start_time = time.perf_counter() tokenizer = AutoTokenizer.from_pretrained("abeja/gpt2-large-japanese") model = AutoModelForCausalLM.from_pretrained("abeja/gpt2-large-japanese") inputs = tokenizer("日本の首都は", return_tensors="pt") outputs = model.generate( **inputs, max_new_tokens=15, pad_token_id=tokenizer.pad_token_id ) generated_texts = tokenizer.batch_decode(outputs, skip_special_tokens=True) elapsed_time = time.perf_counter() - start_time print(generated_texts) print() print(f"elapsed time = {elapsed_time:.3f} sec")
使っているのは、AutoTokenizer
とAutoModelForCausalLM
です。
Auto Classes / Natural Language Processing / AutoModelForCausalLM
ソースコードは、こちらを参考に作成しています。
実行結果。頑なに日本の首都の続きをロンドンとして生成しようとするのですが、pipelineを使った時と比べてパラメーターが違うのだと
思うのですが…。
※temperature
を変えたらいいのかなとも思いますが、今回は試しませんでした
$ python3 text_generation_autoclasses.py You are using the default legacy behaviour of the <class 'transformers.models.t5.tokenization_t5.T5Tokenizer'>. This is expected, and simply means that the `legacy` (previous) behavior will be used so nothing changes for you. If you want to use the new behaviour, set `legacy=False`. This should only be set if you understand what it means, and thouroughly read the reason why this was added as explained in https://github.com/huggingface/transformers/pull/24565 ['日本の首都はロンドンです。 ロンドンは、世界でも有数の大都市で、世界中'] elapsed time = 88.547 sec
今回使ったモデルは、ABEJA社がJapanese CC-100、Japanese Wikipedia、Japanese OSCARで学習したGTP2モデルです。
abeja/gpt2-large-japanese · Hugging Face
トークナイザーは、SentencePieceを使っています(なのでインストールが必要)。
GitHub - google/sentencepiece: Unsupervised text tokenizer for Neural Network-based text generation.
rinna/japanese-gpt2-medium
続いては、rinna/japanese-gpt2-mediumで試してみます。環境は別々に作ったのですが、rinna/japanese-gpt2-mediumも
abeja/gpt2-large-japaneseと同じでSentencePieceを使っているようなので、結局インストールするライブラリーは同じでした…。
$ pip3 install transformers[torch,sentencepiece]
依存ライブラリーのバージョンの情報は省略します。
pipelineを使って作成したソースコード。モデル名が違う以外は、abeja/gpt2-large-japaneseを使った時と同じです。
text_generation_pipeline.py
import time from transformers import pipeline start_time = time.perf_counter() generator = pipeline("text-generation", model="rinna/japanese-gpt2-medium") outputs = generator("日本の首都は") elapsed_time = time.perf_counter() - start_time print(outputs) print() print(f"elapsed time = {elapsed_time:.3f} sec")
実行。初回はモデルのダウンロードが行われるので、時間がかかります。
$ python3 text_generation_pipeline.py You are using the default legacy behaviour of the <class 'transformers.models.t5.tokenization_t5.T5Tokenizer'>. This is expected, and simply means that the `legacy` (previous) behavior will be used so nothing changes for you. If you want to use the new behaviour, set `legacy=False`. This should only be set if you understand what it means, and thouroughly read the reason why this was added as explained in https://github.com/huggingface/transformers/pull/24565 Setting `pad_token_id` to `eos_token_id`:2 for open-end generation. [{'generated_text': '日本の首都は東京であり、日本の首都の中で最も人口が多い大都市です。人口は約280万人で、神奈川県の中でも有数の人口密集地です。東京に在住する人は、人口が最も多い都市です。そして、横浜市や川崎市、相模原市には、'}] elapsed time = 5.614 sec
こちらもしっかり嘘を言いますが…abeja/gpt2-large-japaneseよりも速く返ってきますね。モデルが1.3Gほどとabeja/gpt2-large-japaneseの
半分以下なこともある気がしますが。
$ python3 text_generation_pipeline.py You are using the default legacy behaviour of the <class 'transformers.models.t5.tokenization_t5.T5Tokenizer'>. This is expected, and simply means that the `legacy` (previous) behavior will be used so nothing changes for you. If you want to use the new behaviour, set `legacy=False`. This should only be set if you understand what it means, and thouroughly read the reason why this was added as explained in https://github.com/huggingface/transformers/pull/24565 Setting `pad_token_id` to `eos_token_id`:2 for open-end generation. [{'generated_text': '日本の首都はニューヨークのマンハッタンに位置する。 人口は約304万人。 この都市の起源は1655年にニューヨーク市のマンハッタン島にあったセントポールが、1657年にマンハッタン島の対岸に移動し、マンハッタン島が正式にアメリカ合衆国の首都となった。 17世紀、アメリカ合衆国で'}] elapsed time = 5.762 sec
Auto Classesを使った場合。こちらは、rinna/japanese-gpt2-mediumに書かれてあるようにAutoTokenizer
を設定しておきました。
text_generation_autoclasses.py
import time from transformers import AutoModelForCausalLM, AutoTokenizer start_time = time.perf_counter() tokenizer = AutoTokenizer.from_pretrained("rinna/japanese-gpt2-medium", use_fast=False) tokenizer.do_lower_case = True model = AutoModelForCausalLM.from_pretrained("rinna/japanese-gpt2-medium") inputs = tokenizer("日本の首都は", return_tensors="pt") outputs = model.generate( **inputs, max_new_tokens=15, pad_token_id=tokenizer.pad_token_id ) generated_texts = tokenizer.batch_decode(outputs, skip_special_tokens=True) elapsed_time = time.perf_counter() - start_time print(generated_texts) print() print(f"elapsed time = {elapsed_time:.3f} sec")
実行結果。こちらも実行に応じた変化が見られないので、パラメーターの指定が足りない感じがしますね…。
$ python3 text_generation_autoclasses.py You are using the default legacy behaviour of the <class 'transformers.models.t5.tokenization_t5.T5Tokenizer'>. This is expected, and simply means that the `legacy` (previous) behavior will be used so nothing changes for you. If you want to use the new behaviour, set `legacy=False`. This should only be set if you understand what it means, and thouroughly read the reason why this was added as explained in https://github.com/huggingface/transformers/pull/24565 ['日本の首都はニューヨーク です。 ニューヨークは、 ニューヨークシティ とも呼ばれます。'] elapsed time = 3.194 sec
こちらのモデルは、Japanese CC-100とJapanese Wikipediaで事前学習しているようです。
rinna/japanese-gpt2-medium · Hugging Face
トークナイザーにSentencePieceを使っているのは、abeja/gpt2-large-japaneseと同じです。
おわりに
Transformersを使って、テキスト生成を試してみました。
Transformersのドキュメントを読み解いたりするのにだいぶ苦労しましたが、とりあえずなんとか動かすことはできました…。
思った以上にリソースを使う(ディスクも)ので、自分の環境ではちょっと扱いが難しい感じなのですが、いい勉強題材ではあるので
もう少しいろいろやりたいところですが…どうしたものでしょうね。