この記事では、Ruby を使用して非常にシンプルな言語モデルを作成する方法を説明します。真の大規模言語モデル (LLM) には膨大な量のデータと計算リソースが必要ですが、言語モデリングの背後にあるコア概念の多くを示すおもちゃのモデルを作成できます。例では、入力テキストから「学習」し、観察したパターンに基づいて新しいテキストを生成する基本的なマルコフ連鎖モデルを構築します。
注:このチュートリアルは教育目的であり、言語モデリングへの簡略化されたアプローチを示しています。GPT-4 などの最新のディープラーニング LLM に代わるものではなく、基礎となるアイデアの紹介です。
言語モデルは、単語のシーケンスに確率を割り当てるシステムです。本質的には、特定のシーケンスが特定のコンテキストで発生する可能性を学習することで、言語の統計的構造を捉えるように設計されています。つまり、モデルは大量のテキストを分析して、単語が通常どのように連続しているかを理解し、シーケンス内で次にどの単語またはフレーズが来るかを予測できるようにします。このような機能は、テキスト生成や自動補完などのタスクだけでなく、翻訳、要約、感情分析などのさまざまな自然言語処理 (NLP) アプリケーションでも中心的な役割を果たします。
GPT-4 などの現代の大規模言語モデル (LLM) は、ディープラーニング技術と膨大なデータセットを使用して、言語の複雑なパターンを捉えます。これらのモデルは、入力テキストを多数の人工ニューロン層で処理することで動作し、驚くほど流暢な人間のようなテキストを理解して生成することができます。ただし、これらの洗練されたシステムの背後には、学習した確率に基づいて単語のシーケンスを理解して予測するという同じ基本的な考え方があります。
言語をモデル化する最も簡単な方法の 1 つは、マルコフ連鎖を使用することです。マルコフ連鎖は、単語の出現確率はテキストの履歴全体ではなく、先行する単語の限られたセットのみに依存するという仮定に基づいて動作する統計モデルです。この概念はマルコフ特性として知られています。実際的には、このモデルは、シーケンス内の次の単語は最新の単語を見るだけで予測できると仮定しています。この単純化により、計算上は問題が扱いやすくなり、同時にデータ内の有用なパターンも捕捉できます。
マルコフ連鎖ベースの言語モデルでは:
私たちの実装では、設定可能な「順序」を使用して、予測を行う際に前の単語をいくつ考慮するかを決定します。順序が高いほどコンテキストが充実し、モデルが前の単語に関する情報をより多く持つため、より一貫性があり、文脈的に関連性の高いテキストになる可能性があります。逆に、順序が低いとランダム性が高くなり、予測しにくくなるものの、より創造的な単語のシーケンスにつながる可能性があります。一貫性と創造性の間のこのトレードオフは、言語モデリングにおける中心的な考慮事項です。
これらの基本原理を理解することで、マルコフ連鎖モデルの単純さと、より複雑なニューラル言語モデルの基礎となる基本的な考え方の両方を理解できるようになります。この拡張された視点は、言語予測の背後にある統計的メカニズムを理解するのに役立つだけでなく、自然言語処理のより高度な技術を試すための基盤も築きます。
始める前に、システムに Ruby がインストールされていることを確認してください。次のコマンドを実行して、Ruby のバージョンを確認できます。
ruby -v
Ruby がインストールされていない場合は、 ruby-lang.orgからダウンロードできます。
このプロジェクトでは、専用のディレクトリとファイルを作成することをお勧めします。
mkdir tiny_llm cd tiny_llm touch llm.rb
これで、Ruby コードを記述する準備が整いました。
言語モデルには、テキスト コーパスが必要です。トレーニングには任意のテキスト ファイルを使用できます。簡単な例では、次のような小さなテキスト サンプルを使用できます。
sample_text = <<~TEXT Once upon a time in a land far, far away, there was a small village. In this village, everyone knew each other, and tales of wonder were told by the elders. The wind whispered secrets through the trees and carried the scent of adventure. TEXT
トレーニングの前に、テキストを前処理しておくと便利です。
私たちの目的には、Ruby のString#split
メソッドがトークン化に十分に機能します。
モデルの動作をカプセル化するために、 MarkovChain
という Ruby クラスを作成します。クラスには次のものが含まれます。
train
メソッド。generate
メソッド。以下はモデルの完全なコードです。
class MarkovChain def initialize(order = 2) @order = order # The chain is a hash that maps a sequence of words (key) to an array of possible next words. @chain = Hash.new { |hash, key| hash[key] = [] } end # Train the model using the provided text. def train(text) # Optionally normalize the text (eg, downcase) processed_text = text.downcase.strip words = processed_text.split # Iterate over the words using sliding window technique. words.each_cons(@order + 1) do |words_group| key = words_group[0...@order].join(" ") next_word = words_group.last @chain[key] << next_word end end # Generate new text using the Markov chain. def generate(max_words = 50, seed = nil) # Choose a random seed from the available keys if none is provided or if the seed is invalid. if seed.nil? || [email protected]?(seed) seed = @chain.keys.sample end generated = seed.split while generated.size < max_words # Form the key from the last 'order' words. key = generated.last(@order).join(" ") possible_next_words = @chain[key] break if possible_next_words.nil? || possible_next_words.empty? # Randomly choose the next word from the possibilities. next_word = possible_next_words.sample generated << next_word end generated.join(" ") end end
**初期化:**コンストラクターのinitialize
順序 (デフォルトは 2) を設定し、チェーンの空のハッシュを作成します。ハッシュにはデフォルトのブロックが与えられ、すべての新しいキーが空の配列として開始されます。
**モデルのトレーニング:** train
メソッドは、テキストの文字列を受け取り、それを正規化して単語に分割します。 each_cons
を使用して、長さorder + 1
の連続した単語グループを作成します。最初のorder
単語がキーとして機能し、最後の単語がそのキーの可能な継続の配列に追加されます。
**テキストの生成:** generate
メソッドはシード キーから開始します。何も指定されていない場合は、ランダム キーが選択されます。次に、最大単語数に達するまで、最後のorder
単語を検索し、次の単語をサンプリングすることで、シーケンスを繰り返し構築します。
MarkovChain
クラスができたので、テキスト データでトレーニングしてみましょう。
# Sample text data for training sample_text = <<~TEXT Once upon a time in a land far, far away, there was a small village. In this village, everyone knew each other, and tales of wonder were told by the elders. The wind whispered secrets through the trees and carried the scent of adventure. TEXT # Create a new MarkovChain instance with order 2 model = MarkovChain.new(2) model.train(sample_text) puts "Training complete!"
上記のコードを実行すると(たとえば、 llm.rb
に保存してruby llm.rb
を実行する)、提供されたサンプルテキストを使用してモデルがトレーニングされます。
モデルをトレーニングしたら、新しいテキストを生成できます。サンプル テキストを生成して印刷するコードを追加してみましょう。
# Generate new text using the trained model. generated_text = model.generate(50) puts "Generated Text:" puts generated_text
テキスト生成のシードを提供することもできます。たとえば、モデル内のキーの 1 つ ( "once upon"
など) がわかっている場合は、次のようにします。
seed = "once upon" generated_text_with_seed = model.generate(50, seed) puts "\nGenerated Text with seed '#{seed}':" puts generated_text_with_seed
さまざまなシードとパラメータ (順序や単語の最大数など) を試してみると、出力がどのように変化するかを確認できます。
上記のすべての手順を組み合わせた完全な Ruby スクリプトは次のとおりです。
#!/usr/bin/env ruby # llm.rb # Define the MarkovChain class class MarkovChain def initialize(order = 2) @order = order @chain = Hash.new { |hash, key| hash[key] = [] } end def train(text) processed_text = text.downcase.strip words = processed_text.split words.each_cons(@order + 1) do |words_group| key = words_group[0...@order].join(" ") next_word = words_group.last @chain[key] << next_word end end def generate(max_words = 50, seed = nil) if seed.nil? || [email protected]?(seed) seed = @chain.keys.sample end generated = seed.split while generated.size < max_words key = generated.last(@order).join(" ") possible_next_words = @chain[key] break if possible_next_words.nil? || possible_next_words.empty? next_word = possible_next_words.sample generated << next_word end generated.join(" ") end end # Sample text data for training sample_text = <<~TEXT Once upon a time in a land far, far away, there was a small village. In this village, everyone knew each other, and tales of wonder were told by the elders. The wind whispered secrets through the trees and carried the scent of adventure. TEXT # Create and train the model model = MarkovChain.new(2) model.train(sample_text) puts "Training complete!" # Generate text without a seed generated_text = model.generate(50) puts "\nGenerated Text:" puts generated_text # Generate text with a specific seed seed = "once upon" generated_text_with_seed = model.generate(50, seed) puts "\nGenerated Text with seed '#{seed}':" puts generated_text_with_seed
llm.rb
として保存します。llm.rb
含むディレクトリに移動します。 ruby llm.rb
モデルがトレーニングされたことを示す出力と、生成されたテキストの例が 2 つ表示されます。
次の表は、Tiny LLM 実装のさまざまなバージョンのベンチマーク メトリックをまとめたものです。各メトリックについては以下で説明します。
以下はベンチマーク データを含むマークダウン テーブルです。
モデル | 注文 | トレーニング時間 (ミリ秒) | 生成時間 (ミリ秒) | メモリ使用量 (MB) | 一貫性評価 |
---|---|---|---|---|---|
小さな LLM v1 | 2 | 50 | 10 | 10 | 3/5 |
小さな LLM v2 | 3 | 70 | 15 | 12 | 3.5/5 |
小さな LLM v3 | 4 | 100 | 20 | 15 | 4/5 |
これらのベンチマークは、さまざまなモデル構成間のトレードオフの概要を簡単に示します。次数が増えると、モデルのトレーニングとテキスト生成にかかる時間がわずかに長くなり、メモリ使用量も増える傾向があります。ただし、リソース消費量の増加は、生成されるテキストの一貫性の向上を伴うことがよくあります。
このチュートリアルでは、Ruby を使用して非常にシンプルな言語モデルを作成する方法を示しました。マルコフ連鎖技術を活用して、次のようなシステムを構築しました。
このおもちゃのモデルは、実稼働レベルの LLM とは程遠いものですが、言語モデルが基本的なレベルでどのように機能するかを理解するための足がかりとなります。より高度なテクニックを取り入れたり、句読点をより適切に処理したり、Ruby を機械学習ライブラリと統合してより洗練されたモデルにしたりすることで、このアイデアを拡張できます。
楽しいコーディングを!