是的,是更新這個十分罕見的超酷演算法系列新的一集的時候了。如果你不熟悉這個系列,你可以看看之前的一些文章。
今天的主題是噴泉碼,或者稱為“無率碼”。噴泉碼是將一些資料,例如檔案,轉化為一個有效的任意數量的編碼包的方法,這樣只要你接收到稍大於信源資料包數量的編碼包的子集,就可以恢復信源資料。換句話說,你建立了一個編碼資料的“噴泉”,只要接收端接收到足夠的“水滴”,就可以恢復檔案,而不管它們接到哪一個遺漏了哪一個。
讓噴泉碼如此知名的原因是,它允許你在有損連線(比如說因特網)的情況下傳輸檔案,而且傳輸過程不依賴於你是否知道丟包率,也不需要接收端反饋哪些資料包丟失了。可以看到在很多場景,從通過廣播媒介傳送一個靜態檔案,比如點播電視,到在多源並行下載中傳播檔案包,像BitTorrent那樣,噴泉碼都得到了很好的應用。
雖然從根本上噴泉碼驚人地簡單。它有許多種類,但是在本文中我們只介紹最簡單的——LT碼,或者Luby變換碼。LT碼生成編碼包的步驟如下:
- 在 l 和 k 的之間隨機選取一個數字 d,d 代表檔案中塊的數量。我們將會在後面的內容中討論如何選取最佳的d。
- 從檔案中隨機選取 d 塊,並把它們組合起來。這裡我們可以用異或運算來組合這些塊。
- 傳送合併的塊,同時傳送它由哪些塊構成的資訊。
這些非常簡單是不是?主要依賴於我們怎麼選取塊的數量並組合起來(叫做度分佈),在接下來我們會簡短的介紹一下。你可以從上面的描述中看到有些編碼塊最後只由單一原始碼塊組成,而大部分將由多個原始碼塊組成。
另外一個可能不是立刻顯現的事情是,雖然我們確實不得不讓接收端知道輸出碼塊由哪些碼塊合併產生的,我們不需要詳細地傳送那個列表。如果傳送端和接收端使用相同的偽隨機數生成器(pseudo-random number generator,PRNG),我們可以用一個隨機選擇的種子來生成PRNG,並且用這個來選擇度和該組原始碼塊。然後我們只需要在傳送編碼塊的同時傳送種子,我們的接收端可以用相同的過程來重建我們使用過的原始碼塊列表。
解碼的過程有一點複雜,但是沒有很複雜:
- 重建用於生成編碼塊的原始碼塊列表。
- 對於列表中的每一個原始碼塊,如果已經解碼了,將它和編碼塊做異或運算,並且把它從原始碼塊列表中移除。
- 如果在列表剩下至少兩個原始碼塊,將編碼塊加入到一個等候區。
- 如果在列表中只剩下一個原始碼塊,我們已經成功的把另一個原始碼塊解碼了,那麼把它加入到已解碼檔案中,迭代等候列表,重複以上過程直到有編碼塊包含它。
讓我們通過一個譯碼例項來更清晰說明這個過程。假設我們收到5個編碼塊,每個長度是一個位元組,並且我們知道每個原始碼塊由哪些構成。我們可以用圖來表示資料,如下所示:
左邊的結點代表我們收到的編碼塊,右邊的節點代表原始碼塊。我們收到的第一結點0x48只由一個原始碼塊(第一個原始碼塊)構成,所以已經知道是哪個塊。沿著指向第一個原始碼塊箭頭的反向,可以看到第二個和第三個編碼塊都只依賴於第一個原始碼塊和另外一個原始碼塊,由於我們知道第一個原始碼塊,我們可以對它們做異或運算,如下圖所示:
重複以上過程,我們可以看到我們現在有了足夠的資訊來解碼第四個編碼塊,它依賴於第二個和第三個原始碼塊,而這兩個我們現在都知道了。對它們做異或運算,可以得到第五個也是最後一個原始碼塊,如下所示:
最後,我們可以解碼最後剩下的原始碼塊,得到剩餘的資訊:
應該承認的是這是一個非常特殊的例子,這個例子剛好接收到我們在譯碼這個資訊時需要的塊,沒有剩餘的,並且是一個非常簡單的順序,但是這個例子很好的演示演算法的原理。我確定你可以看到這個演算法應用到大規模碼塊和大規模檔案中會相當簡單。
在前面我提到選擇每個編碼塊都需要的原始碼塊的數量,即度分佈,是很重要的,它確實重要。理想情況下,我們需要生成一些只包含一個原始碼塊的編碼塊,然後可以開始譯碼了,大多數編碼塊依賴很少的其他編碼塊。這種理想的分佈是存在的,叫做理想孤波分佈。
不幸的是,理想孤波分佈在實際情況中並沒有這麼理想,正如隨機變數使得有些原始碼塊不被任何編碼塊包含,或者當所有知道的塊用完之後譯碼停止了。理想孤波分佈的一個變形,叫做穩健孤波分佈,在這方面進行了改進,用非常少的原始碼塊生成更多的碼塊,也通過合併所有的或幾乎所有的原始碼塊生成一些碼塊,來幫助破譯最後一些原始碼塊。
簡而言之,這就是噴泉碼的,更確切的說是LT碼的,工作原理。LT碼是已知的噴泉碼中效率最低的,但是最易解釋的。如果你想進一步學習,我強烈推薦讀這篇關於噴泉碼的技術論文,也可以讀Raptor碼,Raptor碼只比LT碼增加了一點複雜度,但是在傳輸開銷和計算上都顯著的提高了它們的效率。
在我們總結之前有一個進一步的思考問題。對於系統來說噴泉碼可能看起來很理想,比如說位元流,它允許種子生成和散佈幾乎無限制數量的碼塊,或多或少的消除了稀疏種子流“最後一塊”的問題,而且確保兩個隨機選擇的並行端幾乎總有有用資訊相互交換。但是它面臨一個重大的問題:驗證從並行端接收到的資料將會很難。
像位元流這樣的協議使用安全雜湊函式,比如說SHA1,和一個可信任中心(最初的上傳者),向所有的並行端傳送一個權威雜湊表。每個並行然後可以驗證他們下載的雜湊塊的檔案包,並且和權威雜湊進行對比。但是對於噴泉碼,這個是很難的。根本沒有方法在編碼塊上計算SHA1雜湊,更不要說單獨塊上的雜湊。我們不能相信我們的並行端計算的結果,因為它們可以對我們撒謊。我們可以等到我們得到全部檔案,然後從無效碼塊列表出發,嘗試推斷什麼樣的編碼塊是無效的,但這是困難的也是不可靠的,而且資訊來的時候可能已經為時已晚。一個可供選擇的方法是讓最初發布者公佈一個公共金鑰,並且標註所有的生成塊。然後我們就可以驗證編碼塊了,但代價是:現在只有最初發布者可以生成有效的編碼塊了,並且我們失去了最初使用噴泉碼的很多好處。似乎我們被困住了。
還有另一種選擇,而且已經證明是一個非常聰明的方案,叫做同態雜湊,儘管它有自己的注意事項和缺點。我們將會在下一版的超酷演算法中討論。