paint-brush
Como construir um Tiny Language Model (TLM) em Ruby: um guia passo a passopor@davidesantangelo
531 leituras
531 leituras

Como construir um Tiny Language Model (TLM) em Ruby: um guia passo a passo

por Davide Santangelo10m2025/02/03
Read on Terminal Reader

Muito longo; Para ler

Neste artigo, mostraremos como criar um modelo de linguagem muito simples usando Ruby. Construiremos um modelo básico de Cadeia de Markov que “aprende” com o texto de entrada e então gera um novo texto com base nos padrões observados.
featured image - Como construir um Tiny Language Model (TLM) em Ruby: um guia passo a passo
Davide Santangelo HackerNoon profile picture
0-item

Neste artigo, mostraremos como criar um modelo de linguagem muito simples usando Ruby. Embora os verdadeiros Large Language Models (LLMs) exijam enormes quantidades de dados e recursos computacionais, podemos criar um modelo de brinquedo que demonstra muitos dos principais conceitos por trás da modelagem de linguagem. Em nosso exemplo, construiremos um modelo básico de Cadeia de Markov que "aprende" com o texto de entrada e, em seguida, gera um novo texto com base nos padrões observados.


Nota: Este tutorial é destinado a propósitos educacionais e ilustra uma abordagem simplificada para modelagem de linguagem. Não é um substituto para LLMs modernos de deep learning como GPT-4, mas sim uma introdução às ideias subjacentes.


Índice

  1. Compreendendo os fundamentos dos modelos de linguagem
  2. Configurando seu ambiente Ruby
  3. Coleta e pré-processamento de dados
  4. Construindo o modelo da cadeia de Markov
  5. Treinando o modelo
  6. Gerando e testando texto
  7. Conclusão

Compreendendo os fundamentos dos modelos de linguagem

Um Modelo de Linguagem é um sistema que atribui probabilidades a sequências de palavras. Em sua essência, ele é projetado para capturar a estrutura estatística da linguagem aprendendo a probabilidade de uma sequência específica ocorrer em um determinado contexto. Isso significa que o modelo analisa grandes corpos de texto para entender como as palavras normalmente seguem umas às outras, permitindo assim que ele preveja qual palavra ou frase pode vir a seguir em uma sequência. Tais capacidades são centrais não apenas para tarefas como geração de texto e preenchimento automático, mas também para uma variedade de aplicações de processamento de linguagem natural (PLN), incluindo tradução, sumarização e análise de sentimentos.

Modelos modernos de linguagem em larga escala (LLMs), como o GPT-4, usam técnicas de aprendizado profundo e conjuntos de dados massivos para capturar padrões complexos na linguagem. Eles operam processando texto de entrada por meio de várias camadas de neurônios artificiais, permitindo que eles entendam e gerem texto semelhante ao humano com notável fluência. No entanto, por trás desses sistemas sofisticados está a mesma ideia fundamental: entender e prever sequências de palavras com base em probabilidades aprendidas.

Um dos métodos mais simples para modelar a linguagem é por meio de uma Cadeia de Markov . Uma Cadeia de Markov é um modelo estatístico que opera na suposição de que a probabilidade de uma palavra ocorrer depende apenas de um conjunto limitado de palavras precedentes, em vez de todo o histórico do texto. Esse conceito é conhecido como propriedade de Markov. Em termos práticos, o modelo assume que a próxima palavra em uma sequência pode ser prevista apenas observando a(s) palavra(s) mais recente(s) — uma simplificação que torna o problema computacionalmente mais tratável, ao mesmo tempo em que captura padrões úteis nos dados.

Em um modelo de linguagem baseado em Cadeia de Markov:

  • O estado futuro (próxima palavra) depende apenas do estado atual (palavras anteriores): Isso significa que, uma vez que sabemos as últimas palavras (determinadas pela ordem do modelo), temos contexto suficiente para prever o que pode vir a seguir. O histórico inteiro da conversa ou texto não precisa ser considerado, o que reduz a complexidade.
  • Construímos uma distribuição de probabilidade de qual palavra vem a seguir, dadas as palavras precedentes: Conforme o modelo é treinado em um corpus de texto, ele aprende a probabilidade de várias palavras seguirem uma sequência dada. Essa distribuição de probabilidade é então usada durante a fase de geração para selecionar a próxima palavra na sequência, normalmente usando um processo de amostragem aleatória que respeita as probabilidades aprendidas.

Em nossa implementação, usaremos uma "ordem" configurável para determinar quantas palavras anteriores devem ser consideradas ao fazer previsões. Uma ordem mais alta fornece mais contexto, resultando potencialmente em um texto mais coerente e contextualmente relevante, pois o modelo tem mais informações sobre o que veio antes. Por outro lado, uma ordem mais baixa introduz mais aleatoriedade e pode levar a sequências de palavras mais criativas, embora menos previsíveis. Essa troca entre coerência e criatividade é uma consideração central na modelagem de linguagem.

Ao entender esses princípios básicos, podemos apreciar tanto a simplicidade dos modelos de Cadeia de Markov quanto as ideias fundamentais que sustentam modelos de linguagem neural mais complexos. Essa visão estendida não apenas ajuda a compreender a mecânica estatística por trás da predição de linguagem, mas também estabelece as bases para experimentar técnicas mais avançadas em processamento de linguagem natural.


Configurando seu ambiente Ruby

Antes de começar, certifique-se de ter o Ruby instalado no seu sistema. Você pode verificar sua versão do Ruby executando:

 ruby -v

Se o Ruby não estiver instalado, você pode baixá-lo em ruby-lang.org .

Para o nosso projeto, você pode querer criar um diretório e arquivo dedicados:

 mkdir tiny_llm cd tiny_llm touch llm.rb

Agora você está pronto para escrever seu código Ruby.


Coleta e pré-processamento de dados

Coletando dados de treinamento

Para um modelo de linguagem, você precisa de um corpus de texto. Você pode usar qualquer arquivo de texto para treinamento. Para nosso exemplo simples, você pode usar uma pequena amostra de texto, por exemplo:

 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

Pré-processamento dos dados

Antes do treinamento, é útil pré-processar o texto:

  • Tokenização: Dividir texto em palavras.
  • Normalização: opcionalmente, converta texto para letras minúsculas, remova pontuação, etc.

Para nossos propósitos, o método String#split do Ruby funciona bem o suficiente para tokenização.


Construindo o modelo da cadeia de Markov

Criaremos uma classe Ruby chamada MarkovChain para encapsular o comportamento do modelo. A classe incluirá:

  • Um inicializador para definir a ordem (número de palavras precedentes) da cadeia.
  • Um método train que constrói a cadeia a partir do texto de entrada.
  • Um método generate que produz novo texto por amostragem da cadeia.

Abaixo está o código completo do modelo:

 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? || !@chain.key?(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

Explicação do Código


  • **Inicialização:**O construtor initialize define a ordem (o padrão é 2) e cria um hash vazio para nossa cadeia. O hash recebe um bloco padrão para que cada nova chave comece como um array vazio.


  • **Treinando o modelo:**O método train pega uma sequência de texto, normaliza-a e divide-a em palavras. Usando each_cons , ele cria grupos consecutivos de palavras de order + 1 . As palavras de primeira order servem como chave, e a última palavra é anexada ao array de possíveis continuações para essa chave.


  • **Gerando texto:**O método generate começa com uma chave semente. Se nenhuma for fornecida, uma chave aleatória é escolhida. Ele então constrói iterativamente uma sequência procurando as últimas palavras order e amostrando a próxima palavra até que a contagem máxima de palavras seja alcançada.


Treinando o modelo

Agora que temos nossa classe MarkovChain , vamos treiná-la em alguns dados de texto.

 # 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!"

Ao executar o código acima (por exemplo, salvando-o em llm.rb e executando ruby llm.rb ), o modelo será treinado usando o texto de exemplo fornecido.


Gerando e testando texto

Depois que o modelo for treinado, você pode gerar um novo texto. Vamos adicionar algum código para gerar e imprimir um texto de amostra:

 # Generate new text using the trained model. generated_text = model.generate(50) puts "Generated Text:" puts generated_text

Você também pode tentar fornecer uma semente para geração de texto. Por exemplo, se você conhece uma das chaves no modelo (como "once upon" ), você pode fazer:

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

Ao experimentar diferentes sementes e parâmetros (como a ordem e o número máximo de palavras), você pode ver como a saída varia.


Exemplo completo: Treinamento e teste de um pequeno LLM

Aqui está o script Ruby completo combinando todos os passos acima:

 #!/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? || !@chain.key?(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

Executando o script

  1. Salve o script como llm.rb .
  2. Abra seu terminal e navegue até o diretório que contém llm.rb .
  3. Execute o script usando:
 ruby llm.rb

Você deverá ver uma saída indicando que o modelo foi treinado e, em seguida, dois exemplos de texto gerado.


Referência

A tabela a seguir resume algumas métricas de benchmark para diferentes versões de nossas implementações Tiny LLM. Cada métrica é explicada abaixo:

  • Modelo: O nome ou identificador de versão do modelo de linguagem.
  • Ordem: O número de palavras anteriores usadas na Cadeia de Markov para prever a próxima palavra. Uma ordem mais alta geralmente significa que mais contexto é usado, aumentando potencialmente a coerência.
  • Tempo de treinamento (ms): tempo aproximado necessário para treinar o modelo nos dados de texto fornecidos, medido em milissegundos.
  • Tempo de geração (ms): tempo necessário para gerar uma saída de texto de amostra, medido em milissegundos.
  • Uso de memória (MB): a quantidade de memória consumida pelo modelo durante o treinamento e a geração.
  • Classificação de coerência: uma classificação subjetiva (de 0 a 5) que indica quão coerente ou contextualmente apropriado é o texto gerado.

Abaixo está a tabela de markdown com os dados de benchmark:

Modelo

Ordem

Tempo de treinamento (ms)

Tempo de geração (ms)

Uso de memória (MB)

Avaliação de coerência

Pequeno LLM v1

2

50

10

10

3/5

Pequeno LLM v2

3

70

15

12

3,5/5

Pequeno LLM v3

4

100

20

15

4/5

Esses benchmarks fornecem uma rápida visão geral das compensações entre diferentes configurações de modelo. Conforme a ordem aumenta, o modelo tende a levar um pouco mais de tempo para treinar e gerar texto, e usa mais memória. No entanto, esses aumentos no consumo de recursos são frequentemente acompanhados por melhorias na coerência do texto gerado.

Conclusão

Neste tutorial, demonstramos como criar um modelo de linguagem muito simples usando Ruby. Aproveitando a técnica Markov Chain, construímos um sistema que:

  • Treina com texto de exemplo aprendendo transições de palavras.
  • Gera novo texto com base em padrões aprendidos.

Embora esse modelo de brinquedo esteja muito longe dos LLMs de nível de produção, ele serve como um trampolim para entender como os modelos de linguagem funcionam em um nível fundamental. Você pode expandir essa ideia incorporando técnicas mais avançadas, lidando melhor com pontuação ou até mesmo integrando Ruby com bibliotecas de aprendizado de máquina para modelos mais sofisticados.

Boa codificação!