I den här artikeln kommer vi att gå igenom hur man skapar en mycket enkel språkmodell med hjälp av Ruby. Även om sanna stora språkmodeller (LLM) kräver enorma mängder data och beräkningsresurser, kan vi skapa en leksaksmodell som visar många av kärnkoncepten bakom språkmodellering. I vårt exempel kommer vi att bygga en grundläggande Markov Chain-modell som "lär sig" från inmatad text och sedan genererar ny text baserat på mönstren den observerade.
Obs: Denna handledning är avsedd för utbildningsändamål och illustrerar en förenklad metod för språkmodellering. Det är inte en ersättning för moderna djuplärande LLMs som GPT-4 utan snarare en introduktion till de underliggande idéerna.
En språkmodell är ett system som tilldelar sannolikheter till sekvenser av ord. I sin kärna är det utformat för att fånga språkets statistiska struktur genom att lära sig sannolikheten för att en viss sekvens inträffar i ett givet sammanhang. Detta innebär att modellen analyserar stora textkroppar för att förstå hur ord vanligtvis följer varandra, vilket gör att den kan förutsäga vilket ord eller fras som kan komma härnäst i en följd. Sådana funktioner är centrala inte bara för uppgifter som textgenerering och autokomplettering utan också för en mängd olika applikationer för naturlig språkbehandling (NLP), inklusive översättning, sammanfattning och sentimentanalys.
Moderna storskaliga språkmodeller (LLM) som GPT-4 använder djupinlärningstekniker och massiva datauppsättningar för att fånga komplexa mönster i språk. De fungerar genom att bearbeta inmatad text genom många lager av artificiella neuroner, vilket gör det möjligt för dem att förstå och generera människoliknande text med anmärkningsvärt flyt. Men bakom dessa sofistikerade system ligger samma grundläggande idé: att förstå och förutsäga ordsekvenser baserat på inlärda sannolikheter.
En av de enklaste metoderna att modellera språk är genom en Markov-kedja . En Markov-kedja är en statistisk modell som arbetar utifrån antagandet att sannolikheten för att ett ord inträffar bara beror på en begränsad uppsättning föregående ord, snarare än hela textens historia. Detta koncept är känt som Markov-egendomen. I praktiska termer antar modellen att nästa ord i en sekvens kan förutsägas enbart genom att titta på det eller de senaste orden - en förenkling som gör problemet beräkningsmässigt mer löst samtidigt som det fångar användbara mönster i data.
I en Markov Chain-baserad språkmodell:
I vår implementering kommer vi att använda en konfigurerbar "ordning" för att bestämma hur många tidigare ord som ska beaktas när vi gör förutsägelser. En högre ordning ger mer sammanhang, vilket potentiellt kan resultera i mer sammanhängande och kontextuellt relevant text, eftersom modellen har mer information om vad som kom innan. Omvänt introducerar en lägre ordning mer slumpmässighet och kan leda till mer kreativa, om än mindre förutsägbara, ordsekvenser. Denna avvägning mellan koherens och kreativitet är en central faktor i språkmodellering.
Genom att förstå dessa grundläggande principer kan vi uppskatta både enkelheten i Markov Chain-modeller och de grundläggande idéerna som ligger till grund för mer komplexa neurala språkmodeller. Denna utökade vy hjälper inte bara till att förstå den statistiska mekaniken bakom språkförutsägelse utan lägger också grunden för att experimentera med mer avancerade tekniker inom naturlig språkbehandling.
Innan du börjar, se till att du har Ruby installerat på ditt system. Du kan kontrollera din Ruby-version genom att köra:
ruby -v
Om Ruby inte är installerad kan du ladda ner den från ruby-lang.org .
För vårt projekt kanske du vill skapa en dedikerad katalog och fil:
mkdir tiny_llm cd tiny_llm touch llm.rb
Nu är du redo att skriva din Ruby-kod.
För en språkmodell behöver du en textkorpus. Du kan använda vilken textfil som helst för träning. För vårt enkla exempel kan du använda ett litet exempel på text, till exempel:
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
Innan träning är det användbart att förbehandla texten:
För våra syften fungerar Rubys String#split
-metod tillräckligt bra för tokenisering.
Vi kommer att skapa en Ruby-klass vid namn MarkovChain
för att kapsla in modellens beteende. Klassen kommer att innehålla:
train
som bygger kedjan från inmatad text.generate
som producerar ny text genom sampling från kedjan.Nedan är den fullständiga koden för modellen:
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
**Initiering:** initialize
ställer in ordningen (standard är 2) och skapar en tom hash för vår kedja. Hashen ges ett standardblock så att varje ny nyckel börjar som en tom array.
**Träna modellen:** train
tar en textsträng, normaliserar den och delar upp den i ord. Genom att använda each_cons
skapar den på varandra följande grupper av ord av längdordningen order + 1
. Orden av första order
fungerar som nyckeln, och det sista ordet läggs till raden av möjliga fortsättningar för den nyckeln.
**Generera text:** generate
börjar med en frönyckel. Om ingen tillhandahålls, väljs en slumpmässig nyckel. Den bygger sedan iterativt en sekvens genom att slå upp de sista order
och sampla nästa ord tills det maximala antalet ord har uppnåtts.
Nu när vi har vår MarkovChain
klass, låt oss träna den på lite textdata.
# 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!"
När du kör ovanstående kod (till exempel genom att spara den i llm.rb
och köra ruby llm.rb
), kommer modellen att tränas med hjälp av den medföljande exempeltexten.
När modellen är tränad kan du generera ny text. Låt oss lägga till lite kod för att generera och skriva ut en exempeltext:
# Generate new text using the trained model. generated_text = model.generate(50) puts "Generated Text:" puts generated_text
Du kan också prova att tillhandahålla ett frö för textgenerering. Om du till exempel känner till en av nycklarna i modellen (som "once upon"
), kan du göra:
seed = "once upon" generated_text_with_seed = model.generate(50, seed) puts "\nGenerated Text with seed '#{seed}':" puts generated_text_with_seed
Genom att experimentera med olika frön och parametrar (som ordning och maximalt antal ord) kan du se hur resultatet varierar.
Här är det kompletta Ruby-skriptet som kombinerar alla ovanstående steg:
#!/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
Du bör se utdata som indikerar att modellen har tränats och sedan två exempel på genererad text.
Följande tabell sammanfattar några benchmarkmått för olika versioner av våra Tiny LLM-implementeringar. Varje mätvärde förklaras nedan:
Nedan är nedräkningstabellen med referensdata:
Modell | Beställa | Träningstid (ms) | Generationstid (ms) | Minnesanvändning (MB) | Koherensbetyg |
---|---|---|---|---|---|
Tiny LLM v1 | 2 | 50 | 10 | 10 | 3/5 |
Tiny LLM v2 | 3 | 70 | 15 | 12 | 3,5/5 |
Tiny LLM v3 | 4 | 100 | 20 | 15 | 4/5 |
Dessa riktmärken ger en snabb översikt över avvägningarna mellan olika modellkonfigurationer. När beställningen ökar tenderar modellen att ta något längre tid att träna och generera text, och den använder mer minne. Dessa ökningar av resursförbrukning åtföljs dock ofta av förbättringar i den genererade textens sammanhållning.
I den här handledningen demonstrerade vi hur man skapar en mycket enkel språkmodell med Ruby. Genom att utnyttja Markov Chain-tekniken byggde vi ett system som:
Även om denna leksaksmodell är långt ifrån produktionsnivå LLM, fungerar den som en språngbräda för att förstå hur språkmodeller fungerar på en grundläggande nivå. Du kan utöka denna idé genom att införliva mer avancerade tekniker, hantera skiljetecken bättre eller till och med integrera Ruby med maskininlärningsbibliotek för mer sofistikerade modeller.
Glad kodning!