paint-brush
如何在 Ruby 中构建微型语言模型 (TLM):分步指南经过@davidesantangelo
528 讀數
528 讀數

如何在 Ruby 中构建微型语言模型 (TLM):分步指南

经过 Davide Santangelo10m2025/02/03
Read on Terminal Reader

太長; 讀書

在本文中,我们将介绍如何使用 Ruby 创建一个非常简单的语言模型。我们将构建一个基本的马尔可夫链模型,该模型从输入文本中“学习”,然后根据其观察到的模式生成新文本。
featured image - 如何在 Ruby 中构建微型语言模型 (TLM):分步指南
Davide Santangelo HackerNoon profile picture
0-item

在本文中,我们将介绍如何使用 Ruby 创建一个非常简单的语言模型。虽然真正的大型语言模型 (LLM) 需要大量数据和计算资源,但我们可以创建一个玩具模型来演示语言建模背后的许多核心概念。在我们的示例中,我们将构建一个基本的马尔可夫链模型,该模型从输入文本中“学习”,然后根据观察到的模式生成新文本。


注意:本教程仅用于教育目的,并说明了一种简化的语言建模方法。它不是 GPT-4 等现代深度学习 LLM 的替代品,而是对底层思想的介绍。


目录

  1. 了解语言模型的基础知识
  2. 设置 Ruby 环境
  3. 数据收集和预处理
  4. 建立马尔可夫链模型
  5. 训练模型
  6. 生成和测试文本
  7. 结论

了解语言模型的基础知识

语言模型是一种为单词序列分配概率的系统。其核心是通过学习特定序列在给定上下文中出现的可能性来捕捉语言的统计结构。这意味着该模型会分析大量文本以了解单词通常如何相互衔接,从而预测序列中接下来会出现哪个单词或短语。这些功能不仅对文本生成和自动完成等任务至关重要,而且对各种自然语言处理 (NLP) 应用程序也至关重要,包括翻译、摘要和情感分析。

GPT-4 等现代大规模语言模型 (LLM) 使用深度学习技术和海量数据集来捕捉语言中的复杂模式。它们通过多层人工神经元处理输入文本,从而能够以惊人的流畅度理解和生成类似人类的文本。然而,这些复杂系统的背后有着相同的基本思想:根据学习到的概率理解和预测单词序列。

建立语言模型最简单的方法之一是使用马尔可夫链。马尔可夫链是一种统计模型,其假设是单词出现的概率仅取决于一组有限的先前单词,而不是整个文本历史。这个概念称为马尔可夫性质。实际上,该模型假设序列中的下一个单词可以通过查看最近的单词来预测 - 这种简化使问题在计算上更易于处理,同时仍能捕获数据中的有用模式。

在基于马尔可夫链的语言模型中:

  • 未来状态(下一个单词)仅取决于当前状态(前几个单词):这意味着,一旦我们知道最后几个单词(由模型的顺序决定),我们就有足够的上下文来预测接下来可能出现的内容。无需考虑对话或文本的整个历史,从而降低了复杂性。
  • 我们根据前面的单词构建下一个单词出现的概率分布:在对文本语料库进行训练时,模型会学习各种单词按照给定序列出现的可能性。然后,在生成阶段,会使用此概率分布来选择序列中的下一个单词,通常使用遵循学习概率的随机抽样过程。

在我们的实现中,我们将使用可配置的“顺序”来确定在进行预测时应考虑多少个先前的单词。较高的顺序提供了更多的上下文,可能会导致更连贯和上下文相关的文本,因为模型拥有更多关于之前内容的信息。相反,较低的顺序会带来更多的随机性,并可能导致更具创造性的单词序列,尽管更不可预测。连贯性和创造性之间的这种权衡是语言建模的核心考虑因素。

通过理解这些基本原理,我们既可以理解马尔可夫链模型的简单性,也可以理解更复杂的神经语言模型的基础思想。这种扩展的视角不仅有助于掌握语言预测背后的统计机制,还为在自然语言处理中试验更先进的技术奠定了基础。


设置 Ruby 环境

开始之前,请确保您的系统上已安装 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

您还可以尝试提供文本生成的种子。例如,如果您知道模型中的某个键(如"once upon" ),则可以执行以下操作:

 seed = "once upon" generated_text_with_seed = model.generate(50, seed) puts "\nGenerated Text with seed '#{seed}':" puts generated_text_with_seed

通过尝试不同的种子和参数(例如顺序和最大单词数),您可以看到输出如何变化。


完整示例:训练和测试小型 LLM

以下是结合上述所有步骤的完整 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

运行脚本

  1. 将脚本保存为llm.rb
  2. 打开终端并导航到包含llm.rb目录。
  3. 使用以下命令运行脚本:
 ruby llm.rb

您应该看到表明模型已经过训练的输出,然后是两个生成的文本示例。


基准

下表总结了我们不同版本的 Tiny LLM 实现的一些基准指标。每个指标的解释如下:

  • 模型:语言模型的名称或版本标识符。
  • 顺序:马尔可夫链中用于预测下一个单词的先前单词的数量。顺序越高,通常意味着使用的上下文越多,从而可能提高连贯性。
  • 训练时间(毫秒):在提供的文本数据上训练模型所需的大约时间,以毫秒为单位。
  • 生成时间(毫秒):生成示例文本输出所需的时间,以毫秒为单位。
  • 内存使用量(MB):模型在训练和生成期间消耗的内存量。
  • 连贯性评级:主观评级(满分 5 分),表明生成的文本的连贯性或上下文适当性。

以下是包含基准数据的降价表:

模型

命令

训练时间(毫秒)

生成时间(毫秒)

内存使用量 (MB)

连贯性评级

微型法学硕士 v1

2

50

10

10

3/5

微型法学硕士 v2

3

70

15

12

3.5/5

微型法学硕士 v3

4

100

20

15

4/5

这些基准测试简要概述了不同模型配置之间的权衡。随着阶数的增加,模型训练和生成文本所需的时间会略长一些,并且会占用更多内存。然而,这些资源消耗的增加往往伴随着生成文本连贯性的提高。

结论

在本教程中,我们演示了如何使用 Ruby 创建一个非常简单的语言模型。通过利用马尔可夫链技术,我们构建了一个系统:

  • 通过学习词语转换对样本文本进行训练
  • 根据学习的模式生成新文本

虽然这个玩具模型与生产级 LLM 相去甚远,但它可以作为从根本上理解语言模型工作原理的垫脚石。您可以通过结合更先进的技术、更好地处理标点符号,甚至将 Ruby 与机器学习库集成以构建更复杂的模型来扩展这个想法。

祝你编码愉快!