В тази статия ще разгледаме как да създадем много прост езиков модел с помощта на Ruby. Докато истинските големи езикови модели (LLM) изискват огромни количества данни и изчислителни ресурси, ние можем да създадем модел играчка, който демонстрира много от основните концепции зад езиковото моделиране. В нашия пример ще изградим основен модел на Марковска верига, който се „учи“ от въведен текст и след това генерира нов текст въз основа на наблюдаваните модели.
Забележка: Този урок е предназначен за образователни цели и илюстрира опростен подход към езиковото моделиране. Това не е заместител на съвременните LLMs за дълбоко обучение като GPT-4, а по-скоро въведение в основните идеи.
Езиковият модел е система, която присвоява вероятности на последователности от думи. В основата си той е предназначен да улови статистическата структура на езика чрез изучаване на вероятността определена последователност да се появи в даден контекст. Това означава, че моделът анализира големи части от текст, за да разбере как думите обикновено следват една след друга, като по този начин му позволява да предвиди коя дума или фраза може да дойде следващата в последователността. Такива възможности са централни не само за задачи като генериране на текст и автоматично довършване, но и за различни приложения за обработка на естествен език (NLP), включително превод, обобщение и анализ на настроението.
Съвременните широкомащабни езикови модели (LLM) като GPT-4 използват техники за задълбочено обучение и масивни набори от данни за улавяне на сложни модели в езика. Те работят, като обработват въведен текст чрез множество слоеве от изкуствени неврони, което им позволява да разбират и генерират човешки текст със забележителна плавност. Въпреки това, зад тези сложни системи се крие същата фундаментална идея: разбиране и прогнозиране на последователности от думи въз основа на научени вероятности.
Един от най-простите методи за моделиране на език е чрез верига на Марков . Веригата на Марков е статистически модел, който работи при предположението, че вероятността думата да се появи зависи само от ограничен набор от предходни думи, а не от цялата история на текста. Тази концепция е известна като свойство на Марков. От практическа гледна точка, моделът предполага, че следващата дума в последователност може да бъде предвидена единствено чрез разглеждане на най-новата дума(и) - опростяване, което прави проблема изчислително по-податлив, като същевременно улавя полезни модели в данните.
В езиков модел, базиран на Маркова верига:
В нашата реализация ще използваме конфигурируем "ред", за да определим колко предишни думи трябва да се вземат предвид, когато се правят прогнози. По-високият ред осигурява повече контекст, което потенциално води до по-последователен и контекстуално подходящ текст, тъй като моделът има повече информация за това, което е било преди. Обратно, по-нисък ред въвежда повече произволност и може да доведе до по-креативни, макар и по-малко предвидими, последователности от думи. Този компромис между съгласуваност и креативност е централно съображение при езиковото моделиране.
Като разберем тези основни принципи, можем да оценим както простотата на моделите на веригата на Марков, така и основополагащите идеи, които са в основата на по-сложни невронни езикови модели. Този разширен изглед не само помага за разбирането на статистическата механика зад езиковата прогноза, но също така полага основите за експериментиране с по-напреднали техники в обработката на естествен език.
Преди да започнете, уверете се, че имате инсталиран 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
Преди обучение е полезно да обработите предварително текста:
За нашите цели методът String#split
на Ruby работи достатъчно добре за токенизиране.
Ще създадем Ruby клас с име MarkovChain
за да капсулираме поведението на модела. Класът ще включва:
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
Като експериментирате с различни семена и параметри (като ред и максимален брой думи), можете да видите как изходът варира.
Ето пълния 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
Трябва да видите изход, показващ, че моделът е обучен и след това два примера за генериран текст.
Следната таблица обобщава някои сравнителни показатели за различни версии на нашите реализации на Tiny LLM. Всеки показател е обяснен по-долу:
По-долу е таблицата с маркдаун с референтните данни:
Модел | ред | Време за тренировка (ms) | Време за генериране (ms) | Използване на паметта (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. Използвайки техниката на веригата на Марков, ние изградихме система, която:
Въпреки че този модел играчка е далеч от LLMs на производствено ниво, той служи като стъпало за разбиране на това как работят езиковите модели на основно ниво. Можете да разширите тази идея, като включите по-напреднали техники, по-добре боравите с пунктуацията или дори интегрирате Ruby с библиотеки за машинно обучение за по-сложни модели.
Приятно кодиране!