在 Swift 中使用馬爾可夫鏈生成文字

2021-09-11 10:02:44 字數 4303 閱讀 6408

校對:numbbbbb,mmoaay,cee

定稿:cmb

馬爾可夫鏈可用於快速生成真實但無意義的文字。今天,我將使用這種技術來建立乙個基於這篇部落格內容的文字生成器。這個靈感**於讀者 jordan pittman。

理論上講,馬爾可夫鏈是一種狀態機,每乙個狀態轉換都有乙個與之相關的概率。你可以選擇乙個起始狀態,然後隨機地轉換成其他狀態,通過轉移概率來加權,直到到達乙個終止狀態。

馬爾可夫鏈有著廣泛的應用,但最有趣的是用於文字生成。在本文生成領域,每個狀態是文字的一部分,通常是乙個單詞。狀態和轉換是由一些語料庫生成的,然後遍歷整個鏈並為每個狀態輸出單詞來生成文字。這樣生成的文字通常沒有實際意義,因為該鏈不包含足夠的資訊來保留語料庫的任何潛在含義及語法結構,但是缺乏意義本身卻給文字帶來了意料之外的樂趣。

鏈中的節點由word類的例項表示,此類將會為它所表示的單詞儲存乙個字串,同時持有一組指向其他單詞的鏈結。

最終,我決定偷懶使用一種在空間上極其浪費,但在時間上效率很高且易於實現的結構。該結構每個word包含乙個後續words的陣列。如果乙個鏈結被指向多次,那麼將會儲存重複的words陣列。在陣列中選擇乙個隨機索引,根據索引返回具有適當權重的隨機元素。

word類結構如下:

class

word

func

randomnext

() -> word

}複製**

請注意,links陣列可能會導致大量迴圈引用。為了避免記憶體洩漏,我們需要手動清理那些記憶體。

我們引入chain類,它將管理鏈中所有的words

class

chain

}複製**

如果沒有這一步,許多單詞例項的記憶體都會洩漏。

現在讓我們看看如何將單詞新增到鏈中。add方法需要乙個字串陣列,該陣列中每乙個元素都儲存著乙個單詞(或呼叫者希望使用的其他任何字串):

func

add(_ words: [string])

複製**

我們想要遍歷那些成對的單詞,遍歷規則是第二個元素的第乙個單詞緊隨第乙個元素後面的單詞。例如,在句子 "help, i'm being oppressed," 中,我們要迭代("help", "i'm")("i'm", "being")("being", "oppressed")

實際上,還需要多做一點事情,因為我們需要編碼句子的開頭和結尾。我們將句子的開頭和結尾用nil表示,所以我們要迭代的實際序列是(nil, "help")("help", "i'm")("i'm", "being")("being", "oppressed")("oppressed", nil)

為了允許值為nil, 我們的陣列宣告為string?型別,而不是string型別。

let words = words as [string?]

複製**

接下來構造兩個陣列,乙個頭部新增nil,另乙個尾部新增nil。把它們通過zip合併在一起生成我們想要的序列:

let wordpairs = zip([nil] + words, words + [nil])

for (first, second) in wordpairs

}複製**

word輔助方法從words字典中提取例項,如果例項不存在就建立乙個新例項並將其放入字典中。這樣就不用擔心字串匹配不到單詞:

func

word

(_ str: string?)

-> word else

}複製**

最後生成我們要的單詞序列:

func

generate

() -> [string] else

}複製**

返回包含所有單詞的result

return result

}}複製**

最後一件事:我們正在使用string?作為words的鍵型別,但optional不符合hashable協議。下面是乙個擴充套件,當它的封裝型別遵循hashable時新增optionalhashable的實現:

extension

optional: hashable

where

}}複製**

備註:swift 4.2 中optional型別已預設實現hashable協議

以上就是馬爾可夫鏈的結構,下面我們輸入一些真實文字試試看。

我決定從rss提要中提取文字。還有什麼比用我自己部落格全文作為輸入更好的選擇呢?

let feedurl = url(string: "")!

rss是一種xml格式,所以我們使用xmldocument來解析它:

let xmldocument = try! xmldocument(contentsof: feedurl, options: )

let descriptionnodes = try! xmldocument.nodes(forxpath: "//item/description")

我們需要xml節點中的字串,所以我們從中提取並過濾掉為nil的內容。

let descriptionhtmls = descriptionnodes.compactmap()

我們根本不用關心標籤。nsattributedstring可以解析html並生成乙個attributedstring,然後我們可以過濾它:

let descriptionstrings = descriptionhtmls.map()

複製**

我們需要乙個將字串分解成若干部分的函式。我們的目的是生成 string 陣列,每個陣列對應文字裡的一句話。一段文字可能會有很多句話,所以wordsequences函式會返回乙個 string 的二維陣列:

func wordsequences(in str: string) -> [[string]] )

複製**分割的句子最終被新增到result中:

})複製**

列舉完成後,根據輸入的句子計算出result,然後將其返回給呼叫者:

return result

}複製**

回到主**。現在已經有辦法將字串轉換為句子列表,我們就可以繼續構建自己的馬爾可夫鏈。首先我們建立乙個空的chain物件:

let chain = chain()

然後我們遍歷所有的字串,提取句子,並將它們新增到鏈中:

for str in descriptionstrings 

}複製**

最後一步當然是生成一些新句子!我們呼叫generate(),然後用空格連線結果。輸出結果可能命中也可能不命中(考慮到該技術的隨機性,這並不奇怪),所以我們會多生成一些:

for_in

0 ..< 200

複製**

為了演示,下面是這個程式的一些示例輸出:

上面有很多無意義的句子,所以你必須深入挖掘才能找到有意義的句子,但不可否認馬爾可夫鏈可以產生一些非常有趣的輸出。

馬爾可夫鏈

馬爾可夫鏈,因安德烈 馬爾可夫 a.a.markov,1856 1922 得名,是指數學中具有馬爾可夫性質的離散事件 隨機過程 該過程中,在給定當前知識或資訊的情況下,過去 即當前以前的歷史狀態 對於 將來 即當前以後的未來狀態 是無關的。x1,x2,x3.馬爾可夫鏈 markov chain 描述...

馬爾可夫鏈

定義設 是乙個隨機過程,如果 在 t0 時刻所處的狀態已知,它在時刻 t t0 所處的狀態的條件分布與其在 t0 之前所處的狀態無關。通俗地說,就是在知道過程現在的條件下,其將來的條件分布不依賴於過去,則稱 隨機過程 具有馬爾可夫 markov 性 是乙個隨機過程,若其滿足馬爾可夫性,則稱其為馬爾可...

馬爾可夫鏈

1.馬氏性 未來的狀態值只與當前狀態有關,與前面的狀態無關,具體為 2.時變性 就是指轉移概率僅僅與時間間隔寬度有關,和時間始點終點無關。m 步 轉移到狀態j 的轉移概率。例 某計算機機房的一台計算機經常出故障,研究者每隔15 分鐘觀察一次計算 機的執行狀態,收集了24 小時的資料 共作97 次觀察...