卷积码

3. 卷积码

卷积码指Convolutional Code。

卷积码,将k位信息编码成n位(当前组),这n个bit的生成不仅和这k位有关,还和之前的N-1个组的信息有关。总共关联的有N * n 个bit。 我们一般记为 (n,k,N) 卷积码

可以看出来,当N增加时,纠错能力也增加,差错率随着N增大而指数下降。我们记N为卷积码的约束长度。

编码

(n,k,N) 卷积码由下面部分组成。

  • N组k级输入移位寄存器 (一个长度为N * k 的存储空间,理解成一维数组接收数据输入,或理解成N * k 的二维数组暂存状态信息)
  • n级输出移位寄存器 和 n个模二加法器 (每一级输出寄存器对应一个加法器)
image

如上图,每个异或(模二加法器)对应的输入是输入序列(N组k级寄存器)中的某些特定位。这些位是在编码中固定的。卷积码的生成矩阵就是在描述 N * K 位输入移位寄存器和每个模二加法器的连接关系。

一开始输入寄存器里全是0. 然后第一组k个bit输入,接着输出n个bit,再接着输入第二组bit,再输出第二组bit ...

一般来说,卷积码都是 (n,1,N) 的形式。在阅读了一些论文发现采用的都是 (2,1,N) 的形式,如(2,1,3),(2,1,6)等 (为什么)
很明显,当k=1时,每一个bit都要编码成n个bit去传输,流量 * n. 所以n不能太大,2、3可能就已经是极限。

libcorrect 的编码实现

我们可以对 https://github.com/quiet/libcorrect 的源码做分析。

// \src\convolutional\encode.c 

... 
size_t correct_convolutional_encode(correct_convolutional *conv,
                                    const uint8_t *msg,
                                    size_t msg_len,
                                    uint8_t *encoded) {
    ...
    for (size_t i = 0; i < 8 * msg_len; i++) {
            // shiftregister has oldest bits on left, newest on right
            shiftregister <<= 1;
            shiftregister |= bit_reader_read(conv->bit_reader, 1);
            shiftregister &= shiftmask;
            // shift most significant bit from byte and move down one bit at a time
            //  - 移位寄存器左移,留出空位(右侧最低位)存储新的bit。旧bit在最高位通过掩码shiftmask完成删除。 shiftmask = (1 << conv->order) - 1;


            // we do direct lookup of our convolutional output here
            // all of the bits from this convolution are stored in this row
            unsigned int out = conv->table[shiftregister];
            bit_writer_write(conv->bit_writer, out, conv->rate);
            // 注意到完成多次模二加法(异或)也是需要时间的,改用查表即可。预先将结果放在table里即可。
        }

注意到上面代码中的 *conv ,是一个卷积对象,我们可以进入其头文件查看相关的定义。


// \include\correct\convolutional\convolutional.h
struct correct_convolutional {  
    const unsigned int *table;  // size 2**order 
    // 空间换时间,因为是N个bit,所以对应2^N个空间
    size_t rate;                // e.g. 2, 3... 
    // 明显的,这里的rate 指的是 (n,1,N) 中的n, 即模二加法器的个数 或是每次编码的bit数
    
    size_t order;               // e.g. 7, 9... 
    // 明显的, 这里的order 指的是 (n,1,N) 中的N,即状态寄存器的个数。又因为
    unsigned int numstates;     // 2**order
    
    bit_writer_t *bit_writer;
    bit_reader_t *bit_reader;
    // 读写操作
    ...
};

考虑如何预填写table。


// src\convolutional\lookup.c
void fill_table(unsigned int rate,
                unsigned int order,
                const polynomial_t *poly, // 每个模二加法器的输入位关联关系可以用一个多项式来表示,
                unsigned int *table) {
    for (shift_register_t i = 0; i < 1 << order; i++) { // N位bit从全0到全1,用i循环来表示
        unsigned int out = 0;
        unsigned int mask = 1;
        for (size_t j = 0; j < rate; j++) {
            out |= (popcount(i & poly[j]) % 2) ? mask : 0;
            /*
                注意到模二加法器的多项式本质上是一个系数只有0和1的式子
                所以我们可以用一个二进制串(整数)来表示这个多项式
                那么rate(n)个加法器对应着n个整数,存储在poly[]数组里
                
                tmp = i&poly[j] 
                tmp 表示i状态对第j个模二加法器运算后的每一位串联的结果
                但是我们想要的是每一位的异或,所以本质上还是要tmp的每一位异或起来
                
                可以统计tmp中1的个数,若为偶数个则异或结果为0.
                统计个数可以用popcount函数。这是一个魔法函数,能O(1)算出来。
                利用mask放到out对应的位上。从低位到高位,依次对应的是j个加法器。
            */
            mask <<= 1;
        }
        table[i] = out;
    }
}

这说明这里实现的是一个(n,1,N)的卷积码编码。

树状图 网格图

树状图与网格图可以直观的体现所有码字的可能性,更可以帮助设计解码算法。

由于k=1,每次新输入一个bit,取值只能为0或1,所以我们可以将 前N-1个 寄存器串联看成一个状态节点,根据新输入的不同可得两个分叉,分别对应两个新的状态节点... 如此往复,可以构造出一个二叉状态树。(其实是每次输入k个bit,对应一个节点有 2^k 个分叉 )

以(2,1,3) 卷积码为例

1.png

如上图,寄存器初始状态全为0,所以状态根节点a=00.

如果此时输入的新bit为0,则下一步状态节点还是a=00 ,但如果输入的是1,则下一步状态节点就变成b=01.(从保存数据最旧的寄存器开始看,或者说从右往左记录为状态) 同理类推。因为是前N-1个,所以总共可能出现的状态就 2^{k(N-1)} 种,之后的状态必定是循环回去了。

2.png

注意上图的树边,所标记的数字表示的是输出。即在当前这个状态下,根据新输入bit的不同而输出的不同。

例如a=00状态时,输入的新比特为0,三个寄存器全为0的输出是"00"。 再例如状态b=01,如果新比特输入的是0 (即010),输出是10;若新比特输入的是1(即011),输出的是01。(输出从左往右看,这是根据加法器编号从小到大来看的)。

同理,我们只需要保证每个状态以及其后续变化都出现过就行了,所以像树的上方递归3层的a状态可以省略不画,因为本质上是一样的。

3.png

我们可以将同种状态剥离合并(同状态的后续变化过程与之前状态无关),画成网格形式。

4.png

网格图内每一次的状态变化被称为“一节”。像右边的红框内每一次状态可能变化都是一样的,被称为稳态。根据网格图可以进行编码。如输入的bit依次是1100,则经过状态分别是abdcb,输出11010100。

译码

卷积码有多种译码算法,如维特比译码(Viterbi decoding algorithm),序列译码,门限译码等。其中维特比译码性能是接近最优的,但实现起来也很复杂。 libcorrect 也是用的维特比译码算法,这里着重介绍这个算法。

先考虑网格图译码过程。例如接受的码字是11010100,我们可以根据网格图进行对比。可以直接得出结果。

5.png

如果出现误码,我们可以选择与接收序列更接近的一种变化继续下去。但也可能出现所有变化都等距的情况(例如收到的是10,两个变化分别是00和11)。一种朴素的想法是:考虑往后几步的多种变化,再选最小距离的。然而这会导致延时与性能问题,故引入到维特比译码算法。

维特比算法是基于最大似然译码算法(通过概率推导出应该是哪个码最接近),(通过一阵复杂的概率公式推导得出)在这里它等效于最小距离译码。

记状态变化的路径量度(可理解成一条边的权值)为该路径输出与收到序列之间的距离。(一般是汉明距离)。整条路径的路径量度是各边的权值和。

维特比算法是一种类似 动态规划思想 的算法。本质上是用动态规划在这个特殊图上求最短路问题。

  1. 初始状态为 a=00
  2. 当前是第i步译码,前i-1步译码后停留在状态j时的最小路径度量值是f[i-1][j]
  3. 考虑f[i][j]的变化。选择一个可以使f[i][j]值最小的状态j',由其状态j'变化而来。
  4. 每种状态都确定好之前由哪一个状态变化而来,也意味着有多少个状态就有多少个不同的路径。这些路径被称为“幸存路径”
  5. 若幸存路径中有相同重叠的部分,则可以直接输出这部分(作为部分译码结果)。
  6. 若译码未结束返回到第2步。若译码结束且有多种相同度量值的路径可供选择,则随机选择一种。否则选择度量值最小的那个路径。

例如:

6.png
  1. 接收的第一段是11,一开始状态 a,可能的两种变化分别是:
  • 输出00 到状态a (aa,累计路径度量 = 2)
  • 输出11 到状态b (ab,累计路径度量 = 0)

注意到这里两条路径(aa,ab)都是“幸存路径”,且没有重合部分,所以继续译码。

  1. 接收的第二段是01,在第一步之后的两个幸存路径分别对应状态是aa,ab.
  • aa状态
    • 输出00,到状态a(aaa,累计路径度量=2+1=3)
    • 输出11,到状态b(aab,累计路径度量=2+1=3)
  • ab状态
    • 输出10,到状态c(abc, 累计路径度量=0+2=2)
    • 输出01,到状态d(abd,累计路径度量=0+0=0)

如下图左侧

7.png

到译码第三段时,可以发现:f[3][a]即第三段译码后状态是a的可能性有两种,即 aaa-aabc-a,分别对应路径度量值是4和3。所以这里保留abc-a这条路路径,而不是aaa-a

同理,其他状态经过同样操作后,剩余的幸存路径都用红色标出。他们有重合部分即最开始的 a-b,对应的译码结果是1。而最开始的a-a 路径分支就被舍弃了。

8.png

到译码第四段 接收到10时,也有如下变化,最后每种状态保留一条幸存路径。注意,在上一步输出译码为1后,这四条幸存路径并没有新的重合部分。 所以需要继续读取并进行译码。

最后结果如下:

9.png

注意到最后四条路径里以d状态结尾的转移路径的度量值最低,所以选择这条路径做为译码结果。即11001111. 并且这也可以推出是第4段和第7段产生了误码。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容