Markovi kett on lihtne: vaatame põhimõtet üksikasjalikult. Sõnavarabaasi laiendamine

Sirvisin foorumeid ja otsisin küsimusi, mida Pythoni programmeerijatele intervjuude ajal esitatakse, ja leidsin ühe väga imelise küsimuse. Tsiteerin teda vabalt: "Nad palusid mul kirjutada n-nda järgu Markovi keti põhjal jamade generaator." "Aga mul pole veel sellist generaatorit!" - hüüdis mu sisemine hääl- "Kiirustage, avage ülev ja kirjutage!" - jätkas ta visalt. No ma pidin kuuletuma.

Ja siin ma räägin teile, kuidas ma seda tegin.

Kohe otsustati, et generaator avaldab kõik oma mõtted Twitteris ja oma veebisaidil. Peamisteks tehnoloogiateks valisin Flask ja PostgreSQL. Nad suhtlevad üksteisega SQLAlchemy kaudu.

Struktuur.

Niisiis. Järgmisel viisil mudelid näevad välja sellised:
class Srt(db.Mudel): id = db.Veerg(db.Täisarv, esmane_võti = Tõene) sõnade_hulk = db.Veerg(db.Tekst())_sõnade_loend = db.Veerg(db.Tekst()) klass Ülemised sõnad(db) .Model): sõna = db.Veerg(db.String(40), indeks = Tõene, esmane_võti = Tõene, unikaalne = Tõene) def __repr__(self): return self.word class Fraasid(db.Model): id = db .Veerg(db.Täisarv, esmane_võti = Tõene) loodud = db.Veerg(db.Kuupäev, kellaaeg, vaikimisi=kuupäevaeg.kuupäevaaeg.now) fraas = db.Column(db.String(140), indeks = tõene) def __repr__(self ): return str(self.frase)
Lähtetekstideks otsustati võtta populaarsete telesarjade subtiitrid. Srt-klass salvestab ühe episoodi töödeldud subtiitrite kõigi sõnade järjestatud komplekti ja nende samade sõnade kordumatu komplekti (ilma kordusteta). See hõlbustab robotil konkreetsete subtiitritega fraasi otsimist. Esmalt kontrollib see, kas sõnade komplekt sisaldub subtiitrite sõnade hulgas ja seejärel vaatab, kas need on seal õiges järjekorras.

Fraasi esimene sõna tekstist on juhuslik sõna, mis algab tähega suured tähed. Selliste sõnade salvestamiseks kasutatakse UpperWords. Sinna kirjutatakse sõnad samamoodi ilma kordamiseta.

Noh, fraaside klassi on vaja juba loodud säutsude salvestamiseks.
Struktuur on meeleheitlikult lihtne.

Srt-vormingu subtiitrite parser sisaldub eraldi moodulis add_srt.py. Midagi erakordset seal pole, aga kui kedagi huvitab, siis kõik allikad on GitHubis.

Generaator.

Esiteks peate valima oma säutsu jaoks esimese sõna. Nagu varem mainitud, on see mis tahes sõna UpperWordsi mudelist. Selle valik rakendatakse funktsioonis:
def add_word(word_list, n): kui mitte sõnaloend: sõna = 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
Selle sõna valikut rakendab otse rida:

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

Kui kasutate MySQL-i, peate func.random() asemel kasutama funktsiooni func.rand(). See on ainus erinevus selles teostuses, kõik muu töötab täiesti identselt.

Kui esimene sõna on juba olemas, vaatab funktsioon ahela pikkust ja valib sellest sõltuvalt, millise arvu sõnadega tekstis peame oma nimekirja (n-ndat järku ahel) võrdlema ja saama järgmine sõna.

Ja järgmise sõna saame funktsioonis get_word:
def get_word(sõnaloend, n): päringud = mudelid.Srt.query.all() query_list = list() päringu jaoks päringutes: if set(sõnaloend)<= 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
Kõigepealt jookseb skript läbi kõik laetud subtiitrid ja kontrollib, kas meie sõnade komplekt on kindlate subtiitrite sõnade komplektis. Seejärel kombineeritakse filtreeritud subtiitrite tekstid üheks loendiks ja otsitakse tervete fraaside vasteid ning tagastatakse nendele fraasidele järgnevate sõnade asukohad. Kõik lõpeb pimeda sõnavalikuga. Kõik on nagu elus.
Nii lisatakse loendisse sõnad. Säuts ise on kokku pandud funktsiooniks:
def get_twit(): word_list = list() n = N while len(" ".join(word_list))<140: if not add_word(word_list, n): break if len(" ".join(word_list))>140: word_list.pop() katkeb, samas kui word_list[-1][-1] ei ole ".?!!": word_list.pop() return " ".join(word_list)
See on väga lihtne – säuts ei tohi ületada 140 tähemärki ja lõppeda lauselõpulise kirjavahemärgiga. Kõik. Generaator on oma töö teinud.

Kuva saidil.

Moodul views.py haldab kuvamist saidil.
@app.route("/") def index(): return render_template("main/index.html")
See kuvab lihtsalt malli. Kõik säutsud tõmmatakse sellest js-i abil.
@app.route("/page") def page(): page = int(request.args.get("page")) diff = int(request.args.get("erinevus")) limiit = 20 fraasi = models.Phrases.query.order_by(-models.Phrases.id).all() pages = math.ceil(len(fraasid)/float(limit)) count = len(fraasid) fraasid = fraasid return json.dumps(( "fraasid":fraasid, "lehed":lehed, "count":count), cls=controllers.AlchemyEncoder)
Tagastab säutsud konkreetselt lehelt. See on vajalik lõpmatuks kerimiseks. Kõik on üsna tavaline. diff – säutsude arv, mis on lisatud pärast saidi laadimist värskendamise ajal. Lehe säutsude näidis tuleks selle summa võrra nihutada.

Ja värskendus ise:
@app.route("/update") def update(): last_count = int(request.args.get("count")) fraasid = models.Phrases.query.order_by(-models.Phrases.id).all( ) count = len(fraasid) if count > last_count: fraasid = fraasid[:loend-viimane_loendus] return json.dumps(("fraasid":fraasid, "count":count), cls=controllers.AlchemyEncoder) else: return json .dumps(("count":count))
Kliendi poolel helistatakse sellele iga n sekundi järel ja see laadib reaalajas värskelt lisatud säutsu. Nii töötab meie säutsukuva. (Keda huvitab, võib controllers.py-st vaadata AlchemyEncoder klassi, seda kasutatakse SQLAlchemyst saadud säutsude serialiseerimiseks)

Säutsude lisamine andmebaasi ja Twitterisse postitamine.

Ma kasutasin Twitterisse postitamiseks tweepyt. Väga mugav aku, käivitub kohe.

Milline see välja näeb:
def twit(): fraas = get_twit() twited = mudelid.Fraasid(fraas=fraas) db.session.add(twited) db.session.commit() auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)ENAC_CESSTOenKENACces_CESSTOen , ACCESS_TOKEN_SECRET) api = tweepy.API(auth) api.update_status(status=fraas)
Panin selle funktsiooni kutse projekti juure faili cron.py ja nagu võite arvata, käivitab selle cron. Iga poole tunni tagant lisatakse andmebaasi ja Twitterisse uus säuts.


Kõik toimis!

Lõpuks.

Hetkel olen alla laadinud kõik sarjade “Sõbrad” ja “Suure paugu teooria” subtiitrid. Siiani olen valinud Markovi ahela astme võrdseks kahega (subtiitrite baasi kasvades aste suureneb). Näete, kuidas see sees töötab

Kirjeldati, kuidas treenida neutronite võrku nii, et see mängiks Mariot või juhiks robotit. Kuid kas närvivõrk suudab teksti genereerida? Markovi ketid võivad selles aidata.

Seetõttu "armastan" venekeelset Vikipeediat, sest igasugust lihtsat nähtust/võrrandit/reeglit, eriti matemaatikast, kirjeldatakse kohe nii üldsõnaliselt, nii mõistusevastaste valemitega, et ilma selleta ei saagi aru. pool liitrit. Pealegi ei vaevu artiklite autorid lihtsas inimkeeles kirjeldust (vähemalt paar lauset) andma, vaid liiguvad kohe valemite juurde.

Kui keegi tahab teada, mis on Markovi ketid, siis esimeses tõlkes saab ta teada, et:
"Markovi kett on juhuslike sündmuste jada, millel on piiratud või loendatav arv tagajärgi, mida iseloomustab omadus, et tulevik on fikseeritud olevikuga minevikust sõltumatu, mis on nimetatud A. A. Markovi (vanem) auks. .”

Ja seda hoolimata asjaolust, et Markovi kettide põhiidee on väga lihtne, kuid ilma matemaatilise hariduseta on seda Wikipediast lihtsalt võimatu mõista.

Markovi ahelad on vaid süsteemi ühest olekust teise ülemineku tõenäosuse kirjeldus. Kõiki olekuid saab kirjeldada graafiku tippudega. Näiteks võivad sellised tipud olla inimese asendid: [lamamine], [istumine], [seismine], [kõndimine]

Siin on näha, et graafik on suunatud, mis tähendab, et igast olekust teise ei ole võimalik jõuda. Näiteks kui oled pikali, siis on võimatu kohe kõndida. Kõigepealt tuleb istuda, siis püsti tõusta ja alles siis kõndida. Kuid võite kukkuda ja lõpuks pikali jääda igast asendist))
Igal ühendusel on teatud tõenäosus. Nii on näiteks seisvast asendist kukkumise tõenäosus väga väike; Kõigi tõenäosuste summa on 1.

Markovi ketid võimaldavad muu hulgas genereerida sündmusi. Nii või teisiti on enamik tekstigeneraatoreid üles ehitatud Markovi ahelatele.

Proovime kirjutada pirukate generaatori.

Pirukad

Pirukad on neljavärsked ilma riimi, kirjavahemärkide, numbrite või suurtähtedeta. Silpide arv peaks olema 9-8-9-8.


Enamik tekstigeneraatoreid kasutab morfoloogilisi analüsaatoreid. Aga me teeme selle lihtsamaks. Jagame lihtsalt sõnad silpideks ja arvutame välja tõenäosuse, et üks silp tuleb teise silbi järel. See tähendab, et graafiku sõlmedeks on silbid, servad ja nende kaalud - esimesele järgneva teise silbi sagedus.
Järgmisena toidame programmi viiskümmend pirukat.

Näiteks pärast silpi “at” võivad olla järgmised silbid (ääred ja nende kaalud):
"chem" (1) "ho" (4) "mina" (1) "du" (2) "chi" (4) "yatel" (4) "läks" (5) "ku" (1) " " (9) "su"(1) "vych"(3) "mi"(1) "kos"(1) "ob"(1) "det"(2) "sõitis"(1) "uchi"(1) ) "mu"(1) "bi"(1) "tse"(1) "int"(2) "tom"(1) "ko"(1) "võll"(1) "nes"(1) " det"(1) "aga"(1) "vez"(1) "meth"(1) "veterinaar"(1) "dia"(1) "teie"(1)

Nüüd pole vaja teha muud, kui võtta juhuslik silp (näiteks "at"). Kõigi pärast seda tulevate silpide summa on 58. Nüüd peate võtma järgmise silbi, võttes arvesse nende silpide sagedust (arvu):

suurus_t nth = rand() % count;

suurus_kõik = 0 ;

jaoks (const auto &n: next) (

Kõik += n.count;

kui (kõik >= n)

tagasi n.sõna;

Seega genereerime read nii, et esimesel real on 9 silpi, teisel - 8, seejärel 9 ja 8, saame:

Kunagi oli nali äratuskella üle
ta rööviti selle ajal
teie ülemus on siin jah
Onegini efektiga diivan

Siiani ei tundu see eriti sidusa tekstina. Sageli kohtab olematuid sõnu (“poku”). Nüüd on võtmena ainult üks silp. Kuid ühe silbi põhjal lauset on raske koostada. Suurendame silpide arvu, mille alusel genereerime järgmise silbi, vähemalt 3-ni:

Asfalti mõistusele piisavalt
kell on seitse jagatud
laud on välja võetud, must kast on
ta kasvas üles, maksis kätte, leidis
Siin on esimene pirukas, mida võib enam-vähem ekslikult pidada inimese kirjutatud.
Teksti sisukamaks muutmiseks peate kasutama morfoloogilisi analüsaatoreid ja siis pole sõlmedeks silbid, vaid sõnade metakirjeldused (näiteks "verb, mitmuses, minevikuvorm").

Sellised programmid võimaldavad teil juba "sisukamat" teksti kirjutada. Näiteks rooter on teadusteksti generaatori kirjutatud artikkel, mis on läbi vaadatud ja isegi teadusajakirjas avaldatud.

See artikkel annab üldine idee selle kohta, kuidas Markovi protsessi modelleerimist kasutades tekste genereerida. Eelkõige tutvustame Markovi ahelaid ja praktikana rakendame Pythonis väikese tekstigeneraatori.

Alustuseks kirjutame Vikipeedia lehelt üles vajalikud, kuid veel mitte väga selged määratlused, et vähemalt ligikaudselt mõista, millega tegu:

Markovi protsess t t

Markovi kett

Mida see kõik tähendab? Selgitame välja.

Põhitõed

Esimene näide on äärmiselt lihtne. Kasutades lauset lasteraamatust, me meisterdame põhikontseptsioon Markovi ahelaid ja määratleda ka, mis need meie kontekstis on keha, lingid, tõenäosusjaotus ja histogrammid. Kuigi ettepanek antakse edasi inglise keel, on teooria olemust lihtne mõista.

See ettepanek on raami, ehk siis alus, mille alusel edaspidi tekst genereeritakse. See koosneb kaheksast sõnast, kuid seal on ainult viis ainulaadset sõna - see on lingid(me räägime Markovianist ketid). Selguse huvides värvime iga lingi oma värviga:

Ja kirjutame tekstis üles iga lingi ilmumiste arvu:

Ülaltoodud pildil näete seda sõna "kala" esineb tekstis 4 korda sagedamini kui iga teine ​​sõna ( "Üks", "kaks", "punane", "sinine"). See tähendab tõenäosust kohata sõna meie korpuses "kala" 4 korda suurem kui joonisel näidatud iga teise sõna kohtumise tõenäosus. Rääkides matemaatika keeles, saame määrata juhusliku suuruse jaotusseaduse ja arvutada, millise tõenäosusega ilmub üks sõnadest tekstis pärast praegust. Tõenäosus arvutatakse järgmiselt: peame jagama meile vajaliku sõna esinemiste arvu korpuses koguarv kõik sõnad selles. Sõna pärast "kala" see tõenäosus on 50%, kuna see esineb 4 korda 8-sõnalises lauses. Iga ülejäänud lingi puhul on see tõenäosus 12,5% (1/8).

Esitage jaotust graafiliselt juhuslikud muutujad võimalik kasutada histogrammid. IN sel juhul, on lause iga lingi esinemissagedus selgelt nähtav:

Niisiis koosneb meie tekst sõnadest ja ainulaadsetest linkidest ning me kuvasime histogrammil iga lingi ilmumise tõenäosusjaotuse lauses. Kui arvad, et statistikaga ei tasu vaeva näha, siis loe edasi. Ja võib-olla päästab see teie elu.

Määratluse olemus

Nüüd lisame oma tekstile elemendid, mis on alati vihjatud, kuid mida igapäevases kõnes ei esitata - lause algus ja lõpp:

Iga lause sisaldab neid nähtamatuid "algusi" ja "lõppusid" lisame need meie distributsiooni linkidena:

Tuleme tagasi artikli alguses antud määratluse juurde:

Markovi protsess - juhuslik protsess, mille evolutsioon pärast mis tahes seatud väärtus aja parameeter t ei sõltu eelnevast arengust t, eeldusel, et protsessi väärtus sel hetkel on fikseeritud.

Markovi kett - erijuhtum Markovi protsess, kui selle olekute ruum on diskreetne (st mitte rohkem kui loendatav).

Mida see siis tähendab? Jämedalt öeldes modelleerime protsessi, kus süsteemi olek järgmisel ajahetkel sõltub ainult selle olekust Sel hetkel, ja ei sõltu mingil viisil kõigist eelnevatest olekutest.

Kujutage ette, mis on teie ees aken, mis kuvab ainult süsteemi hetkeseisu (meie puhul on see üks sõna) ja peate määrama, milline on järgmine sõna, ainult selles aknas esitatud andmete põhjal. Meie korpuses järgivad sõnad üksteist järgmise mustri järgi:

Seega moodustuvad sõnapaarid (isegi lause lõpus on oma paar - tühi tähendus):

Rühmitame need paarid esimese sõna järgi. Näeme, et igal sõnal on oma lingid, mis meie lause kontekstis saab järgne talle:

Esitame selle teabe muul viisil - iga lingi jaoks määrame massiivi kõigist sõnadest, mis võivad pärast seda linki tekstis ilmuda:

Vaatame lähemalt. Näeme, et igal lingil on sõnad, mis saab tulge sellele lausega järele. Kui peaksime ülaltoodud diagrammi kellelegi teisele näitama, saaks see inimene suure tõenäosusega meie rekonstrueerida esialgne pakkumine, see tähendab keha.

Näide. Alustame sõnast "Alusta". Järgmisena valige sõna "Üks", kuna meie skeemi järgi on see ainus sõna, mis võib järgneda lause algusele. Sõna taga "Üks" samuti võib järgneda ainult üks sõna - "kala". Nüüd näeb uus ettepanek vahepealses versioonis välja selline "Üks kala". Edasi muutub olukord keerulisemaks - jaoks "kala" võib olla sõnu võrdse tõenäosusega 25% "kaks", "punane", "sinine" ja lause lõpp "Lõpp". Kui eeldame, et järgmine sõna on "kaks", rekonstrueerimine jätkub. Kuid me saame valida lingi "Lõpp". Sel juhul genereeritakse meie skeemi alusel juhuslikult lause, mis on korpusest väga erinev - "Üks kala".

Me lihtsalt simuleerisime Markovi protsess- tuvastas iga järgmise sõna ainult teadmiste põhjal praeguse sõna kohta. Materjali täielikuks mõistmiseks koostame diagrammid, mis näitavad meie korpuses olevate elementide vahelisi sõltuvusi. Ovaalid tähistavad linke. Nooled viivad potentsiaalsete linkideni, mis võivad ovaalis olevale sõnale järgneda. Iga noole kõrval on tõenäosus, millega järgmine link praeguse lingi järel ilmub:

Suurepärane! Oleme õppinud vajalikku teavet liikuda edasi ja analüüsida keerukamaid mudeleid.

Sõnavarabaasi laiendamine

Artikli selles osas koostame mudeli sama põhimõtte järgi nagu varem, kuid kirjelduses jätame mõned sammud välja. Kui teil on raskusi, pöörduge tagasi esimese ploki teooria juurde.

Võtame veel neli tsitaati samalt autorilt (ka inglise keeles, see ei tee meile haiget):

"Täna sina Kas sa oled. See on tõest tõesem. Pole elus kedagi, kes oleks sina - kui sina.

« Sul on ajud peas. Sul on jalad kingades. Saate end juhtida mis tahes valitud suunas. Sa oled omaette."

"Mida rohkem loed, seda rohkem asju saate teada." Mida rohkem õpid, seda rohkematesse kohtadesse sa lähed.

"Mõtle vasakule ja mõtleõige ja mõtle madalalt ja mõtle kõrgelt. Oh, mõtteid sa saad mõelge järele, kui ainult proovite."

Korpuse keerukus on suurenenud, kuid meie puhul on see ainult pluss - nüüd saab tekstigeneraator toota sisukamaid lauseid. Fakt on see, et igas keeles on sõnu, mis esinevad kõnes sagedamini kui teised (näiteks kasutame eessõna "in" palju sagedamini kui sõna "krüogeenne"). Kuidas rohkem sõnu meie korpuses (ja seega ka nendevahelistes sõltuvustes), seda rohkem on generaatoril infot selle kohta, milline sõna esineb tekstis kõige tõenäolisemalt pärast praegust sõna.

Lihtsaim viis seda selgitada programmi vaatenurgast. Teame, et iga lingi jaoks on sõnad, mis võivad sellele järgneda. Ja ka iga sõna iseloomustab selle esinemiste arv tekstis. Meil on vaja mingit viisi kogu selle teabe ühte kohta kogumiseks; Selleks sobib kõige paremini sõnaraamat, mis salvestab paare “(võti, väärtus)”. Sõnastikuvõti salvestab süsteemi hetkeseisu, st ühe keha lingi (näiteks "te" alloleval pildil); ja sõnastiku väärtusesse salvestatakse teine ​​sõnastik. Pesastatud sõnastikus on võtmeteks sõnad, mis võivad esineda tekstis pärast korpuse aktiivset linki ( "mõtleb" Ja "rohkem" võib tekstis järele minna "te") ja väärtused on nende sõnade esinemiste arv tekstis pärast meie linki (sõna "mõtleb" ilmub tekstis sõna järel "te" 1 kord, sõna "rohkem" pärast sõna "te"- 4 korda):

Lugege ülaltoodud lõik mitu korda uuesti läbi, et veenduda, et saate sellest täpselt aru. Pange tähele, et pesastatud sõnastik on sel juhul sama histogramm, mis aitab meil jälgida linke ja nende esinemise sagedust tekstis võrreldes teiste sõnadega. Tuleb märkida, et isegi selline sõnavarabaas on tekstide õigeks genereerimiseks väga väike loomulik keel- see peaks sisaldama rohkem kui 20 000 sõna või veel parem, rohkem kui 500 000. Aga vaatame seda sõnavara baas, mille saime.

Markovi ahel on sel juhul konstrueeritud sarnaselt esimesele näitele - iga järgmine sõna valitakse ainult praeguse sõna teadmiste põhjal, kõiki teisi sõnu ei võeta arvesse. Kuid tänu sellele, et sõnaraamatus on salvestatud andmed selle kohta, millised sõnad ilmuvad teistest sagedamini, saame valimisel nõustuda teadlik otsus. Vaatame konkreetset näidet:

Veel:

See tähendab, kui praegune sõna on sõna "rohkem", pärast seda võivad olla sõnad võrdse tõenäosusega 25% "asjad" Ja "kohad", ja 50% tõenäosusega - sõna "see". Kuid tõenäosused võivad kõik olla võrdsed:

Mõtle:

Windowsiga töötamine

Siiani oleme akendega arvestanud vaid ühe sõna suurust. Saate akna suurust suurendada, nii et tekstigeneraator toodab rohkem "kontrollitud" lauseid. See tähendab, et mida suurem on aken, seda väiksemad on genereerimisel kerest kõrvalekalded. Akna suuruse suurendamine vastab Markovi ahela üleminekule rohkemale kõrge järjekord. Varem ehitasime akna jaoks esimest järku vooluringi, kaks sõna tekitavad teist järku ahela, kolm kolmandat järku vooluringi ja nii edasi.

Aken- need on andmed praegune olek süsteemid, mida kasutatakse otsuste tegemiseks. Kui sobime suur aken ja väike kogum andmeid, siis suure tõenäosusega saame iga kord sama lause. Võtame sõnavarabaasi meie esimesest näitest ja laiendame akent suurusele 2:

Laiendus on tähendanud, et igal aknal on nüüd ainult üks võimalus süsteemi järgmise oleku jaoks – ükskõik, mida me teeme, saame alati sama lause, mis on identne meie juhtumiga. Seetõttu varuge akendega katsetamiseks ja tekstigeneraatori ainulaadse sisu tagastamiseks sõnavarabaasi vähemalt 500 000 sõna.

Rakendamine Pythonis

Diktogrammi andmestruktuur

Diktogramm (dict on Pythonis sisseehitatud sõnastiku andmetüüp) kuvab linkide vahelise seose ja nende esinemissageduse tekstis, st nende leviku. Kuid samal ajal on sellel sõnastiku omadus, mida me vajame – programmi täitmisaeg ei sõltu sisendandmete hulgast, mis tähendab, et loome tõhusa algoritmi.

Impordi juhuslik klass Diktogramm(dict): def __init__(self, iterable=None): # Initsialiseeri meie distributsioon uus objekt klass, # lisa olemasolevad elemendid super(diktogramm, ise).__init__() self.types = 0 # unikaalsete võtmete arv distributsioonis self.tokens = 0 # kõigi jaotuse sõnade koguarv, kui itereeritav: self.update( itereeritav) def update (self, iterable): # Värskenda jaotust olemasoleva # itereeritava andmekogumi elementidega iterable: if item in self: self += 1 self.tokens += 1 else: self = 1 self. tüübid += 1 self.tokens += 1 def count(self, item): # Tagastab üksuse loenduri väärtuse või 0, kui üksus ise: return self return 0 def return_random_word(self): random_key = random.sample(self, 1) # Teine võimalus: # juhuslik .valik(histogramm.võtmed()) return random_key def return_weighted_random_word(self): # Genereeri pseudojuhuslik arv vahemikus 0 kuni (n-1), # kus n on sõnade koguarv random_int = random.randint(0, self.tokens-1 ) index = 0 list_of_keys = self.keys() # print "juhuslik indeks:", random_int for i in range(0, self.types): index += ise] # prindi indeks if(indeks > juhuslik_int): # prindi_võtmete_loend [i] return_of_keys[i]

Diktogrammi struktuuri konstruktorit saab edasi anda mis tahes objektist, mida saab itereerida. Selle objekti elementideks on sõnad diktogrammi lähtestamiseks, näiteks kõik sõnad raamatust. Sel juhul loendame elemente nii, et ühelegi neist juurde pääsemiseks ei pea me iga kord kogu andmestikku läbima.

Samuti tegime juhusliku sõna tagastamiseks kaks funktsiooni. Üks funktsioon valib sõnastikus juhusliku võtme ja teine, võttes arvesse iga sõna esinemiste arvu tekstis, tagastab meile vajaliku sõna.

Markovi ahela struktuur

histogrammidest import Diktogramm def make_markov_model(data): markov_model = dict() for i in range(0, len(data)-1): if data[i] in markov_model: # Lihtsalt lisa olemasolevale distributsioonile markov_model].update( ]) else: markovi_mudel] = Diktogramm() tagastab markovi_mudel

Ülaltoodud teostuses on meil sõnastik, mis salvestab aknad võtmena paaris „(võti, väärtus)” ja jaotused selle paari väärtustena.

N-ndat järku Markovi ahela struktuur

histogrammidest import Diktogramm def make_higher_order_markov_model(order, data): markov_model = dict() for i in range(0, len(data)-order): # Loo akna aken = korteež(andmed) # Lisa sõnaraamatusse if window in markov_model: # Lisa olemasolevale distributsioonile markov_model.update() else: markov_model = Dictogram() return markov_model

Väga sarnane esimese järjekorra Markovi ketiga, kuid sel juhul laos autokolonn võtmena sõnaraamatu paaris “(võti, väärtus)”. Kasutame seda loendi asemel, kuna korteež on muudatuste eest kaitstud ja see on meie jaoks oluline - lõppude lõpuks ei tohiks sõnaraamatu võtmed muutuda.

Mudeli parsimine

Suurepärane, oleme sõnastiku kasutusele võtnud. Aga kuidas saaksime luua sisu, mis põhineb praegusel olekul ja sammul järgmisse olekusse? Vaatame läbi oma mudeli:

Histogrammide import Diktogrammide import juhuslikult kogumitest import deque import re def gener_random_start(modell): # Mis tahes algussõna genereerimiseks tühjendage rida: # return random.choice(model.keys()) # "Õige" algussõna genereerimiseks , kasutage allolevat koodi: # Õiged algsõnad on need, mis olid korpuses lausete alguseks, kui "END" mudelis: seed_word = "END", samas kui seed_word == "END": seed_word = model["END"]. return_weighted_random_word() return seed_word return random.choice(mudel.keys()) def gener_random_sentence(longth, markovi_mudel): praegune_sõna = genereeri_juhuslik_start(markovi_mudel) lause = i jaoks vahemikus(0, pikkus): praegune_diktogramm = markovi_mudel juhuslik_kaalutud_sõna. () praegune_sõna = juhuslik_kaalutud_sõna lause.lisa(praegune_sõna) lause = lause.suurtäht() return " ".join(lause) + "." tagasilause

Mis järgmiseks?

Proovige välja mõelda, kus saate ise Markovi ahelatel põhinevat tekstigeneraatorit kasutada. Lihtsalt ärge unustage, et kõige olulisem on see, kuidas te mudelit sõelute ja millised eripiirangud genereerimisele seate. Selle artikli autor kasutas näiteks säutsu generaatorit luues suurt akent, piiras genereeritava sisu 140 tähemärgini ja kasutas lausete alustamiseks ainult “õigeid” sõnu, st neid, mis olid lausete alguseks. korpus.

Veebiehituses ja SEO-s kasutatakse Markovi ahelaid lähtetekstide põhjal pseudotähenduslike tekstide genereerimiseks. Seda kasutatakse kindlaksmääratud ukseavade tembeldamiseks märksõnad, sisuteksti massi ja sarnaste “mustade” trikkide sisestamiseks. Õnneks on otsingumootorid õppinud Markovi kettide põhjal loodud sisu tõhusalt tuvastama ja sellised nutikad inimesed ära keelama. Ma ei hakka teile selliseid tehnoloogiaid õpetama, selleks on spetsiaalsed nõmedad saidid, mind huvitab ainult algoritmi tarkvara rakendamine.


Markovi ahel on testide jada, millest igaühes esineb ainult üks k-st. kokkusobimatud sündmused Ai pärit täisgrupp. Kus tingimuslik tõenäosus pij(s) asjaolust, et sündmus Aj toimub s-s katses, eeldusel, et sündmus Ai leiab aset (s - 1)-ndas katses, ei sõltu eelmiste katsete tulemustest.

Need, kes tahavad ajudele puhuda, saavad lugeda matemaatiline mudel. Peal inimkeel Kõik need valemid taanduvad järgmisele. Lähtetekstis defineeritakse sõnad ja jäetakse järjekord, mille järel sõnad tulevad. Seejärel luuakse see nende andmete põhjal uus tekst, milles sõnad ise on valitud juhuslikult, kuid nendevahelised seosed säilivad. Võtame näitena lastelaulu:

Metsa pärast, mägede pärast
Vanaisa Egor tuleb:
ma ise hobuse seljas,
naine lehmal,
lapsed vasikatel,
lapselapsed kitsepoegadel.

Parsisime teksti linkideks ja linkideks

Sest [mets, mäed]
metsad [tänu]
mäed [sõidud]
[vanaisa] tuleb
vanaisa [Egor]
Egor [ise]
mina [on]
[hobune, lehm, vasikad, lapsed]
hobune [naine]
naine [on]
lehm [lapsed]
lapsed [peal]
vasikad [lapselapsed]
lapselapsed [on]

Selles loendis olevad lingid esindavad ainulaadsed sõnad tekstist ja sisse nurksulud seosed on loetletud - sõnade loend, mis võivad esineda pärast seda sõna.

Linkide loendist teksti genereerimisel valitakse esimesel iteratsioonil juhuslik link, määratakse selle ühendused, valitakse linkide loendist juhuslik link ja võetakse see vastu uue lingina. Seejärel korratakse toimingut kuni jõudmiseni õige suurus tekst. Tulemuseks võib olla näiteks midagi sellist:

Egor ise vasikal, lapselapsed hobusel, naine lehmal, lapsed lehmal
Selles näites erineb saadud tekst algsest tekstist vähe, kuna originaaltekst väga lühike. Kui võtta esialgne mitme kilobaidi või isegi megabaidine sõnastik, on väljundiks täiesti ühtne tekst, kuigi sellel pole mõtet.

  1. // Loe läbi lähtetekst, mille põhjal genereeritakse uus
  2. $str = file_get_contents("markov.txt");
  3. // Süsteemi kodeeringu määramine
  4. setlocale(LC_ALL, "ru_RU.CP1251");
  5. // Eemaldage tekstist tähemärgid, välja arvatud numbrid, tähed ja mõned kirjavahemärgid
  6. $str = eregi_replace ("[^-a-zа-я0-9 !\?\.\,]" , " " , $str );
  7. // Puhastage tühikud enne lausete lõpetamist
  8. $str = eregi_asendamine (" (1,)([!\?\.\,])" , "\\1" , $str );
  9. // Jaga tekst sõnadeks
  10. $tmp = preg_split ("/[[:space:]]+/is" , $str );
  11. // "Linkide" massiiv
  12. $sõnad =Massiiv();
  13. // Täida lingid
  14. for($i = 0 ; $i< count ($tmp ); $i ++) {
  15. if ($tmp [ $i + 1 ]!= "" ) (
  16. $sõnad [ $tmp [ $i ]]= $tmp [ $i + 1 ];
  17. $sõnad = massiivi_kaart("massiivi_unikaalne" , ​​$sõnad );
  18. // Massiiv algussõnad lausetes
  19. $start =Massiiv();
  20. foreach($words as $word => $lings ) (
  21. if (ereg ("^[A-Z][a-Z]+" , $word )) (
  22. $algus = $sõna ;
  23. // Loo lähteteksti põhjal 100 lauset
  24. jaoks ($i = 0; $i< 100 ; $i ++) {
  25. samas (tõsi) (
  26. $w = $start [ rand (0 ,(count ($start )- 1 ))];
  27. if (ereg ("[\.!\?]$" , $w )) ( jätka; )
  28. $lause = $w . "" ;
  29. // Sõnade arv lauses
  30. $cnt = 1 ;
  31. // Loo pakkumine
  32. kuigi (tõsi) (
  33. $lingid = $sõnad [ $w ];
  34. // Lüliti kett
  35. $w = $sõnad [ $w ][ rand (0 ,(loend ($sõnad [ $w ])- 1 ))];
  36. $lause .= $w . "" ;
  37. // Kui sõna oli lause lõpus
  38. if (ereg ("[\.!\?]$" , $w )) ( break; )
  39. $cnt++;
  40. // Kui generaator on ahelas, siis sund välju
  41. if ($cnt > 19) ( break; )
  42. // Edukaks loetakse lause pikkusega 5-20 sõna
  43. if ($cnt > 5 && $cnt< 20 ) { break; }
  44. // Loodud pakkumine
  45. kaja $lause ;

Väike selgitus, kuidas see kõik töötab. Esiteks laaditakse fail "markov.txt", see peab olema win-1251 kodeeringus. Seejärel eemaldatakse sellelt kõik märgid, välja arvatud tähed ja mõned kirjavahemärgid, ning seejärel lõigatakse välja mittevajalikud tühikud. Selgub selge tekst, mis seejärel jaguneb üksikud sõnad. See on kõik, meil on ahelas üksikud lülid. Nüüd tuleb määrata sõnadevahelised seosed ehk millised sõnad milliste taga võivad asuda. See on kõige ressursimahukam protsess, nii et suurte failide puhul peate olema kannatlik. Kui genereerimist nõutakse sageli, on tõenäoliselt mõistlik salvestada mõnda andmebaasi linkide massiivi, et sellele kiirelt juurde pääseda. Järgmine samm- sõnade identifitseerimine, millega laused algavad. Ma nõustusin tingimusega, et selliste sõnade esimene täht peaks olema suur, saate rohkem täpne määratlus. Teksti genereerimine toimub ülalkirjeldatud algoritmi järgi, lisasin sellele lihtsalt mitu kontrolli silmuste vastu.

Näete Markovi ahelate ja ülaltoodud skripti põhjal töötavat tekstigeneraatori näidet