Markov-kæden er enkel: Lad os se på princippet i detaljer. Tilføjelse af tweets til databasen og udstationering på Twitter

Det blev beskrevet, hvordan man træner et neutronnetværk, så det spiller Mario eller styrer en robot. Men kan neurale netværk generere tekst? Markov-kæder kan hjælpe med dette.

Det er derfor, jeg “elsker” den russisksprogede Wikipedia, for ethvert simpelt fænomen/ligning/regel, især fra matematik, er umiddelbart beskrevet på en så generel måde, med så åndssvage formler, at man ikke kan finde ud af det uden en halv liter. Desuden gider forfatterne af artiklerne ikke give en simpel beskrivelse (mindst et par sætninger) menneskeligt sprog, og gå direkte til formlerne.

Hvis nogen vil vide, hvad Markov-kæder er, så vil han i den første oversættelse finde ud af, at:
"Markov-kæden er en sekvens tilfældige begivenheder med et begrænset eller tælleligt antal udfald, kendetegnet ved den egenskab, at fremtiden, løst sagt, med en fast nutid er uafhængig af fortiden. Opkaldt til ære for A. A. Markov (senior)."

Og dette på trods af, at den grundlæggende idé om Markov-kæder er meget enkel, men det er simpelthen umuligt at forstå dette fra Wikipedia uden en matematisk uddannelse.

Markov-kæder er blot en beskrivelse af sandsynligheden for, at et system går fra en tilstand til en anden. Alle tilstande kan beskrives ved grafens toppunkter. Sådanne knudepunkter kan for eksempel være menneskelige positioner: [liggende], [siddende], [stående], [gående]

Her kan du se, at grafen er rettet, hvilket betyder, at det ikke er muligt at komme fra hver stat til en anden. Hvis du for eksempel ligger ned, er det umuligt at gå med det samme. Du skal først sætte dig ned, så stå op og først derefter gå. Men du kan falde og ende med at ligge ned fra enhver stilling))
Hver forbindelse har en vis sandsynlighed. Så for eksempel er sandsynligheden for at falde fra en stående stilling meget lille; det er meget mere sandsynligt at stå længere, gå eller sidde ned. Summen af ​​alle sandsynligheder er 1.

Markov-kæder giver dig blandt andet mulighed for at generere events. På den ene eller anden måde er de fleste tekstgeneratorer bygget på Markov-kæder.

Lad os prøve at skrive en generator af tærter.

Tærter

Tærter - kvad uden rim, tegnsætning, tal, uden store bogstaver. Antallet af stavelser skal være 9-8-9-8.


De fleste tekstgeneratorer bruger morfologiske analysatorer. Men vi gør det nemmere. Lad os lige dele ordene op i stavelser og udregne sandsynligheden for, at den ene stavelse kommer efter den anden. Det vil sige, at knudepunkterne på grafen vil være stavelserne, kanterne og deres vægte - hyppigheden af ​​den anden stavelse efter den første.
Dernæst fodrer vi programmet med halvtreds tærter.

For eksempel, efter stavelsen "at" kan der være følgende stavelser (kanter og deres vægte):
"chem" (1) "ho" (4) "mig" (1) "du" (2) "chi" (4) "yatel" (4) "gik" (5) "ku" (1) " " (9) "su"(1) "vych"(3) "mi"(1) "kos"(1) "ob"(1) "det"(2) "drev"(1) "uchi"(1) ) "mu"(1) "bi"(1) "tse"(1) "int"(2) "tom"(1) "ko"(1) "aksel"(1) "nes"(1) " det"(1) "men"(1) "vez"(1) "meth"(1) "vet"(1) "dia"(1) "dig"(1)

Nu skal du bare tage en tilfældig stavelse (for eksempel "at"). Summen af ​​alle stavelser, der kommer efter den, er 58. Nu skal du tage den næste stavelse under hensyntagen til hyppigheden (antallet) af disse stavelser:

size_t nth = rand() % count;

size_t all = 0 ;

for (konst auto &n: næste) (

Alle += n.tæller;

hvis (alle >= n.)

returnere n.ord;

Således genererer vi linjer, så den første linje har 9 stavelser, den anden - 8, derefter 9 og 8, får vi:

Der var engang en joke om vækkeuret
han blev kidnappet mens det var
din chef er her ja
Onegin effekter sofa

Indtil videre ligner det ikke en særlig sammenhængende tekst. Ikke-eksisterende ord ("poku") støder man ofte på. Der er nu kun én stavelse som nøgle. Men det er svært at bygge en sætning baseret på én stavelse. Lad os øge antallet af stavelser, på grundlag af hvilke vi vil generere den næste stavelse til mindst 3:

Nok asfalt til sindet
klokken er syv divideret med
bordet er taget ud, den sorte boks er
han voksede op, tog hævn, fandt
Her er den første tærte, der mere eller mindre kan forveksles med at være skrevet af en person.
For at gøre teksten mere meningsfuld skal du bruge morfologiske analysatorer, og så vil noderne ikke være stavelser, men metabeskrivelser af ord (for eksempel "verbum, flertal, datid").

Sådanne programmer giver dig allerede mulighed for at skrive mere "meningsfuld" tekst. For eksempel er en rooter en artikel skrevet af en videnskabelig tekstgenerator, som blev gennemgået og endda publiceret i et videnskabeligt tidsskrift.

En Markov-kæde er en række begivenheder, hvor hver efterfølgende begivenhed afhænger af den foregående. I denne artikel vil vi undersøge dette koncept mere detaljeret.

En Markov-kæde er en almindelig og ret simpel måde at modellere tilfældige hændelser på. Brugt i de fleste forskellige områder, fra tekstgenerering til finansiel modellering. For det meste berømt eksempel er SubredditSimulator. I I dette tilfælde Markov-kæden bruges til at automatisere indholdsoprettelse gennem hele subreddit.

Markov-kæden er overskuelig og nem at bruge, fordi den kan implementeres uden brug af statistiske eller matematiske begreber. Markov-kæden er ideel til at lære probabilistisk modellering og datavidenskab.

Scenarie

Forestil dig, at der kun er to vejrforhold: Det kan være enten solrigt eller overskyet. Du kan altid nøjagtigt bestemme vejret i dette øjeblik. Garanteret klart eller overskyet.

Nu vil du lære at forudsige vejret i morgen. Intuitivt forstår du, at vejret ikke kan ændre sig dramatisk på én dag. Dette påvirkes af mange faktorer. Morgendagens vejr afhænger direkte af det aktuelle osv. For at forudsige vejret indsamler man således data for flere år og kommer frem til, at efter en overskyet dag er sandsynligheden for en solskinsdag 0,25. Det er logisk at antage, at sandsynligheden for to overskyede dage i træk er 0,75, da vi kun har to mulige vejrforhold.

Nu kan du forudsige vejret flere dage i forvejen baseret på det aktuelle vejr.

Dette eksempel viser Nøglekoncepter Markov kæder. En Markov-kæde består af et sæt overgange, der er bestemt af en sandsynlighedsfordeling, som igen opfylder Markov-egenskaben.

Bemærk venligst, at i eksemplet afhænger sandsynlighedsfordelingen kun af overgangene med aktuelle dag til den næste. Det her unik ejendom Markov-processen - den gør dette uden at bruge hukommelse. Typisk er denne tilgang ikke i stand til at skabe en sekvens, hvor enhver tendens observeres. For eksempel, mens en Markov-kæde kan simulere skrivestil baseret på hyppigheden af ​​et ords brug, kan den ikke skabe tekster med dyb mening, da det kun kan fungere med store tekster. Det er derfor, en Markov-kæde ikke kan producere kontekstafhængigt indhold.

Model

Formelt set er en Markov-kæde en probabilistisk automat. Over normalt repræsenteret som en matrix. Hvis en Markov-kæde har N mulige stater, så vil matricen have formen N x N, hvor indtastningen (I, J) vil være sandsynligheden for overgang fra tilstand I til tilstand J. Desuden skal en sådan matrix være stokastisk, det vil sige rækkerne hhv. kolonner skal tilføje op til én. I en sådan matrix vil hver række have sin egen sandsynlighedsfordeling.

Generelt billede af en Markov-kæde med tilstande i form af cirkler og kanter i form af overgange.

Et eksempel på overgangsmatrix med tre mulige tilstande.

Markov-kæden har initial vektor tilstande, repræsenteret som en N x 1 matrix. Den beskriver sandsynlighedsfordelingerne for starten i hver af de N mulige tilstande. Indgangen I beskriver sandsynligheden for, at kæden starter i tilstand I.

Disse to strukturer er ganske tilstrækkelige til at repræsentere en Markov-kæde.

Vi har allerede diskuteret, hvordan man får sandsynligheden for en overgang fra en tilstand til en anden, men hvad med at få den sandsynlighed i et par trin? For at gøre dette skal vi bestemme sandsynligheden for overgang fra tilstand I til tilstand J i M trin. Det er faktisk meget enkelt. Overgangsmatricen P kan bestemmes ved at beregne (I, J) ved at hæve P til potensen M. For små værdier af M kan dette gøres manuelt ved gentagen multiplikation. Men for store værdier M hvis du er bekendt med lineær algebra, mere effektiv måde at hæve en matrix til en potens vil først diagonalisere denne matrix.

Markov-kæden: konklusion

Når du nu ved, hvad en Markov-kæde er, kan du nemt implementere den i et af programmeringssprogene. Simple kæder Markov er grundlaget for at studere mere komplekse metoder modellering.

Jeg gennemsøgte foraene og ledte efter spørgsmål, som Python-programmører bliver stillet under interviews, og stødte på et meget vidunderligt. Jeg vil citere ham frit: "De bad mig om at skrive en nonsensgenerator baseret på en n. ordens Markov-kæde." "Men jeg har ikke sådan en generator endnu!" - råbte min indre stemme- "Skynd dig, åben sublim og skriv!" - fortsatte han vedholdende. Nå, jeg var nødt til at adlyde.

Og her vil jeg fortælle dig, hvordan jeg lavede det.

Det blev straks besluttet, at generatoren ville udtrykke alle sine tanker på Twitter og sin hjemmeside. Jeg valgte Flask og PostgreSQL som hovedteknologierne. De vil kommunikere med hinanden gennem SQLAlchemy.

Struktur.

Så. På følgende måde modeller ser sådan ud:
klasse Srt(db.Model): id = db.Column(db.Integer, primary_key = True) set_of_words = db.Column(db.Text()) list_of_words = db.Column(db.Text()) class UpperWords(db .Model): word = db.Column(db.String(40), index = True, primary_key = True, unique = True) def __repr__(selv): returner self.word class Phrases(db.Model): id = db .Column(db.Integer, primary_key = True) created = db.Column(db.DateTime, default=datetime.datetime.now) phrase = db.Column(db.String(140), index = True) def __repr__(selv ): return str(selv.sætning)
Som kildetekster Det blev besluttet at tage undertekster fra populære tv-serier. Srt-klassen gemmer et ordnet sæt af alle ord fra de behandlede undertekster for én episode og et unikt sæt af de samme ord (uden gentagelser). Dette vil gøre det nemmere for botten at søge efter en sætning i specifikke undertekster. Det vil først kontrollere, om sættet af ord er indeholdt i sættet af ord i underteksterne, og derefter se, om de ligger der i i den rigtige rækkefølge.

Det første ord i sætningen fra teksten er et tilfældigt ord, der starter med store bogstaver. UpperWords bruges til at gemme sådanne ord. Ordene er skrevet der på samme måde uden gentagelser.

Nå, klassen Phrases er nødvendig for at gemme allerede genererede tweets.
Strukturen er desperat enkel.

Undertekstparseren af ​​.srt-formatet er inkluderet i et separat modul add_srt.py. Der er ikke noget ekstraordinært der, men hvis nogen er interesseret, er alle kilderne på GitHub.

Generator.

Først skal du vælge det første ord til dit tweet. Som nævnt tidligere, vil dette være et hvilket som helst ord fra UpperWords-modellen. Dens valg er implementeret i funktionen:
def add_word(word_list, n): hvis ikke word_list: word = db.session.query(models.UpperWords).order_by(func.random()).first().word #postgre elif len(word_list)<= n: word = get_word(word_list, len(word_list)) else: word = get_word(word_list, n) if word: word_list.append(word) return True else: return False
Valget af dette ord implementeres direkte af linjen:

Word = db.session.query(models.UpperWords).order_by(func.random()).first().word

Hvis du bruger MySQL, så skal du bruge func.rand() i stedet for func.random(). Dette er den eneste forskel i denne implementering; alt andet vil fungere fuldstændig identisk.

Hvis det første ord allerede er der, ser funktionen på længden af ​​kæden, og afhængigt af dette vælger vi med hvilket antal ord i teksten vi skal sammenligne vores liste (n. ordens kæde) og få næste ord.

Og vi får det næste ord i get_word-funktionen:
def get_word(word_list, n): queries = models.Srt.query.all() query_list = list() for query i queries: if set(word_list)<= set(query.set_of_words.split()): query_list.append(query.list_of_words.split()) if query_list: text = list() for lst in query_list: text.extend(lst) indexies = ) if text == word_list] word = text return word else: return False
Først og fremmest kører scriptet gennem alle indlæste undertekster og tjekker, om vores sæt af ord er inkluderet i sættet af ord med specifikke undertekster. Derefter kombineres teksterne i de filtrerede undertekster til én liste, og der søges efter matchende sætninger, og positionerne af ordene efter disse sætninger returneres. Det hele ender med et blindt ordvalg. Alt er som i livet.
Sådan tilføjes ord til listen. Selve tweetet er samlet i en funktion:
def get_twit(): word_list = list() n = N mens len(" ".join(word_list))<140: if not add_word(word_list, n): break if len(" ".join(word_list))>140: word_list.pop() går i stykker, mens word_list[-1][-1] ikke er i ".?!!": word_list.pop() returnerer " ".join(word_list)
Det er meget enkelt – tweetet må ikke overstige 140 tegn og slutte med et punktum, der afslutter sætningen. Alle. Generatoren har gjort sit arbejde.

Vises på webstedet.

views.py-modulet håndterer visningen på webstedet.
@app.route("/") def index(): return render_template("main/index.html")
Den viser blot skabelonen. Alle tweets vil blive trukket fra den ved hjælp af js.
@app.route("/page") def page(): page = int(request.args.get("page")) diff = int(request.args.get("forskel")) grænse = 20 sætninger = models.Phrases.query.order_by(-models.Phrases.id).all() pages = math.ceil(len(phrases)/float(limit)) count = len(phrases) phrases = phrases return json.dumps(( "phrases":phrases, "pages":pages, "count":count), cls=controllers.AlchemyEncoder)
Returnerer tweets fra en bestemt side. Dette er nødvendigt for uendelig rulning. Alt er ret almindeligt. diff – antallet af tweets tilføjet efter at webstedet blev indlæst under opdateringen. Prøven af ​​tweets for siden bør flyttes med dette beløb.

Og selve opdateringen:
@app.route("/update") def update(): last_count = int(request.args.get("count")) phrases = models.Phrases.query.order_by(-models.Phrases.id).all( ) count = len(phrases) if count > last_count: phrases = phrases[:count-last_count] return json.dumps(("phrases":phrases, "count":count), cls=controllers.AlchemyEncoder) else: return json .dumps(("count":count))
På klientsiden kaldes den hvert n. sekund og indlæser nyligt tilføjede tweets i realtid. Sådan fungerer vores tweet-display. (Hvis nogen er interesseret, kan du se på AlchemyEncoder-klassen i controllers.py, den bruges til at serialisere tweets modtaget fra SQLAlchemy)

Tilføjelse af tweets til databasen og udstationering på Twitter.

Jeg brugte tweepy til at skrive på Twitter. Meget praktisk batteri, starter med det samme.

Hvad det ligner:
def twit(): phrase = get_twit() twited = models.Phrases(phrase=phrase) db.session.add(twited) db.session.commit() auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET) auth.set_access_TOKEN( , ACCESS_TOKEN_SECRET) api = tweepy.API(auth) api.update_status(status=sætning)
Jeg placerede opkaldet til denne funktion i cron.py i roden af ​​projektet, og som du måske kan gætte, er det lanceret af cron. Hver halve time tilføjes et nyt tweet til databasen og Twitter.


Alt fungerede!

Endelig.

I øjeblikket har jeg downloadet alle underteksterne til serierne “Friends” og “The Big Bang Theory”. Indtil videre har jeg valgt, at graden af ​​Markov-kæden skal være lig med to (efterhånden som undertekstgrundlaget stiger, vil graden stige). Du kan se, hvordan det fungerer i

I webkonstruktion og SEO bruges Markov-kæder til at generere pseudo-meningsfulde tekster baseret på kildetekster. Dette bruges til at stemple døråbninger med givne søgeord, til at skrive indholdstekstmasse og lignende "sorte" tricks. Heldigvis har søgemaskiner lært effektivt at identificere indhold skabt baseret på Markov-kæder og forbyde sådanne kloge mennesker. Jeg vil ikke lære dig sådanne teknologier, der er specielle lortesider til det, jeg er kun interesseret i softwareimplementeringen af ​​algoritmen.


En Markov-kæde er en sekvens af forsøg, hvor kun én af de k uforenelige begivenheder Ai fra hele gruppen optræder. I dette tilfælde afhænger den betingede sandsynlighed pij(s) for, at hændelsen Aj finder sted i det s. forsøg, forudsat at hændelsen Ai forekommer i det (s - 1). forsøg, ikke af resultaterne af tidligere forsøg.

De, der vil sprænge hjernen, kan læse om den matematiske model. På menneskeligt sprog koger alle disse formler ned til følgende. I kildeteksten defineres ord og bibeholdes rækkefølgen af ​​hvilke ord der kommer efter. Derefter skabes der på baggrund af disse data en ny tekst, hvor selve ordene er valgt tilfældigt, men sammenhængen mellem dem er bevaret. Lad os tage et børnerim som eksempel:

På grund af skoven, på grund af bjergene
Bedstefar Egor kommer:
mig selv på en hest,
kone på en ko,
børn på kalve,
børnebørn på geder.

Lad os analysere teksten i links og links

På grund af [skov, bjerge]
skove [på grund af]
bjerge [ture]
[bedstefar] kommer
bedstefar [Egor]
Egor [sig selv]
mig selv [på]
på [hest, ko, kalve, børn]
hest [kone]
kone [på]
ko [børn]
børn [på]
kalve [børnebørn]
børnebørn [på]

Linkene på denne liste repræsenterer unikke ord fra teksten, og linkene i firkantede parenteser er links - en liste over ord, der kan stå efter det ord.

Ved generering af tekst fra en liste over links, ved den første iteration, vælges et tilfældigt link, dets forbindelser bestemmes, et tilfældigt link vælges fra listen over links og accepteres som et nyt link. Derefter gentages handlingen, indtil den ønskede tekststørrelse er nået. Resultatet kan for eksempel være noget som dette:

Egor selv på en kalv, børnebørn på en hest, kone på en ko, børn på en ko
I dette eksempel adskiller den resulterende tekst sig lidt fra den originale tekst, fordi den oprindelige tekst er meget kort. Hvis du tager en indledende ordbog på flere kilobyte eller endda megabyte, vil outputtet være en fuldstændig sammenhængende tekst, selvom det ikke giver nogen mening.

  1. // Læs kildeteksten, på grundlag af hvilken en ny vil blive genereret
  2. $str = file_get_contents("markov.txt");
  3. // Indstil systemkodning
  4. setlocale(LC_ALL, "ru_RU.CP1251");
  5. // Fjern tegn fra teksten undtagen tal, bogstaver og nogle tegnsætningstegn
  6. $str = eregi_replace ("[^-a-zа-я0-9 !\?\.\,]" , " " , $str );
  7. // Ryd op mellemrum, før du afslutter sætninger
  8. $str = eregi_replace (" (1,)([!\?\.\,])" , "\\1" , $str );
  9. // Del teksten op i ord
  10. $tmp = preg_split ("/[[:mellemrum:]]+/is" , $str );
  11. // Array af "links"
  12. $ord =Array();
  13. // Udfyld linkene
  14. for($i = 0 ; $i< count ($tmp ); $i ++) {
  15. if ($tmp [ $i + 1 ]!= "" ) (
  16. $words [ $tmp [ $i ]]= $tmp [ $i + 1 ];
  17. $words = array_map("array_unique" , ​​​​$words );
  18. // Array af indledende ord i sætninger
  19. $start =Array();
  20. foreach($ord som $word => $links) (
  21. if (eg ("^[A-Z][a-Z]+" , $ord )) (
  22. $start = $ord ;
  23. // Generer 100 sætninger baseret på kildeteksten
  24. for ($i = 0; $i< 100 ; $i ++) {
  25. mens (sandt) (
  26. $w = $start [ rand (0 ,(tæller ($start )- 1 ))];
  27. if (ereg ("[\.!\?]$" , $w )) ( fortsæt; )
  28. $sætning = $w . " " ;
  29. // Antal ord i en sætning
  30. $cnt = 1 ;
  31. // Generer tilbud
  32. mens (sandt) (
  33. $links = $ord [ $w ];
  34. // Skift kæde
  35. $w = $ord [ $w ][ rand (0 ,(antal ($ord [ $w ])- 1 ))];
  36. $sætning .= $w . " " ;
  37. // Hvis ordet stod i slutningen af ​​sætningen
  38. if (eg ("[\.!\?]$" , $w )) ( brud; )
  39. $cnt++;
  40. // Hvis generatoren er i en løkke, så tving udgang
  41. if ($cnt > 19) ( break; )
  42. // En sætning med en længde på 5-20 ord anses for vellykket
  43. if ($cnt > 5 && $cnt< 20 ) { break; }
  44. // Genereret tilbud
  45. ekko $sætning ;

En lille forklaring på hvordan det hele fungerer. Først indlæses filen "markov.txt", den skal være i win-1251-kodning. Så fjernes alle tegn fra den, undtagen bogstaver og nogle tegnsætningstegn, og så skæres unødvendige mellemrum ud. Det viser sig klar tekst, som så opdeles i individuelle ord. Det er det, vi har individuelle led i kæden. Nu skal vi bestemme sammenhængene mellem ord, det vil sige hvilke ord der kan placeres bag hvilke. Dette er den mest ressourcekrævende proces, så du bliver nødt til at være tålmodig med store filer. Hvis generering er påkrævet ofte, så giver det sandsynligvis mening at gemme en række links og links i en eller anden database for at få hurtig adgang til det. Næste skridt- at identificere de ord, som sætninger begynder med. Jeg accepterede betingelsen om, at det første bogstav i sådanne ord skulle være stort, du kan gøre mere præcis definition. Tekstgenerering udføres i henhold til den ovenfor beskrevne algoritme, jeg har lige tilføjet flere kontroller mod looping til den.

Du kan se et fungerende eksempel på en tekstgenerator baseret på Markov-kæder og ovenstående script

Denne artikel giver generel idé om, hvordan man genererer tekster ved hjælp af Markov-procesmodellering. Vi vil især introducere Markov-kæder, og som praksis implementerer vi en lille tekstgenerator i Python.

Til at begynde med, lad os nedskrive de nødvendige, men endnu ikke meget klare, definitioner fra Wikipedia-siden for i det mindste nogenlunde at forstå, hvad vi har med at gøre:

Markov proces t t

Markov kæde

Hvad betyder alt dette? Lad os finde ud af det.

Grundlæggende

Det første eksempel er ekstremt simpelt. Ved hjælp af en sætning fra en børnebog, vil vi mestre grundlæggende koncept Markov kæder, og definerer også, hvad de er i vores sammenhæng krop, links, sandsynlighedsfordeling og histogrammer. Selvom forslaget er givet på engelsk sprog, vil essensen af ​​teorien være let at forstå.

Dette forslag er ramme, altså den base, som teksten vil blive genereret på baggrund af i fremtiden. Den består af otte ord, men på samme tid unikke ord kun fem er links(vi taler om Markovian kæder). For klarhedens skyld, lad os farve hvert link i sin egen farve:

Og vi skriver ned antallet af forekomster af hvert link i teksten:

På billedet ovenfor kan du se, at ordet "fisk" optræder i teksten 4 gange oftere end hvert af de andre ord ( "En", "to", "rød", "blå"). Det vil sige sandsynligheden for at støde på ordet i vores korpus "fisk" 4 gange højere end sandsynligheden for at støde på hvert andet ord vist i figuren. Når vi taler på matematikkens sprog, kan vi bestemme fordelingsloven for en stokastisk variabel og beregne med hvilken sandsynlighed et af ordene vil optræde i teksten efter det aktuelle. Sandsynligheden beregnes som følger: vi skal dividere antallet af forekomster af ordet, vi skal bruge i korpuset med samlet antal alle ordene i den. For ordet "fisk" denne sandsynlighed er 50 %, da den optræder 4 gange i en sætning på 8 ord. For hvert af de resterende links er denne sandsynlighed 12,5% (1/8).

Viser fordelingen grafisk tilfældige variable muligt at bruge histogrammer. I dette tilfælde er hyppigheden af ​​forekomsten af ​​hvert link i sætningen tydeligt synlig:

Så vores tekst består af ord og unikke links, og vi viste sandsynlighedsfordelingen for udseendet af hvert link i en sætning på et histogram. Hvis du synes, det ikke er værd at genere med statistik, så læs videre. Og måske vil det redde dit liv.

Essensen af ​​definitionen

Lad os nu tilføje elementer til vores tekst, der altid er underforstået, men som ikke er udtrykt i daglig tale - begyndelsen og slutningen af ​​sætningen:

Enhver sætning indeholder disse usynlige "begyndelse" og "slut"; lad os tilføje dem som links til vores distribution:

Lad os vende tilbage til definitionen givet i begyndelsen af ​​artiklen:

Markov proces - tilfældig proces, hvis udvikling efter evt indstillet værdi tidsparameter t afhænger ikke af den forudgående udvikling t, forudsat at værdien af ​​processen på dette tidspunkt er fast.

Markov kæde - særlig situation Markov-processen, når rummet af dens tilstande er diskret (dvs. ikke mere end tælleligt).

Så hvad betyder det? Groft sagt modellerer vi en proces, hvor systemets tilstand i det næste øjeblik kun afhænger af dets tilstand på nuværende tidspunkt og ikke på nogen måde afhænger af alle tidligere tilstande.

Forestil dig, hvad der er foran dig vindue, som kun viser systemets aktuelle tilstand (i vores tilfælde er det et ord), og du skal bestemme, hvad det næste ord kun vil være baseret på de data, der præsenteres i dette vindue. I vores korpus følger ord hinanden efter følgende mønster:

Således dannes ordpar (selv slutningen af ​​sætningen har sit eget par - en tom betydning):

Lad os gruppere disse par efter det første ord. Vi vil se, at hvert ord har sit eget sæt af links, som i sammenhæng med vores sætning kan Følg ham:

Lad os præsentere disse oplysninger på en anden måde - for hvert link tildeler vi en række af alle ord, der kan forekomme i teksten efter dette link:

Lad os se nærmere. Vi ser, at hvert link har ord, der kan komme efter det i en sætning. Hvis vi skulle vise diagrammet ovenfor til en anden, kunne denne person med en vis sandsynlighed rekonstruere vores første tilbud, altså kroppen.

Eksempel. Lad os starte med ordet "Start". Vælg derefter ordet "En", da dette ifølge vores skema er det eneste ord, der kan følge begyndelsen af ​​en sætning. Bag Ordet "En" også kun ét ord kan følge - "fisk". Nu ser det nye forslag i mellemversionen ud "En fisk". Yderligere bliver situationen mere kompliceret - for "fisk" der kan være ord med lige stor sandsynlighed på 25 % "to", "rød", "blå" og slutningen af ​​sætningen "Ende". Hvis vi antager, at det næste ord er "to", vil genopbygningen fortsætte. Men vi kan vælge et link "Ende". I dette tilfælde, baseret på vores skema, vil en sætning blive tilfældigt genereret, der er meget forskellig fra korpuset - "En fisk".

Vi har lige simuleret Markov proces- identificerede hvert næste ord kun på grundlag af viden om det nuværende. For fuldt ud at forstå materialet, lad os bygge diagrammer, der viser afhængighederne mellem elementerne inde i vores korpus. Ovalerne repræsenterer links. Pilene fører til potentielle links, der kan følge ordet i ovalen. Ved siden af ​​hver pil er sandsynligheden for, at det næste link vises efter det nuværende:

Store! Vi har lært nødvendige oplysninger at komme videre og analysere mere komplekse modeller.

Udvidelse af ordforrådet

I denne del af artiklen vil vi bygge en model efter samme princip som før, men i beskrivelsen vil vi udelade nogle trin. Hvis du har problemer, så vend tilbage til teorien i første blok.

Lad os tage yderligere fire citater fra den samme forfatter (også på engelsk, det skader os ikke):

"I dag du er du. Det er mere sandt end sandt. Der er ingen i live, der er dig-er end dig."

« Du har hjerner i dit hoved. Du har fødderne i skoene. Du kan styre dig selv i enhver retning, du vælger. Du er alene."

"Jo mere du læser, jo flere ting vil du vide." Jo mere du lærer, jo flere steder vil du gå."

"Tænk til venstre og tænke ret og tænk lavt og tænk højt. Åh, tænker du kan tænk, hvis bare du prøver."

Korpusets kompleksitet er steget, men i vores tilfælde er dette kun et plus - nu vil tekstgeneratoren kunne producere mere meningsfulde sætninger. Faktum er, at der i ethvert sprog er ord, der optræder i tale oftere end andre (for eksempel bruger vi præpositionen "i" meget oftere end ordet "kryogen"). Hvordan flere ord i vores korpus (og derfor afhængighederne mellem dem), jo mere information har generatoren om, hvilket ord der med størst sandsynlighed vil optræde i teksten efter det aktuelle.

Den nemmeste måde at forklare dette på er fra et programsynspunkt. Vi ved, at der for hvert link er et sæt ord, der kan følge det. Og også, hvert ord er karakteriseret ved antallet af dets optrædener i teksten. Vi har brug for en måde at fange al denne information på ét sted; Til dette formål er en ordbog med "(nøgle, værdi)"-par bedst egnet. Ordbogsnøglen registrerer systemets aktuelle tilstand, det vil sige et af kroppens links (f.eks. "det" på billedet nedenfor); og en anden ordbog vil blive gemt i ordbogsværdien. I den indlejrede ordbog vil nøglerne være ord, der kan optræde i teksten efter det aktuelle link i korpuset ( "tænker" Og "mere" kan gå efter i teksten "det"), og værdierne er antallet af forekomster af disse ord i teksten efter vores link (ordet "tænker" vises i teksten efter ordet "det" 1 gang, ord "mere" efter ordet "det"- 4 gange):

Læs afsnittet ovenfor flere gange for at sikre, at du forstår det nøjagtigt. Bemærk venligst, at den indlejrede ordbog i dette tilfælde er det samme histogram; det hjælper os med at spore links og hyppigheden af ​​deres optræden i teksten i forhold til andre ord. Det skal bemærkes, at selv en sådan ordforrådsbase er meget lille til korrekt generering af tekster i naturligt sprog- det burde indeholde mere end 20.000 ord, eller endnu bedre, mere end 100.000. Og endnu bedre, mere end 500.000. Men lad os se på den ordforrådsbase, vi har.

Markov-kæden i dette tilfælde er konstrueret på samme måde som det første eksempel - hvert næste ord vælges kun på grundlag af viden om det aktuelle ord, alle andre ord tages ikke i betragtning. Men takket være lagringen i ordbogen af ​​data om, hvilke ord der optræder oftere end andre, kan vi acceptere, når vi vælger informeret beslutning. Lad os se på et specifikt eksempel:

Mere:

Altså hvis det nuværende ord er ordet "mere", efter det kan der være ord med lige stor sandsynlighed på 25 % "ting" Og "steder", og med en sandsynlighed på 50% - ordet "at". Men sandsynligheden kan alle være lige store:

Tænke:

Arbejde med Windows

Indtil nu har vi kun betragtet vinduer på størrelse med ét ord. Du kan øge størrelsen af ​​vinduet, så tekstgeneratoren producerer flere "verificerede" sætninger. Det betyder, at jo større vinduet er, jo mindre er afvigelserne fra kroppen under genereringen. Forøgelse af vinduesstørrelsen svarer til overgangen af ​​Markov-kæden til mere høj orden. Tidligere byggede vi et førsteordens kredsløb; for et vindue vil to ord producere et andenordens kredsløb, tre vil producere et tredjeordens kredsløb og så videre.

Vindue- dette er dataene i nuværende tilstand systemer, der bruges til at træffe beslutninger. Hvis vi matcher stort vindue og et lille sæt data, så vil vi højst sandsynligt modtage den samme sætning hver gang. Lad os tage ordforrådet fra vores første eksempel og udvide vinduet til størrelse 2:

Udvidelsen har betydet, at hvert vindue nu kun har én mulighed for den næste tilstand af systemet – uanset hvad vi gør, vil vi altid modtage den samme dom, identisk med vores sag. For at eksperimentere med vinduer, og for at tekstgeneratoren skal returnere unikt indhold, skal du derfor fylde op ordforrådsgrundlag fra 500.000 ord.

Implementering i Python

Diktogram datastruktur

Et diktogram (dict er en indbygget ordbogsdatatype i Python) vil vise forholdet mellem links og deres hyppighed af forekomst i teksten, det vil sige deres distribution. Men samtidig vil det have den ordbogsegenskab, vi har brug for - programmets udførelsestid vil ikke afhænge af mængden af ​​inputdata, hvilket betyder, at vi skaber en effektiv algoritme.

Importer tilfældig klasse Dictogram(dict): def __init__(self, iterable=Ingen): # Initialiser vores distribution som nyt objekt klasse, # tilføj eksisterende elementer super(Dictogram, self).__init__() self.types = 0 # antal unikke nøgler i distributionen self.tokens = 0 # totalt antal af alle ord i distributionen, hvis det kan itereres: self.update( iterable) def update (self, iterable): # Opdater distributionen med elementer fra det eksisterende # iterable datasæt for element i iterable: if item in self: self += 1 self.tokens += 1 else: self = 1 self. typer += 1 self.tokens += 1 def count(self, item): # Returner varens tællerværdi, eller 0 hvis varen i self: return self return 0 def return_random_word(self): random_key = random.sample(self, 1) # En anden måde: # random .choice(histogram.keys()) return random_key def return_weighted_random_word(self): # Generer et pseudo-tilfældigt tal mellem 0 og (n-1), # hvor n er det samlede antal ord random_int = random.randint(0, self.tokens-1 ) index = 0 list_of_keys = self.keys() # print "random index:", random_int for i in range(0, self.types): index += self] # print index if(index > random_int): # print list_of_keys [i] returner list_of_keys[i]

Konstruktøren af ​​diktogramstrukturen kan passeres ethvert objekt, der kan itereres over. Elementerne i dette objekt vil være ordene til at initialisere diktogrammet, for eksempel alle ordene fra en bog. I dette tilfælde tæller vi elementerne, så vi ikke behøver at gennemgå hele datasættet hver gang for at få adgang til nogen af ​​dem.

Vi lavede også to funktioner til at returnere et tilfældigt ord. Den ene funktion vælger en tilfældig nøgle i ordbogen, og den anden, under hensyntagen til antallet af forekomster af hvert ord i teksten, returnerer det ord, vi har brug for.

Markov kæde struktur

fra histogrammer import Dictogram def make_markov_model(data): markov_model = dict() for i in range(0, len(data)-1): if data[i] in markov_model: # Bare føje til en eksisterende distribution markov_model].update( ]) andet: markov_model] = Dictogram() returnerer markov_model

I implementeringen ovenfor har vi en ordbog, der gemmer vinduer som en nøgle i et "(nøgle, værdi)"-par og distributioner som værdier i det par.

N. ordens Markov kædestruktur

fra histogrammer import Dictogram def make_higher_order_markov_model(order, data): markov_model = dict() for i in range(0, len(data)-order): # Opret et vinduesvindue = tuple(data) # Tilføj til ordbogen hvis vindue i markov_model: # Vedhæft til en eksisterende distribution markov_model.update() else: markov_model = Dictogram() returner markov_model

Meget lig en første ordre Markov-kæde, men i dette tilfælde opbevarer vi kortege som en nøgle i et "(nøgle, værdi)"-par i en ordbog. Vi bruger det i stedet for en liste, da tuplet er beskyttet mod eventuelle ændringer, og det er vigtigt for os - når alt kommer til alt, bør nøglerne i ordbogen ikke ændre sig.

Modelparsing

Super, vi har implementeret ordbogen. Men hvordan kan vi nu generere indhold baseret på den nuværende tilstand og skridtet til den næste tilstand? Lad os gennemgå vores model:

Fra histogrammer import Diktogram import tilfældig fra samlinger import deque import re def gener_random_start(model): # For at generere et hvilket som helst frø, fjern kommenterer linjen: # return random.choice(model.keys()) # For at generere den "korrekte" seed, brug kode nedenfor: # Korrekt indledende ord- det er dem, der var begyndelsen af ​​sætninger i korpuset, hvis "END" i model: seed_word = "END" mens seed_word == "END": seed_word = model["END"].return_weighted_random_word() returner frø_ord returnerer tilfældigt. choice(model .keys()) def generere_tilfældig_sætning(længde, markov_model): nuværende_ord = generere_tilfældig_start(markov_model) sætning = for i i området(0, længde): current_dictogram = markov_model random_weighted_word = current_dictogram.return_weighted_random_word() sætning_tilfældig_vægtet (current_word) sætning = sætning.capitalize() returnerer " ".join(sætning) + "." tilbagevenden sætning

Hvad er det næste?

Prøv at tænke på, hvor du selv kan bruge en tekstgenerator baseret på Markov-kæder. Bare glem ikke, at det vigtigste er, hvordan du analyserer modellen, og hvilke særlige begrænsninger du sætter for generation. Forfatteren af ​​denne artikel brugte f.eks., da han oprettede tweet-generatoren, et stort vindue, begrænsede det genererede indhold til 140 tegn og brugte kun "korrekte" ord til at begynde sætninger, det vil sige dem, der var begyndelsen af ​​sætninger i korpuset.