ゼロから始めるLLMs:大規模言語モデルのためのデータ準備 🚀
シリーズ第1回では、Large Language Models(LLMs)とそれを支えるTransformerアーキテクチャの基本を扱いました。まだの方はぜひ導入をご一読ください(2025年10月現在)。
この第2回では、LLM構築で見落とされがちな「データ準備」に焦点を当てます。生のテキストをそのままモデルに流し込めば良い──というのは誤解です。実務では、非構造化テキストをニューラルネットワークが解釈できる構造に変換する工程が、学習の精度・効率に大きく影響します。本記事では、テキスト前処理、トークナイゼーション、埋め込み生成、データサンプリング、特殊トークンの扱いなど、モデル学習のためのデータ準備を包括的に解説します。
なぜデータ準備が重要か
LLMが正しく動作する前提として、データは綿密に整備されている必要があります。単にテキストを小さく分割するだけでなく、クリーンで一貫性のある意味のある入力をモデルに渡すことが重要です。これが欠けると、最先端のモデルでも十分に学習できません。
主な理由は次の3点です。
- 標準化(Standardization):複数ソースのデータを整合させ、一貫した形式にする。
- 効率化(Efficiency):ニューラルネットワークが処理しやすい形に変換する。
- 精度向上(Accuracy):適切に整形されたデータが、より正確な学習につながる。
以下で各ステップを順に見ていきます。
1. テキスト前処理(Text Preprocessing)
テキスト前処理は、データ準備の最初の段階で、ノイズ除去や形式統一を行います。不要な文字や記号の除去、句読点・大文字小文字の正規化、Unicode正規化(NFC/NFKC)などが含まれます。言語や用途によって必要な処理は変わります(例:ソーシャルメディアだと絵文字やハッシュタグの扱い、コードドキュメントだとインラインコードの除外など)。
例として、次のような生テキストがあります(コードブロック内はそのまま):
Hello, World! This is an Example--testing 123.
基本的な前処理パイプラインで行う例:
- 余分な空白の削除:
"Hello, World!"→"Hello, World!" - 句読点の正規化:
--や長いダッシュを一つのダッシュやピリオドへ - 表記の統一:用途に応じて全て小文字化するか、固有名詞は維持するか決める
- Unicode正規化:NFKCで全角・半角や合字を統一する(特に多言語データで重要)
簡単なPythonでの例(コードブロックは原文のまま変更していません):
import re# Sample texttext = "Hello, World! This is an Example--testing 123."# Basic Tokenization using Regular Expressionstokens = re.split(r'(\s|[,.?!])', text)tokens = [t.strip() for t in tokens if t.strip()]print(tokens)
出力例(そのまま):
['Hello', ',', 'World', '!', 'This', 'is', 'an', 'Example', 'testing', '123']
補足(実務で注意すべき点)⚠️:
- 正規化の副作用:小文字化で固有名詞や頭字語の意味が失われることがあるため、タスクに応じて設定する。
- 言語依存処理:形態素解析が必要な言語(日本語など)では分かち書きルールを別途設計する。
- メタデータ保持:発話日時やソースを別フィールドで保存すると後のフィルタリングに役立つ。
2. トークナイゼーション(Tokenization)
前処理後はトークナイゼーションです。テキストを単語、サブワード、あるいは文字単位の「トークン」に分割します。LLMsはトークン列を入力として扱うため、ここでの選択がモデル性能や語彙サイズに影響します。
シンプルな単語分割の例(前節の正規表現を使った場合):
tokens = re.split(r'(\s|[,.?!])', text)tokens = [t.strip() for t in tokens if t.strip()]
ただし、単純な単語分割は未知語(OOV)が多くなりがちです。そこでよく使われるのが Byte Pair Encoding(BPE、バイトペア符号化)などのサブワード手法です。
Byte Pair Encoding(BPE)
BPEは稀な単語をサブワードに分解することで語彙数を抑え、未知語に強くします。GPT-2 / GPT-3 などで採用されています。実装例として tiktoken ライブラリを使う例(ライブラリ名はそのまま):
import tiktoken# Using BPE tokenizationtokenizer = tiktoken.get_encoding("gpt2")text = "Hello, do you like coffee?"token_ids = tokenizer.encode(text)print(token_ids)
なぜBPEが有用か:
- 未知語への対処:学習時に見ていない語でもサブワードに分割すれば表現可能。
- 語彙サイズと性能のバランス:語彙が爆発的に増えるのを防ぐ。
- 一般化性能の改善:共通のサブワードを通じて未知の組み合わせに対応しやすい。
トークナイゼーションの比較(簡潔な表):
| 方法 | 長所 | 短所 | 推奨用途 |
|---|---|---|---|
| 単語ベース | 理解しやすく人間寄り | OOVが多い | 小規模コーパス、言語研究 |
| BPE / サブワード | 語彙削減・未知語に強い | 学習・実装が必要 | 大規模コーパス、LLM |
| 文字ベース | 常にカバレッジあり | 長いシーケンスになる | 言語に依存しない処理、特殊文字多いデータ |
(参考リンク:tiktoken GitHub)
3. 埋め込み(Embeddings)生成
トークンが得られたら、それをニューラルネットワークが扱える数値に変換する必要があります。埋め込みは各トークンを連続値ベクトルに変換し、意味的な類似性をベクトル空間上で表現します。例えば「king」と「queen」が近いベクトルになるように学習されます。
PyTorch を使った埋め込みの簡単な例(ブロックは原文のまま):
import torch# Create an embedding layervocab_size = 50000 # Total number of tokens in the vocabularyembedding_dim = 256 # Each token will be mapped to a 256-dimensional vectorembedding_layer = torch.nn.Embedding(vocab_size, embedding_dim)# Sample token IDstoken_ids = torch.tensor([1, 2, 3, 4])embeddings = embedding_layer(token_ids)print(embeddings.shape)
埋め込みが重要な理由:
- トークンを数値化:ニューラルネットが扱える形式に変換するために必須。
- 意味関係の表現:語義的に近い語が近接することで文脈理解が向上。
- 学習による最適化:学習過程で埋め込み自体がファインチューニングされ、データセット固有の関係を捉える。
補足(実務的な注意)💡:
- 事前学習済みの埋め込みを利用するか、モデルと一緒に学習するかはタスク依存。
- 埋め込み次元は性能と計算コストのトレードオフ:512〜2048の範囲がよく使われますが、用途により調整。
4. スライディングウィンドウによるデータサンプリング(Sliding Window)
多くのLLMは次トークン予測を目的に学習します。長いテキストをそのまま与える代わりに、固定長のウィンドウをスライドさせてオーバーラップするシーケンスを作り、入力(コンテキスト)と出力(次のトークン)のペアを生成します。これにより、1つの長文から多くの学習例を取り出せます。
例(原文のコードブロック):
# Sample text tokenization (illustrative, assume each word is a token)token_ids = ["LLMs", "learn", "to", "predict", "one", "word", "at", "a", "time"]context_size = 4 # Number of tokens in each input contextinput_output_pairs = []for i in range(len(token_ids) - context_size): x = token_ids[i:i + context_size] # Input context y = token_ids[i + context_size] # Target word to predict input_output_pairs.append((x, y))# Display generated input-output pairsfor x, y in input_output_pairs: print(f"x: {x}, y: {y}")
出力例(そのまま):
x: ['LLMs', 'learn', 'to', 'predict'], y: 'one'x: ['learn', 'to', 'predict', 'one'], y: 'word'x: ['to', 'predict', 'one', 'word'], y: 'at'x: ['predict', 'one', 'word', 'at'], y: 'a'x: ['one', 'word', 'at', 'a'], y: 'time'
スライディングウィンドウの利点:
- 学習データを増やす:同じ文章から複数の例を生成。
- コンテキスト保持:一定長の履歴をモデルに与える。
- 計算効率:長文を小さく分割して扱えるためバッチ処理が容易。
補足(実務的判断)🔧:
- ステップサイズ(ウィンドウの移動幅)を小さくすると例は増えるが相関が高くなる。メモリや学習の多様性を考慮して調整する。
- 次トークン予測以外(例えば文書分類)では、ラベルの整合性に注意してウィンドウ切り出しを行う。
5. 特殊トークンの扱い(Special Tokens)
特殊トークンはシーケンスの区切りや未知語、パディングなどを示すために重要です。一般的な特殊トークンの例:
-
<|endoftext|>:テキストの終端を示す -
<|unk|>:語彙にない未知語を表す -
<|pad|>:バッチ処理のためにシーケンスを詰めるパディング
正しい特殊トークンの扱いがないと、モデルは入力境界や文脈を誤解することがあります。取り扱い例(原文のコードブロック):
# Sample tokens including special tokenstokens = ["Hello", "world", ".", "<|endoftext|>", "This", "is", "an", "LLM"]# Simulated vocabulary with token IDsvocab = { "Hello": 1, "world": 2, ".": 3, "<|endoftext|>": 4, "This": 5, "is": 6, "an": 7, "LLM": 8, "<|unk|>": 0 # ID for unknown tokens}# Convert tokens to their corresponding IDstoken_ids = [vocab.get(token, vocab["<|unk|>"]) for token in tokens]print(token_ids)
特殊トークンが重要な理由:
- 境界検出:シーケンスの開始・終了を明示できる。
- 未知語処理:不明な語を
<|unk|>で置き換えて処理を継続できる。 - バッチ処理の効率化:
<|pad|>による長さ揃えでミニバッチ学習が可能。
データ準備パイプラインの視覚的な要約
- テキスト入力(例: “Hello, world.”)
- トークナイゼーション(例:
['Hello', ',', 'world', '.']) - トークンID(例:
[1, 2, 3, 4]) - 埋め込み(例:256次元ベクトルに変換)
実務で見落としがちな追加トピック(短めの補足)
データ品質と重複排除(Deduplication)
大量データでは重複が学習バイアスを生む。シャッフル前に文単位/ドキュメント単位の重複除去を行うと効果的です。
シーケンス長とトランケーション
最大シーケンス長をどう設定するかは重要です。トランケーションは情報を失うので、重要なコンテキストを残すように前側・後側どちらを切るかを設計する必要があります。
ラベル付きデータの整形(監督学習や微調整用)
指示文(instruction)、入出力ペア、メタデータを明示的にフォーマットしておくと、微調整(fine-tuning)や評価が楽になります。
多言語・特殊文字対応
多言語コーパスでは Unicode 正規化とトークナイザーの設計(言語ごとのルール)を慎重に整えること。絵文字や制御文字の扱いも設計に入れる。
最後に(まとめ)📝
データ準備はLLMの学習における最も重要な工程の一つです。テキストをクリーンにし、適切にトークン化し、意味を保ったまま数値ベクトルへ変換することで、モデルはより効率的かつ正確に学習できます。本稿で紹介した各ステップは相互に関連しており、用途やデータ特性に応じて調整する必要があります。
次回は、ニューラルネットワークにおける「アテンション機構」の意義から始め、基本的な自己注意(self-attention)の仕組み、拡張された自己注意、そしてLLMが1トークンずつ生成するための因果的注意(causal attention)について掘り下げます。さらに、過学習対策としてのドロップアウトや、マルチヘッド注意の積み重ね方についても扱う予定です。お楽しみに。