LLMを使っておとぎ話を作る
2024年07月23日 火曜日
CONTENTS
前回の記事では、コード生成からカスタマーサポートの自動化まで、大規模言語モデル(Large Language Models:LLM)が企業にもたらす影響について紹介しました。その複雑さにもかかわらず、LLMの導入がより身近になり、より多くの人々がその機能を利用できるようになったことをお伝えしました。LLMシリーズの2回目となる本稿では、LLMの実用的なアプリケーションをより深く掘り下げます。具体的には、コンテンツの生成と微調整に焦点を当てます。LLMを様々なタスクで効果的に使用するためには、これらの側面を理解することが極めて重要です。ここでは、LLMを使って童話を生成する方法について紹介します。
コンテンツ生成にLLMを使う
LLMは、コンテンツ生成、要約、翻訳、分類、チャットボットサポートなど、様々なテキスト・アプリケーションをパワーアップすることができます。
コンテンツ生成は、LLMの人気の主な理由です。目の肥えた読者をも混乱させるほど、人間が作ったかのようなコンテンツを作り出す機械の能力は実に魅力的です。この機能は、誰もがコンテンツを作成し、ソーシャルメディア・プラットフォームを使って数秒で公開できる今日のデジタル世界では特に大きな力を発揮します。企業にとって、ソーシャルメディアは宣伝のための重要なツールですが、公に発信する文章を作成するには時間がかかります。このような場合にLLMを利用すればコンテンツを素早く作成でき、効率化することができます。
LLMの微調整
前回、コンテンツ生成について説明しましたが、生成されるコンテンツの正確さについては何も触れませんでした。LLMを使うとき、私たちはLLMが私たちの望むものを何でも生成してくれると期待していますが、現実にはそうではありません。適切なコンテンツを生成できるかどうかは、LLMが学習した教材の量と質に左右されます。LLMは通常、膨大で多様なデータセットで訓練されるため、特定のドメインでLLMを使用した場合、生成されるコンテンツの精度が満足のいくものにならない可能性があります。
この問題は、LLMを微調整することで解決できます。モデルが既に持ち合わせている文法や構文、語彙を間違えずに文章を生成する知識と、我々が独自に提供する新しいドメイン固有の知識を組み合わせることで、モデルはドメイン固有のコンテンツをミスなく生成できるようになります。
例えば、前回の記事で取り上げたカスタマーサポートについて考えてみます。回答プロセスを自動化するLLMを作るには、まずコンテンツ生成のために事前に訓練されたモデルから始めます。次に、過去の返信を利用してモデルを微調整します。こうすることで、モデルは過去のやりとりに基づいて、どのようにコンテンツを生成し、どのようなフォーマットを使うべきかを学習することができます。
そして、私たちのおとぎ話はまだ始まったばかりだ…
LLMの微調整についての理論は、既存の実装を利用して微調整を試してみることと比べてはるかに難しく、機械学習(ML)の基本的な知識だけでなく、モデルのアーキテクチャの理解が必要になります。以前述べたように、このブログシリーズは、テキストの前処理、パラメータの選択、学習率などの詳細な方法論に踏み込むのではなく、LLMとその使用法の基本的な概要を提供することを目的としているため、この記事ではそれら詳細については割愛いたします。微調整について深く掘り下げることに興味がある方には、「Build a Large Language Model (From Scratch)」をチェックすることをお勧めします。
それでは、オリジナルの童話を生成してみましょう。
まず、必要なPythonライブラリをダウンロードします。ここではOSをmacOSと仮定しています。それ以外のOSをお使いの場合は、公式のPyTorchドキュメントを参照してください。
pip install transformers torch torchvision torchaudio accelerate datasets requests
ライブラリのインストールが完了したら、微調整したモデルを開発する準備は完了です。著作権の切れた資料を集めたデジタルライブラリー「The Gutenberg Project」にアクセスし、童話生成モデルに適した資料を探しましょう。今回は、Jacob Grimm (ヤーコプ グリム)と Wilhelm Grimm (ヴィルヘルム グリム)のグリム兄弟による「Grimms’ Fairy Tales」(グリム童話)を選びました。モデルが適切なコンテンツを生成するためには、童話を含む素材を選択することが非常に重要です。
import requests f = open("pg2591.txt", "w") f.write(requests.get("https://www.gutenberg.org/cache/epub/2591/pg2591.txt").text) f.close()
本をダウンロードした後、テキストの前処理を行い、テキストがタスクに適したフォーマットになっていることを確認します。
storiesTitles = [ "THE GOLDEN BIRD", "HANS IN LUCK", "JORINDA AND JORINDEL", "THE TRAVELLING MUSICIANS", "OLD SULTAN", "THE STRAW, THE COAL, AND THE BEAN", "BRIAR ROSE", "THE DOG AND THE SPARROW", "THE TWELVE DANCING PRINCESSES", "THE FISHERMAN AND HIS WIFE", "THE WILLOW-WREN AND THE BEAR", "THE FROG-PRINCE", "CAT AND MOUSE IN PARTNERSHIP", "THE GOOSE-GIRL", "THE ADVENTURES OF CHANTICLEER AND PARTLET", "RAPUNZEL", "FUNDEVOGEL", "THE VALIANT LITTLE TAILOR", "HANSEL AND GRETEL", "THE MOUSE, THE BIRD, AND THE SAUSAGE", "MOTHER HOLLE", "LITTLE RED-CAP [LITTLE RED RIDING HOOD]", "THE ROBBER BRIDEGROOM", "TOM THUMB", "RUMPELSTILTSKIN", "CLEVER GRETEL", "THE OLD MAN AND HIS GRANDSON", "THE LITTLE PEASANT", "FREDERICK AND CATHERINE", "SWEETHEART ROLAND", "SNOWDROP", "THE PINK", "CLEVER ELSIE", "THE MISER IN THE BUSH", "ASHPUTTEL", "THE WHITE SNAKE", "THE WOLF AND THE SEVEN LITTLE KIDS", "THE QUEEN BEE", "THE ELVES AND THE SHOEMAKER", "THE JUNIPER-TREE", "THE TURNIP", "CLEVER HANS", "THE THREE LANGUAGES", "THE FOX AND THE CAT", "THE FOUR CLEVER BROTHERS", "LILY AND THE LION", "THE FOX AND THE HORSE", "THE BLUE LIGHT", "THE RAVEN", "THE GOLDEN GOOSE", "THE WATER OF LIFE", "THE TWELVE HUNTSMEN", "THE KING OF THE GOLDEN MOUNTAIN", "DOCTOR KNOWALL", "THE SEVEN RAVENS", "THE WEDDING OF MRS FOX", "THE SALAD", "THE STORY OF THE YOUTH WHO WENT FORTH TO LEARN WHAT FEAR WAS", "KING GRISLY-BEARD", "IRON HANS", "CAT-SKIN", "SNOW-WHITE AND ROSE-RED" ] stories = {title: [] for title in storiesTitles} f = open("pg2591.txt", "r") title = None for line in f.readlines(): text = line.strip() if "*** END OF THE PROJECT GUTENBERG EBOOK GRIMMS' FAIRY TALES ***" == text: break if text in stories: title = text continue if title: stories[title].append(text) f.close() dataset = { "text": [] } for story in stories: dataset["text"].append(" ".join(list(filter(lambda x: x!="", stories[story])))) dataset["text"] = list(filter(lambda x: x!="", dataset["text"]))
前処理が完了したら、微調整中の潜在的なエラーを避けるため、環境パラメータを初期化する必要があります。
import os download_dir = './huggingface/transformers' os.environ["HF_HOME"] = download_dir os.environ["PYTORCH_MPS_HIGH_WATERMARK_RATIO"] = "0.0"
次に、「Grimms’ Fairy Tales」のテキストを数値ベクトルに変換するために、事前に訓練されたトークナイザーをダウンロードします。LLMは数値データを理解するので、トークナイザーは人間のテキストを数値ベクトルに変換しています。ここではGPT-2で使われているトークナイザを選択しました。
from transformers import AutoTokenizer from datasets import Dataset tokenizer = AutoTokenizer.from_pretrained("openai-community/gpt2") tokenizer.pad_token = tokenizer.eos_token def tokenize_function(examples): return tokenizer(examples["text"], padding="max_length", truncation=True) dataset = Dataset.from_dict(dataset) tokenized_datasets = dataset.map(tokenize_function, batched=True, remove_columns=["text"])
この結果、テキストの数値表現は膨大なものになります。扱いやすさのために、我々はこの数値表現をバッチ、つまりベクトルの一部を含む小さなブロックに分割します。
block_size = 128 def group_texts(examples): concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()} total_length = len(concatenated_examples[list(examples.keys())[0]]) total_length = (total_length // block_size) * block_size result = { k: [t[i : i + block_size] for i in range(0, total_length, block_size)] for k, t in concatenated_examples.items() } result["labels"] = result["input_ids"].copy() return result lm_datasets = tokenized_datasets.map( group_texts, batched=True, batch_size=1000, num_proc=4, )
ここで、事前に訓練されたLLMをダウンロードし、最適な結果を得るためにトークナイザーと互換性があることを確認します。この目的のためにGPT-2を使用します。
from transformers import AutoConfig, AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("openai-community/gpt2")
ダウンロードしたモデルを微調整します。この作業には時間がかかります。
from transformers import Trainer, TrainingArguments training_args = TrainingArguments( "test_trainer", evaluation_strategy = "no", learning_rate=2e-5, weight_decay=0.01, num_train_epochs=200, use_mps_device=True ) trainer = Trainer( model=model, args=training_args, train_dataset=lm_datasets, ) trainer.train()
微調整が完了したら、将来の使用のためにモデルを保存します。
trainer.save_model("my-model")
これで、オリジナルの童話を生成するという目標に近づきました。微調整したモデルをロードし、童話生成に使ってみましょう。
from transformers import AutoTokenizer, AutoModelForCausalLM import textwrap tokenizer = AutoTokenizer.from_pretrained("openai-community/gpt2") model = AutoModelForCausalLM.from_pretrained("./my-model") text = "Once upon a time, there was a king" inputs = tokenizer(text, return_tensors="pt").input_ids outputs = model.generate(inputs, max_new_tokens=110) generated_text = tokenizer.batch_decode(outputs, skip_special_tokens=True) print("\n".join(textwrap.wrap(" ".join(generated_text), 80)))
完成です!人間ではなく、LLMによって書かれたおとぎ話が誕生しました!
Once upon a time, there was a king who had two beautiful daughters. They slept in two beds all in one room; and when they went to bed, the doors were shut and locked up; but every morning their shoes were found to be quite worn through as if they had been danced in all night; and yet nobody could find out how it happened, or where they had been. Then the king made it known to all the land, that if any person could discover the secret, and find out where it was that the princesses danced in the night, he should be king after his death.
次はどうする?
この記事では、おとぎ話を生成するためにLLMを微調整する方法を説明しました(同じ方法論は、詩の作成など他のトピックにも適用できます)。しかし、このLLMによって生成されたコンテンツは、精度が高くなかったり、素直でなかったりする可能性があることは注目に値します。この問題は、ハイパーパラメータチューニング、童話のより大規模なコーパスの使用、あるいはLlama 3のようなより高度なLLMの採用によって対処することができると考えられます。このようなモデルによる微調整には時間がかかるかもしれませんが、結果はより正確になることが予想されます。さらに、特定のドメインのコンテンツを生成するLLMを作成することを目的とする場合、LLMをゼロからトレーニングすることも検討する価値があります。一方で、これにはかなりのコンピューター・リソースと時間が必要になります。
本連載の最後となる次回は、業界の視点からLLMを探ってみます。また、LLMを使ってテキストを分類する簡単な例も紹介します。ご期待ください!