IA4 - 編碼、解碼與糾錯: 以二進制訊號為例
![]() |
位於美國新墨西哥州的甚大天線陣列 (Very Large Array, VLA) |
資訊 (information) 的傳遞在人類文明演進的歷史中扮演著極重要的角色,不僅僅是知識、技能,更是讓文化以及思想的瑰寶得以傳承,縱然生命會消逝,但前人所建立起的經驗卻能夠代代相傳給往後的人們。這些知識、技能和思想都可以歸納成資訊的一種,而在資訊遞交給後代的過程就稱做傳輸 (transmission)。傳輸不見得是保真的,亦即後代的人們所讀到前人的著作可能有多種版本,例如莎士比亞 (W. Shakespear) 的著作全集就有多種版本在流通,又或是1631年的邪惡聖經 (Wicked Bible) 事件,印刷廠將十誡中的Thou shalt not commit adultery (不可姦淫) 誤植成Thou shalt commit adultery (應當姦淫)。這些都是資訊在傳遞的過程當中發生失真,大部分的失真皆無大礙,並不影響資料原來意義的解讀,但少部分的失真卻是重大的,可能導致文意生變,甚至完全與當時書寫者的核心概念大相徑庭。所以既然失誤不可避免,有沒有什麼方法能讓我們盡可能的重建出原始資料的樣貌呢?
一、訊號的傳遞
讓我們來看看資料是怎麼傳輸的,以通電話為例 (圖1),當你朋友撥手機給你時,他的聲音會被手機錄下並編碼,接著通過基地台、電信機房,再到離你最近的基地台將訊號廣播出去,你的手機收到訊號後將之解碼重新轉成類比音訊,再透過喇叭將你朋友的聲音播放給你聽,同時地,若換你說話,則這個過程將逆向發生!
![]() |
圖1. 聲音訊號如何透過手機傳到朋友的手機或話筒上,補充一下當代手機的編解碼的方式是利用類似貝氏機率的方式去重建可能的原始訊號,不是這篇專題內提到的Rr編碼 |
但注意訊號在傳遞的過程可能失真,就像你聽到手機播放你朋友的聲音時,總會覺得不像是親耳聆聽他的聲音一樣清晰,像隔著一層簾幕一樣,或甚至音調也有一些變化,有時背景還會傳來微若的雜訊滋滋聲。這便是圖1中間紫色虛線框裡描述的,當訊號透過層層基地台、電信機房時,原始的訊號發生了失真,可能被干擾了,或位元流失、誤植⋯等等,如果你的手機收到這樣破損的訊號,它甚至可能沒辦法重新解讀成類比音訊撥放給你聽。這時候,在一開始你朋友的手機做了一件事,那就是不僅僅是將你朋友的聲音送出去而已,在發送聲音訊號時,同時也一併發送可以讓你手機重建原始訊號並揪出可能的干擾源並剔除,這個過程便稱做編碼 (encode),其中還附帶糾錯方式 (error correction)。而當你的收機收到訊號後,它會查看糾錯方式,並和收到的訊號做匹配,糾正不正確的雜訊,接著重建你朋友的聲音再撥放,這個過程則是解碼 (decode)。
1.1 有損傳輸
在繼續看編碼與解碼前,讓我們先來了解一下傳輸,特別是對大部分的通訊傳輸而言,它們都是不保真的,表示通過傳輸通道後,原始的資料都會發生異變。我們以二進制的例子來說,如果我傳輸了一組資料如下 發送1111111111接收1011111110 在上面的例子裡,原始發送的資料是10個1,但通過傳輸後,在接收端則是收到在第1和第9個 (依照python慣例所有序列都從0開始,這也是英制的習慣😂) 位置翻號變成0了,這便是資料傳輸時發生的失真。如果接收端知道接收的資料應全部為1,那它就可以自行更正,而由上面的例子我們發現10個位元中有兩個發生變異,我們也可以量化該傳輸通道的失真率約是0.2 (這其實蠻大的😅),往後我們在通過該傳輸通道的時候,我們對它的錯誤率有所掌握,便可用對應的算法來應付。以上便是有損傳輸的概念,沒有興趣怎麼構造一個有損傳輸的模擬的話,可以直接跳第二節了,而在第三節我們會看一個實例,通過一個雜訊通道傳輸如下圖2的加菲貓 (我未擁有加菲貓版權,本網誌亦無透過廣告利益,也無任何其他回饋給作者本人的利益,如有侵權請來信告知!) 圖片,然後再透過解碼器解碼重建圖片。
![]() |
圖2. 162×128的灰階加菲貓塗鴉,位元0表示白色,1表黑色 |
好的,我們可以這樣做有損傳輸的模擬,我先假定失真率,或者雜訊發生的機率是f,即有f的機率會讓0和1互換 1→1f↘↗↗↘0→1−f0 上述左方是原始資料,右方是經傳輸過的資料,所以如果沒有發生異變,則有(1−f)的機率會直接到對應到右方與原始資料相同的位元值,但如果發生變異,則有f的機率左上的1會變成右下的0而左下的0變成右上的1。由於原始資料經傳輸後是否發生變異就像白努力試驗一樣,若擲出正面則發生變異,反面則保持原樣,只是這擲的銅板是不公正的。所以變得到了有損傳輸的模擬算法:
- 傳輸進來的資料x=(x1,x2,⋯,xk)
- 對每個元素進行白努力試驗,其中輸入機率f,用True和False來表示試驗結果
將結果收集成一組向量f=(f1,f2,⋯,fk) - 則每個元素xi會得到一個試驗結果fi
if (fi==True): 翻號1↔0
else: xi保持不變
二、編碼與解碼
好了,接下來我們就來看要如何重建上面所述被有損傳輸攪亂或者變異的資料,要解碼首先要先編碼,在編碼的過程中,我們同時也需要告訴解碼器該如何重建原始資料。2.1 編碼器
這裡要看的是一種簡單的編碼和解碼方式,稱作重複編碼 Rr (repetition),假設原始的訊號是一組含有k個元素的向量x=(x1,x2,⋯,xk),則編碼器會輸出r個拷貝,數學表示如 輸出=共r個x⏞(xx⋮x)=(x1x2⋯xkx1x2xk⋮⋱⋮x1x2⋯xk), 當然(1)要重複幾個r是任意的,我會後面會解釋r應取奇數才是恰當的。另外為了排版方便起見,有時我們會將行向量 (column vector) 寫成 x=(x1x2⋮)=(x1,x2,⋯)T, 以列向量 (row vector) 的轉置 (transpose, T) 表示。尤其在呈現算法步驟的時候,因為排版空間的關係,沒辦法塞進一個垂直的行向量,用列向量比較節省空間,還有就是這篇小專題們只考慮二進制的訊號,亦即傳送的資訊僅為0和1,所以那些元素xi只有這兩種選擇而已。由上面的概念,Rr編碼僅單純重複拷貝原始資料r次,然後就送進傳輸通道去了,所以很簡單地得到了編碼器算法:
- 輸入欲傳輸的資料x與編碼器拷貝資料的次數r
eg. x=[1,0,0,1,0,1,0]和r=3 - return: (x,x,⋯,x⏟拷貝r個)T
eg. 輸出[[1,0,0,1,0,1,0],[1,0,0,1,0,1,0],[1,0,0,1,0,1,0]]
用簡單的python腳本來實現:
def encoder(data, repeat=3):
"""
data: data to be transmitted
repeat: times of repetition of data
"""
# Examples:
# If data = [a,b,c] and repeat=3
# The following returns [[a,b,c],[a,b,c],[a,b,c]]
return np.tile(data,(repeat,1))
在這個腳本裡,編碼器其實沒做任何事情,它就只是將資料 (data) 拷貝多次送出而已,我們用numpy的tile函數來實現複製多次的功能,其中複製的次數由參數repeat決定,預設是3。2.2 解碼器
從1.1節我們知道訊號在傳輸的途中可能受到雜訊的干擾,使得有些1翻成0或0翻成1,所以經過傳輸後 編碼後⏞(xx⋮x)→noisy channel→傳輸後⏞(x1x2⋮xr). 編碼器一開始輸出了r個相同的x,而傳輸後由於雜訊的影響,每個x都可能不再是原來的x,而且這r個傳輸後的x也都可能不太一樣了,所以我們將傳輸後每個原始資料的拷貝都標上編號,代表每個xi都可能與原始的x有所不同。舉個例子來說,他可能長成像這樣 (x1x2⋮xr)=(x11x12⋯x1kx21x22x2k⋮⋱⋮xr1xr2⋯xrk)=(1001010100101010010001001010⋮1101010), 經過雜訊的通道後,每份原始資料的拷貝都有可能受到一定程度的污染,如同(3)右邊那個含有1和0的矩陣,假設原始資料如同第一列所示是[1,0,0,1,0,1,0],但由於傳輸雜訊的關係,每一列所代表的原始資料的拷貝中都可能有一兩個元素從0翻成1或反之。
對每一行的元素來說,假設是(3)的第一行,它代表r個拷貝中的第一個元素,假設這個元素是1,原則上雜訊雖然會使得元素翻號,但應不至於太頻繁的翻號,所以將這r第一行的元素加總平均後,如果它的值大於0.5代表屬於1的數量佔大多數,按照多數決的想法,我們可以推斷原始資料的第一個元素應該是1,但如果小於0.5,則0佔大多數,該處的元素應該是0。所以在一開始編碼器複製多個原始資料的拷貝時,等同是讓解碼器能夠糾錯,盡量重建被雜訊通道攪亂的資料。基於這樣的概念,我們可以得到二進制解碼器算法:
- 輸入經過雜訊通道傳輸後的資料(x1,x2,⋯,xr)T
- 將每一行上的值加總並除以r得到x′=(x′1,x′2,⋯,x′k)
- if (x′i>0.5): return 1
else: return 0
原則上來說,如果取r是奇數的話,那麼x′i≠0.5,亦即經過了雜訊的傳輸後,僅有可能是0或1佔多數的情況,這樣解碼器則很容易推論原始資料在該處應該是0或1。但如果r是偶數,則便有可能最後在同一行中0和1一樣多,這樣便無法判別原始資料應該是哪一個,所以在一開始編碼拷貝r個原始資料時,r應避免取成偶數。用python來實現解碼器算法得到:
若把gar_array送進encoder函數裡,它會拷貝三個gar_array,再將它們送進channel函數模擬失真的傳輸過程,將輸出的值一樣用imshow作圖後得到如圖3,三張充滿雜訊的加菲貓黑白圖。
不難發現三張圖都充滿了雜訊,這是由於傳輸的過程當中有0.1的機率會使得0↔1產生翻轉,最後有些該出現黑點地方的反而變成白點,應該白色的地方反而變成黑色。但這都沒關係,一樣將這三個副本輸進decoder後將輸出做圖,我們得到了圖4。
通過重建後的加菲貓比未重建前少掉非常多的雜訊,不過還是有一些躁點,這是由於我們編碼採用重複三次的R3,如下的例子 原始10010編碼111000000111000傳輸110010000010001重建10000 由於我們是複製三份一模一樣的原始資料,但傳輸後假設三個副本當種有兩個很不幸地出現錯誤,那麼採多數決的狀況來重建,就會像倒數第二個位元組一樣,經傳輸後反而是錯誤的位元組佔大多數,這時重建的結果則仍然是錯誤的,這便稱做Rr編碼的固有錯誤率 (intrinsic error)。給定傳輸通道的發生錯誤的機率f,則Rr這種編碼經過重建後仍會存在一定比率的錯誤,這是無法消去的。但我們可以推斷,只要重拷貝多次,固有錯誤率應該會降低,即固有錯誤率和r成反比。例如在圖5,這次我們取R11,即編碼時送出11份拷貝,最後重建的結果會發現很多躁點都消失了,連貓眼都很完美的重現,不像是圖4一樣仍然埋藏在雜訊當中。
def decoder(incoming):
"""
incoming: data received
"""
# How many repetitions from the binary encoder
repeat = len(incoming)
# Step 2: Summing the incoming data along each column
corrected = np.sum(incoming, axis=0)/repeat
# Step 3: Returns the reconstructed binary data
return (corrected>0.5).astype(int)
上述腳本的第一步使用len是為了得到encode函數到底重複拷貝了幾次資料,所以我們的解碼器decode函數完全不需要使用者額外輸入編碼器重複次數r它會從收到的資料自動偵測。三、實例
在源代碼的網站上會有一個叫做data的資料夾,內含一個garfield.txt的文字檔,裡面共有20736個由0和1組成的位元組,將之讀進notebook後利用plt的imshow函數便可以得到加菲貓的黑白圖像。源代碼的範例如# Loading binary text file
data = open("data/garfield.txt","r")
bi_gar = data.readlines()
data.close()
# Convert string to numpy array
# After this, gar_img is an array with dimension (,20736)
# Later on, gar_img will be transmitted via noisy channel
gar_raw=bi_gar[0].split(",")
gar_array=np.asfarray(gar_raw)
# Reshape the 1-D array into (162,128) array for plotting
plt.imshow(gar_array.reshape((162,128)),cmap="binary_r",interpolation="None")
執行完上面的代碼後應該會得到如圖2的黑白加菲貓,這就是原始資料。
若把gar_array送進encoder函數裡,它會拷貝三個gar_array,再將它們送進channel函數模擬失真的傳輸過程,將輸出的值一樣用imshow作圖後得到如圖3,三張充滿雜訊的加菲貓黑白圖。
![]() |
圖3. 通過noisy channel後的三張gar_array的拷貝 |
不難發現三張圖都充滿了雜訊,這是由於傳輸的過程當中有0.1的機率會使得0↔1產生翻轉,最後有些該出現黑點地方的反而變成白點,應該白色的地方反而變成黑色。但這都沒關係,一樣將這三個副本輸進decoder後將輸出做圖,我們得到了圖4。
![]() |
圖4. 通過decoder重建後的加菲貓,原始資料經過R3編碼 |
通過重建後的加菲貓比未重建前少掉非常多的雜訊,不過還是有一些躁點,這是由於我們編碼採用重複三次的R3,如下的例子 原始10010編碼111000000111000傳輸110010000010001重建10000 由於我們是複製三份一模一樣的原始資料,但傳輸後假設三個副本當種有兩個很不幸地出現錯誤,那麼採多數決的狀況來重建,就會像倒數第二個位元組一樣,經傳輸後反而是錯誤的位元組佔大多數,這時重建的結果則仍然是錯誤的,這便稱做Rr編碼的固有錯誤率 (intrinsic error)。給定傳輸通道的發生錯誤的機率f,則Rr這種編碼經過重建後仍會存在一定比率的錯誤,這是無法消去的。但我們可以推斷,只要重拷貝多次,固有錯誤率應該會降低,即固有錯誤率和r成反比。例如在圖5,這次我們取R11,即編碼時送出11份拷貝,最後重建的結果會發現很多躁點都消失了,連貓眼都很完美的重現,不像是圖4一樣仍然埋藏在雜訊當中。
讓我們在結束前花一些時間看看固有錯誤率。以R3為例的話,代表三個副本內只要出現兩個以上的錯誤即會使得重建的結果仍然是錯誤的,由於每個位元組經過傳輸後出現錯誤的機率是f,兩個都錯就是f2,再來這兩個錯誤是處在哪個位置並不重要,所以是個排列的問題,必須乘上二項式係數(32)=3,對f=0.1來說,理論預測的固有錯誤率是(32)f2=0.03,即每百個像素平均會錯2−3個,對黑白加菲貓共有20736個像素點的情況,固有錯誤率是620個點上下。當然傳輸後很不幸三個副本都翻號也是會導致錯誤,只是這樣的情況的機率等於f3=0.001,所以固有錯誤率的精確值是0.031。
從圖6來看,在加菲貓的R3編碼每個位置的位元都送出三份拷貝,但如果在傳輸的過程中,該位置的位元有兩個以上發生錯誤,像圖6左邊的狀況,則該處的位元不能正確重建。另外右邊R5的狀況,在同個位置的五份拷貝中,只要錯三個以上也不能重建。將這些出現可能出現多個錯誤的機率加總起來,便稱作Rr編碼的固有錯誤率pinstri(Rr)。總括來說,r個副本,只要錯超過一半一上,則解碼器不能正確重建原始資料。
對五個拷貝的副本,五個中錯三個以上的機率用二項式分佈描述是 (53)f3(1−f)2⏟錯3個+(54)f4(1−f)⏟錯4個+f5⏟全錯≡pintri(R5), 其中pinstri(R5)表示R5編碼的固有錯率,而f是傳輸時發生錯誤的機率。按照上述的邏輯,對於Rr編碼法,只要錯超過一半以上,就會使得解碼的糾錯失效。所以對於r個拷貝來說,從錯了(r+1)/2個開始,一路錯到滿r個的機率是 pinstri(Rr)≡(rr+12)f(r+1)/2(1−f)(r−1)/2⏟錯r+12個+(rr+32)f(r+3)/2(1−f)(r−3)/2⏟錯r+12+1個+⋯+fr⏟全錯=r∑n=(r+1)/2(rn)fn(1−f)r−n, 對任何奇數r皆成立。通常來說,固有錯誤率最大的貢獻項是首項,即f的最低次方項,這是由於f通常小於1,以f=0.1的情況,他的次方每多一次,則其值小一個order,除非二項式係數(rn)可以補足因為多乘上一個f變小的的損失,所以對於固有錯誤率的計算,通常可以考慮首項或者是next-to-leading-order term便夠精準了。
我們在這裡取的重複編碼法Rr其實已經不常被使用了,想像每打一通電話,編碼會送出三份以上的拷貝,就等同我們要付三通電話的費用,再加上這樣頗浪費頻寬,所以後來有許多編碼與糾錯的方式不斷被提出,例如漢明碼 (Hamming's code) 便是一個例子,當然這也是有機會再討論的專題了!
Source on github: I&A4_encode_decode.ipynb
從圖6來看,在加菲貓的R3編碼每個位置的位元都送出三份拷貝,但如果在傳輸的過程中,該位置的位元有兩個以上發生錯誤,像圖6左邊的狀況,則該處的位元不能正確重建。另外右邊R5的狀況,在同個位置的五份拷貝中,只要錯三個以上也不能重建。將這些出現可能出現多個錯誤的機率加總起來,便稱作Rr編碼的固有錯誤率pinstri(Rr)。總括來說,r個副本,只要錯超過一半一上,則解碼器不能正確重建原始資料。
![]() |
圖6. Rr編碼中只要拷貝的副本在傳輸後有同個位置的位元超過一半以上出現錯誤則該處的位元重建結果仍然是錯的 |
對五個拷貝的副本,五個中錯三個以上的機率用二項式分佈描述是 (53)f3(1−f)2⏟錯3個+(54)f4(1−f)⏟錯4個+f5⏟全錯≡pintri(R5), 其中pinstri(R5)表示R5編碼的固有錯率,而f是傳輸時發生錯誤的機率。按照上述的邏輯,對於Rr編碼法,只要錯超過一半以上,就會使得解碼的糾錯失效。所以對於r個拷貝來說,從錯了(r+1)/2個開始,一路錯到滿r個的機率是 pinstri(Rr)≡(rr+12)f(r+1)/2(1−f)(r−1)/2⏟錯r+12個+(rr+32)f(r+3)/2(1−f)(r−3)/2⏟錯r+12+1個+⋯+fr⏟全錯=r∑n=(r+1)/2(rn)fn(1−f)r−n, 對任何奇數r皆成立。通常來說,固有錯誤率最大的貢獻項是首項,即f的最低次方項,這是由於f通常小於1,以f=0.1的情況,他的次方每多一次,則其值小一個order,除非二項式係數(rn)可以補足因為多乘上一個f變小的的損失,所以對於固有錯誤率的計算,通常可以考慮首項或者是next-to-leading-order term便夠精準了。
四、小結
在這個小專題裡我們討論了資訊傳輸時會發生錯誤,但如果能夠適當的引入糾錯,例如在資料傳輸前先將其編碼並放入糾錯資訊,那麼在受方重建資訊時,便能夠盡量得到跟原始資料相仿的結果。我們在這裡取的重複編碼法Rr其實已經不常被使用了,想像每打一通電話,編碼會送出三份以上的拷貝,就等同我們要付三通電話的費用,再加上這樣頗浪費頻寬,所以後來有許多編碼與糾錯的方式不斷被提出,例如漢明碼 (Hamming's code) 便是一個例子,當然這也是有機會再討論的專題了!
Source on github: I&A4_encode_decode.ipynb
留言
張貼留言