En este artículo, veremos cómo crear un modelo de lenguaje muy simple con Ruby. Si bien los verdaderos modelos de lenguaje grandes (LLM) requieren enormes cantidades de datos y recursos computacionales, podemos crear un modelo de juguete que demuestre muchos de los conceptos básicos detrás del modelado de lenguaje. En nuestro ejemplo, crearemos un modelo básico de cadena de Markov que "aprende" del texto de entrada y luego genera un texto nuevo en función de los patrones observados.
Nota: Este tutorial tiene fines educativos e ilustra un enfoque simplificado del modelado de lenguaje. No sustituye a los LLM de aprendizaje profundo modernos como GPT-4, sino que es una introducción a las ideas subyacentes.
Un modelo de lenguaje es un sistema que asigna probabilidades a secuencias de palabras. En esencia, está diseñado para capturar la estructura estadística del lenguaje mediante el aprendizaje de la probabilidad de que una secuencia particular ocurra en un contexto determinado. Esto significa que el modelo analiza grandes cantidades de texto para comprender cómo las palabras suelen sucederse unas a otras, lo que le permite predecir qué palabra o frase podría venir a continuación en una secuencia. Estas capacidades son fundamentales no solo para tareas como la generación de texto y el autocompletado, sino también para una variedad de aplicaciones de procesamiento del lenguaje natural (PLN), incluidas la traducción, el resumen y el análisis de sentimientos.
Los modelos de lenguaje a gran escala (LLM, por sus siglas en inglés) modernos, como GPT-4, utilizan técnicas de aprendizaje profundo y conjuntos de datos masivos para capturar patrones complejos en el lenguaje. Funcionan procesando el texto de entrada a través de numerosas capas de neuronas artificiales, lo que les permite comprender y generar texto similar al humano con una fluidez notable. Sin embargo, detrás de estos sofisticados sistemas se esconde la misma idea fundamental: comprender y predecir secuencias de palabras en función de probabilidades aprendidas.
Uno de los métodos más simples para modelar el lenguaje es mediante una cadena de Markov . Una cadena de Markov es un modelo estadístico que opera bajo el supuesto de que la probabilidad de que aparezca una palabra depende únicamente de un conjunto limitado de palabras anteriores, en lugar de la historia completa del texto. Este concepto se conoce como la propiedad de Markov. En términos prácticos, el modelo supone que la siguiente palabra en una secuencia se puede predecir únicamente observando la(s) palabra(s) más recientes, una simplificación que hace que el problema sea computacionalmente más manejable mientras sigue capturando patrones útiles en los datos.
En un modelo de lenguaje basado en cadenas de Markov:
En nuestra implementación, utilizaremos un "orden" configurable para determinar cuántas palabras anteriores se deben tener en cuenta al hacer predicciones. Un orden más alto proporciona más contexto, lo que potencialmente da como resultado un texto más coherente y contextualmente relevante, ya que el modelo tiene más información sobre lo que vino antes. Por el contrario, un orden más bajo introduce más aleatoriedad y puede dar lugar a secuencias de palabras más creativas, aunque menos predecibles. Este equilibrio entre coherencia y creatividad es una consideración central en el modelado del lenguaje.
Al comprender estos principios básicos, podemos apreciar tanto la simplicidad de los modelos de cadenas de Markov como las ideas fundamentales que sustentan los modelos neuronales más complejos del lenguaje. Esta visión ampliada no solo ayuda a comprender la mecánica estadística detrás de la predicción del lenguaje, sino que también sienta las bases para experimentar con técnicas más avanzadas en el procesamiento del lenguaje natural.
Antes de comenzar, asegúrese de tener Ruby instalado en su sistema. Puede comprobar su versión de Ruby ejecutando lo siguiente:
ruby -v
Si Ruby no está instalado, puedes descargarlo desde ruby-lang.org .
Para nuestro proyecto, es posible que desees crear un directorio y un archivo dedicados:
mkdir tiny_llm cd tiny_llm touch llm.rb
Ahora estás listo para escribir tu código Ruby.
Para un modelo de lenguaje, necesitas un corpus de texto. Puedes usar cualquier archivo de texto para el entrenamiento. Para nuestro ejemplo simple, puedes usar una pequeña muestra de texto, por ejemplo:
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
Antes del entrenamiento, es útil preprocesar el texto:
Para nuestros propósitos, el método String#split
de Ruby funciona bastante bien para la tokenización.
Crearemos una clase Ruby llamada MarkovChain
para encapsular el comportamiento del modelo. La clase incluirá:
train
que construye la cadena a partir del texto de entrada.generate
que produce texto nuevo tomando muestras de la cadena.A continuación se muestra el código completo del 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? || [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
**Inicialización:** El constructor initialize
establece el orden (el valor predeterminado es 2) y crea un hash vacío para nuestra cadena. El hash recibe un bloque predeterminado para que cada nueva clave comience como una matriz vacía.
**Entrenamiento del modelo:** El método train
toma una cadena de texto, la normaliza y la divide en palabras. Mediante each_cons
, crea grupos consecutivos de palabras de longitud order + 1
. Las palabras de primer order
sirven como clave y la última palabra se agrega a la matriz de posibles continuaciones para esa clave.
**Generación de texto:** El método generate
comienza con una clave inicial. Si no se proporciona ninguna, se elige una clave aleatoria. Luego, construye iterativamente una secuencia buscando las últimas palabras order
y tomando muestras de la siguiente palabra hasta que se alcanza el recuento máximo de palabras.
Ahora que tenemos nuestra clase MarkovChain
, entrenémosla con algunos datos 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!"
Cuando ejecuta el código anterior (por ejemplo, guardándolo en llm.rb
y ejecutando ruby llm.rb
), el modelo se entrenará utilizando el texto de muestra proporcionado.
Una vez que se haya entrenado el modelo, se puede generar un texto nuevo. Agreguemos un código para generar e imprimir un texto de muestra:
# Generate new text using the trained model. generated_text = model.generate(50) puts "Generated Text:" puts generated_text
También puedes intentar proporcionar una semilla para la generación de texto. Por ejemplo, si conoces una de las claves del modelo (como "once upon"
), puedes hacer lo siguiente:
seed = "once upon" generated_text_with_seed = model.generate(50, seed) puts "\nGenerated Text with seed '#{seed}':" puts generated_text_with_seed
Al experimentar con diferentes semillas y parámetros (como el orden y el número máximo de palabras), puedes ver cómo varía el resultado.
Aquí está el script Ruby completo que combina todos los pasos anteriores:
#!/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
Debería ver un resultado que indique que el modelo ha sido entrenado y luego dos ejemplos de texto generado.
La siguiente tabla resume algunas métricas de referencia para diferentes versiones de nuestras implementaciones de Tiny LLM. Cada métrica se explica a continuación:
A continuación se muestra la tabla de rebajas con los datos de referencia:
Modelo | Orden | Tiempo de entrenamiento (ms) | Tiempo de generación (ms) | Uso de memoria (MB) | Calificación de coherencia |
---|---|---|---|---|---|
Pequeña LLM v1 | 2 | 50 | 10 | 10 | 3/5 |
Pequeña LLM v2 | 3 | 70 | 15 | 12 | 3,5/5 |
Pequeña LLM v3 | 4 | 100 | 20 | 15 | 4/5 |
Estos puntos de referencia ofrecen una descripción general rápida de las ventajas y desventajas entre las diferentes configuraciones del modelo. A medida que aumenta el orden, el modelo tiende a tardar un poco más en entrenarse y generar texto, y utiliza más memoria. Sin embargo, estos aumentos en el consumo de recursos suelen ir acompañados de mejoras en la coherencia del texto generado.
En este tutorial, demostramos cómo crear un modelo de lenguaje muy simple con Ruby. Aprovechando la técnica de la cadena de Markov, creamos un sistema que:
Si bien este modelo de juguete está muy lejos de los LLM de nivel de producción, sirve como un trampolín para comprender cómo funcionan los modelos de lenguaje a un nivel fundamental. Puede ampliar esta idea incorporando técnicas más avanzadas, manejando mejor la puntuación o incluso integrando Ruby con bibliotecas de aprendizaje automático para obtener modelos más sofisticados.
¡Feliz codificación!