有限码长下的 Polar 码:系统 Polar 码、CRC 与性能曲线怎么看
前 15 篇已经介绍了 Polar 码的构造、SC、SCL 和 CA-SCL。本篇继续回答一个更接近实际的问题:理论上容量可达的 Polar 码,为什么在有限码长下还需要系统编码、CRC 和大量仿真?
本文先区分渐近结论和有限码长性能,再说明系统 Polar 码究竟改变了什么,最后解释 CRC、BER、BLER 和性能曲线之间的关系。全文采用 0-based 索引,并使用下面的基本符号:N=2^n 是 Polar 母码长,A 是消息向量比特数,L_{\mathrm{CRC}} 是 CRC 比特数,K 是实际进入 Polar 编码器的信息比特数,R=K/N 是 Polar 码率。
“容量可达”到底说明什么
“容量可达”描述的是码长趋于无穷时的一类理论性质,不是对某一个短码的性能承诺。设信道为二元输入离散无记忆信道 W,其对称容量为 I(W)。Polar 码的渐近结论可以概括为:当 N 越来越大时,可以选择合适的信息位集合,使
\frac{K}{N}\longrightarrow I(W),\qquad P_{\mathrm{BLER}}\longrightarrow 0这里 P_{\mathrm{BLER}} 表示块错误概率。这个结论的含义是:码率可以逼近信道的对称容量,同时仍能把块错误概率压到 0。它没有说任意 N、任意构造方法、任意译码器下的 Polar 码都已经达到这个极限。
因此,“容量可达”至少包含三个条件:码长要进入渐近区域,信息位要按照信道可靠性合理选择,译码器也要采用与理论结论相对应的规则。实际系统往往使用有限的 N,还会加入 CRC、列表译码、量化和率匹配,所以最终性能必须通过有限码长实验观察。
为什么有限码长会产生性能差距
有限码长下,Polar 码的子信道还没有完全分成“几乎可靠”和“几乎不可靠”两类。可靠性排序中会有一批处在中间区域的子信道,构造器必须在这些位置中作出选择。这个选择一旦有偏差,就会直接影响译码错误率。
性能差距通常来自以下几方面:
- 极化仍不充分。 N 较小时,部分子信道的可靠性处于过渡区,不能简单按照渐近结论处理。
- 构造依赖条件。 GA 等构造方法使用 design-SNR,记为 \rho_{\mathrm{des}}。如果构造条件与实际仿真的 \rho_{\mathrm{sim}} 相差较大,可靠性排序可能不再合适。
- 译码能力有限。 SC 只保留一条判决路径;SCL 保留 L_{\max} 条路径,但列表宽度受复杂度和存储限制。
- 发送链路会改变观测。 率匹配、LLR 量化和饱和都会改变译码器实际看到的 \underline{\lambda}。
有限块长理论常用下面的正规近似来说明这种差距的尺度:
R^*(N,\epsilon)\approx C-\sqrt{\frac{V}{N}}Q^{-1}(\epsilon)+O\left(\frac{\log N}{N}\right)其中 C 是信道容量,V 是信道色散,\epsilon 是目标块错误概率,Q^{-1} 是高斯 Q 函数的反函数。这个式子不是某个具体 Polar 构造的性能公式,它只说明:当 N 有限时,可靠通信的最大码率一般会比容量低,并且会出现与 1/\sqrt{N} 有关的代价。
先核对消息长度、CRC 长度和码率
加入 CRC 后,进入 Polar 编码器的并不是只有消息向量。设消息向量为
\underline{m}_0^{A-1}\in\mathbb{F}_2^ACRC 根据 \underline{m} 产生 L_{\mathrm{CRC}} 个校验比特,记为 \underline{r}_0^{L_{\mathrm{CRC}}-1}。把两者连接起来,得到 Polar 编码器真正接收的信息序列:
\underline{b}_0^{K-1}=\underline{m}_0^{A-1}\Vert\underline{r}_0^{L_{\mathrm{CRC}}-1},\qquad K=A+L_{\mathrm{CRC}}因此,Polar 码率是
R=\frac{K}{N}=\frac{A+L_{\mathrm{CRC}}}{N}而如果只关心消息向量占母码长的比例,还可以定义消息效率
R_{\mathrm{msg}}=\frac{A}{N}这两个量不能混用。R 决定有多少比特进入 Polar 编码器,R_{\mathrm{msg}} 只计算真正要交付给上层的消息向量。
一个参数核对例子
取
A=32,\qquad L_{\mathrm{CRC}}=6,\qquad N=64则
K=32+6=38,\qquad R=\frac{38}{64}=0.59375,\qquad R_{\mathrm{msg}}=\frac{32}{64}=0.5如果把 32/64=0.5 写成 Polar 码率,就漏掉了进入编码器的 6 个 CRC 比特。反过来,如果比较的是消息向量的传输效率,写成 38/64 又会把校验比特当成消息向量。
如果保持 K=38 不变而把码长改为 N=128,则
R=\frac{38}{128}=0.296875这个配置和 N=64 的配置已经不是相同码率的比较。若要研究码长的影响,必须明确是固定 K、固定 A,还是固定 R。不同的固定方式会改变问题本身。
什么是系统 Polar 码
先回顾非系统 Polar 编码。设 \mathbf{G}_N 为 Polar 生成矩阵,信息位集合为 \mathcal{A},冻结位集合为 \mathcal{A}^c=[0,N-1]\setminus\mathcal{A}。冻结位固定为 0 时,极化输入向量 \underline{u}_0^{N-1} 经过编码得到
\underline{c}_0^{N-1}=\underline{u}_0^{N-1}\mathbf{G}_N在非系统形式中,消息向量或消息向量加 CRC 的比特通常位于 \underline{u}_{\mathcal{A}} 中,但不一定会在码字 \underline{c} 的相同坐标上直接出现。
系统 Polar 码增加了一个坐标约束。选定码字中的系统坐标集合 \mathcal{I},满足 |\mathcal{I}|=K,希望
\underline{c}_{\mathcal{I}}=\underline{b}_0^{K-1}这里的 \underline{b} 是消息向量与 CRC 连接后的信息序列。系统编码的关键不是把 \underline{b} 直接写进 \underline{u}_{\mathcal{A}},而是先求出一组 \underline{u}_{\mathcal{A}},使编码后的指定坐标恰好等于 \underline{b}。
把生成矩阵抽取出信息位对应的行和系统坐标对应的列,记为
\mathbf{G}_{\mathcal{A},\mathcal{I}}=\mathbf{G}_N[\mathcal{A},\mathcal{I}]在冻结位为 0 且该子矩阵可逆时,有
\underline{c}_{\mathcal{I}}=\underline{u}_{\mathcal{A}}\mathbf{G}_{\mathcal{A},\mathcal{I}}要满足系统约束,就需要在 \mathbb{F}_2 中求解
\underline{u}_{\mathcal{A}}=\underline{b}_0^{K-1}\mathbf{G}_{\mathcal{A},\mathcal{I}}^{-1}这说明系统编码是一次线性方程求解,而不是对码字的几位进行事后替换。
N=4 的系统编码例子
为了把这个过程算清楚,取自然顺序下的生成矩阵
\mathbf{G}_4=\begin{bmatrix}1&0&0&0\\1&1&0&0\\1&0&1&0\\1&1&1&1\end{bmatrix},\qquad \mathcal{A}=\{2,3\},\qquad \mathcal{I}=\{2,3\}冻结位为 u_0=u_1=0,抽出的子矩阵是
\mathbf{G}_{\mathcal{A},\mathcal{I}}=\begin{bmatrix}1&0\\1&1\end{bmatrix}设要发送的系统信息为 \underline{b}=(b_0,b_1)。在 \mathbb{F}_2 上有
\begin{bmatrix}1&0\\1&1\end{bmatrix}\begin{bmatrix}1&0\\1&1\end{bmatrix}=\begin{bmatrix}1&0\\0&1\end{bmatrix}所以这个子矩阵的逆矩阵就是自身,从而
\underline{u}_{\mathcal{A}}=(b_0,b_1)\begin{bmatrix}1&0\\1&1\end{bmatrix}=(b_0\oplus b_1,b_1)把 u_2=b_0\oplus b_1、u_3=b_1 写回输入向量后,编码得到
c_2=u_2\oplus u_3=b_0,\qquad c_3=u_3=b_1因此 (c_2,c_3)=(b_0,b_1),系统约束成立。
如果直接把 (b_0,b_1) 填入 (u_2,u_3),则
c_2=b_0\oplus b_1通常不等于 b_0。这就是系统编码最容易被误解的地方:系统位置上的信息是经过求解后出现在码字中的,不是简单复制进去的。
系统 Polar 编码的 C++ 实现
下面的函数实现文章前面定义的系统编码过程。输入的 message 是长度为 K 的消息序列;它可以只包含消息向量,也可以包含消息向量和 CRC,具体取决于上层约定。infoIndices 按母码索引升序给出信息位集合 \mathcal A,systemIndices 给出码字中的系统坐标集合 \mathcal I,两者长度都必须是 K。
代码采用自然顺序生成矩阵
\mathbf G_N=\mathbf F_2^{\otimes n},\qquad [\mathbf G_N]_{i,j}=1\ \Longleftrightarrow\ (j\mathbin{\&}i)=j.其中 C++ 的 & 只用于判断索引的二进制位关系;真正的编码加法仍然使用 GF(2) 中的异或。函数先求解 \underline{u}_{\mathcal A}\mathbf G_{\mathcal A,\mathcal I}=\underline b,再把求得的输入向量编码为长度 N 的码字。
#include <algorithm>
#include <cstddef>
#include <stdexcept>
#include <vector>
std::vector<int> systematicPolarEncode(
const std::vector<int>& message,
std::size_t N,
const std::vector<std::size_t>& infoIndices,
const std::vector<std::size_t>& systemIndices) {
const std::size_t K = infoIndices.size();
// Polar 母码长必须是 2 的幂;系统坐标和信息位集合都必须有 K 个位置。
if (N == 0 || (N & (N - 1)) != 0 ||
message.size() != K || systemIndices.size() != K) {
throw std::invalid_argument("inconsistent Polar-code dimensions");
}
// 检查比特值、索引范围和索引唯一性。
std::vector<bool> seenInfo(N, false);
std::vector<bool> seenSystem(N, false);
for (int bit : message) {
if (bit != 0 && bit != 1) {
throw std::invalid_argument("message bits must be binary");
}
}
for (std::size_t i : infoIndices) {
if (i >= N || seenInfo[i]) {
throw std::invalid_argument("invalid information-bit index");
}
seenInfo[i] = true;
}
for (std::size_t i : systemIndices) {
if (i >= N || seenSystem[i]) {
throw std::invalid_argument("invalid systematic-bit index");
}
seenSystem[i] = true;
}
// 构造自然顺序下的 G_N。G[i][j] 表示输入位 u_i
// 是否参与码字位 c_j;因此 c_j 是所有相关 u_i 的 GF(2) 异或。
std::vector<std::vector<int>> G(N, std::vector<int>(N, 0));
for (std::size_t i = 0; i < N; ++i) {
for (std::size_t j = 0; j < N; ++j) {
G[i][j] = ((j & i) == j) ? 1 : 0;
}
}
// 把行向量方程 u_A * G[A,I] = message 改写成普通的
// 方程组:每一行对应一个系统坐标,每一列对应一个未知的 u_A。
std::vector<std::vector<int>> equation(
K, std::vector<int>(K + 1, 0));
for (std::size_t row = 0; row < K; ++row) {
equation[row][K] = message[row];
for (std::size_t col = 0; col < K; ++col) {
equation[row][col] =
G[infoIndices[col]][systemIndices[row]];
}
}
// GF(2) Gauss-Jordan 消元。每一步把当前列化为单位阵的一列;
// 如果找不到主元,说明 G[A,I] 不可逆,不能按这组坐标系统化。
for (std::size_t col = 0; col < K; ++col) {
std::size_t pivot = col;
while (pivot < K && equation[pivot][col] == 0) {
++pivot;
}
if (pivot == K) {
throw std::invalid_argument(
"G[A,I] is singular; choose another system set");
}
if (pivot != col) {
std::swap(equation[pivot], equation[col]);
}
for (std::size_t row = 0; row < K; ++row) {
if (row != col && equation[row][col] != 0) {
for (std::size_t j = col; j <= K; ++j) {
equation[row][j] ^= equation[col][j];
}
}
}
}
// 消元完成后,左侧是单位阵,最后一列就是 u_A。
std::vector<int> u(N, 0);
for (std::size_t k = 0; k < K; ++k) {
u[infoIndices[k]] = equation[k][K];
}
// 执行 c = u * G_N。每个 c_j 都在 GF(2) 中累加,
// 因而这里使用异或,而不是普通整数加法。
std::vector<int> codeword(N, 0);
for (std::size_t i = 0; i < N; ++i) {
for (std::size_t j = 0; j < N; ++j) {
codeword[j] ^= (u[i] & G[i][j]);
}
}
// 这是系统编码的最终不变量:指定系统坐标必须逐位等于 message。
for (std::size_t k = 0; k < K; ++k) {
if (codeword[systemIndices[k]] != message[k]) {
throw std::logic_error("systematic-coordinate check failed");
}
}
return codeword;
}
对前面的 N=4 例子,可以这样调用:
#include <cstddef>
#include <vector>
std::vector<int> systematicPolarEncode(
const std::vector<int>& message,
std::size_t N,
const std::vector<std::size_t>& infoIndices,
const std::vector<std::size_t>& systemIndices);
const std::vector<int> message = {1, 0};
const std::vector<std::size_t> information = {2, 3};
const std::vector<std::size_t> systematic = {2, 3};
const std::vector<int> codeword =
systematicPolarEncode(message, 4, information, systematic);
// 返回 (1, 0, 1, 0),其中 c_2=1、c_3=0。
这里 information 和 systematic 的顺序都决定了 message[k] 对应哪个位置:message[0] 对应 c_2,message[1] 对应 c_3。如果只把消息比特直接写入 u_2,u_3,就会得到不同的码字,这正是系统编码需要先解线性方程的原因。
系统 Polar 码、CRC 和译码器各自解决什么问题
系统编码和 CRC 经常一起出现,但它们不是同一种机制。
| 机制 | 主要作用 | 是否增加校验约束 |
|---|---|---|
| 系统 Polar 编码 | 让信息序列在码字的指定坐标上直接可见 | 否 |
| CRC | 在消息和校验比特之间建立代数关系 | 是 |
| SCL | 保留多条候选,降低单路径判决造成的损失 | 否 |
| CA-SCL | 用 CRC 从 SCL 候选中排除不合法序列 | CRC 提供约束 |
系统化主要改变同一个线性码的表示方式。对于相同的信息位集合和相同的码线性子空间,系统编码不会凭空增加冗余;它的优点是信息比特在码字中有直接位置,便于观测和统计。CRC 则确实增加了 L_{\mathrm{CRC}} 个校验比特,并且会使进入 Polar 编码器的比特数从 A 变为 K=A+L_{\mathrm{CRC}}。
因此,比较系统 Polar 码和非系统 Polar 码时,必须说明比较的是哪一种错误:码字错误、极化输入信息位错误,还是消息向量错误。比较 SCL 和 CA-SCL 时,还要说明 CRC 长度和 L_{\max},否则码率和译码条件可能同时发生变化。
BER、BLER 和 CRC 失败不是一回事
性能曲线首先要说明统计对象。设发送的消息向量为 \underline{m}_0^{A-1},译码后得到 \hat{\underline{m}}_0^{A-1}。定义第 i 个消息向量比特是否错误:
e_i=\begin{cases}1,&\hat{m}_i\ne m_i,\\0,&\hat{m}_i=m_i\end{cases}一个消息块是否错误,则定义为
e_{\mathrm{blk}}=\begin{cases}1,&\exists i\in[0,A-1],\ \hat{m}_i\ne m_i,\\0,&\text{otherwise}\end{cases}经过 n_{\mathrm{blk}} 个消息块后,若错误比特总数为 N_{\mathrm{bit,err}},错误块数为 N_{\mathrm{blk,err}},则
\widehat{\mathrm{BER}}=\frac{N_{\mathrm{bit,err}}}{A n_{\mathrm{blk}}},\qquad \widehat{\mathrm{BLER}}=\frac{N_{\mathrm{blk,err}}}{n_{\mathrm{blk}}}BER 的统计单位是比特,BLER 的统计单位是消息块。一个错误块可能只有 1 个错误比特,也可能有很多错误比特,所以 BER 和 BLER 不会相同。
CRC 失败应单独记录。它表示候选没有通过校验,不等于消息向量一定错误,也不等于消息向量一定正确。比如译码出的消息部分恰好正确,但 CRC 部分有一位错误,此时 CRC 失败而消息向量 BLER 为 0;反过来,消息部分发生错误的候选也可能以很小的概率通过 CRC。
如果资料使用 FER,必须先确认它的统计对象和本文的消息块是否相同。只有在两者都按“一个消息块是否出错”统计时,才可以明确写出 \mathrm{FER}\equiv\mathrm{BLER}。不能把 CRC 失败次数直接当成 BER,也不能无条件把它替换成 BLER。
一个完整的统计例子
假设在某个 \rho_{\mathrm{sim}} 点发送 n_{\mathrm{blk}}=10\,000 个消息块,每个消息含 A=32 个比特。译码结果中有 20 个消息块出错,错误消息比特总数为 64 个,则
\widehat{\mathrm{BLER}}=\frac{20}{10\,000}=2\times10^{-3} \widehat{\mathrm{BER}}=\frac{64}{32\times10\,000}=2\times10^{-4}如果另外有 35 个候选没有通过 CRC,应该再记录 CRC 失败率
\widehat{P}_{\mathrm{CRC\text{-}fail}}=\frac{35}{10\,000}=3.5\times10^{-3}分子表示的事件不同。CRC 失败率比消息 BLER 高,并不能据此得出消息错误更多;其中可能包含消息正确但 CRC 位错误的情况。
性能曲线应该怎样比较
横轴通常是仿真信噪比 \rho_{\mathrm{sim}},纵轴可以是 BER 或 BLER。读图时按下面顺序检查:
- 先确认纵轴统计的是消息向量 BER、信息比特 BER,还是码字 BER;
- 再确认纵轴是 BLER 还是 CRC 失败率;
- 确认不同曲线使用相同的 N、A、L_{\mathrm{CRC}}、构造方法和 L_{\max};
- 确认横轴的单位是 dB 还是线性信噪比,并确认噪声方差定义一致;
- 最后比较同一个目标错误率下所需的 \rho_{\mathrm{sim}}。
例如,假设两条曲线在目标 BLER 10^{-2} 处分别对应 2.4\,\mathrm{dB} 和 2.8\,\mathrm{dB},前者在该实验条件下少需要 0.4\,\mathrm{dB} 的信噪比。这个结论只能说明两条曲线在给定 N、码率、构造和译码配置下的差异,不能直接推广为某个方法在所有参数下都更好。
比较时最容易出现的错误,是同时改变多个条件。例如一条曲线使用 N=128、L_{\max}=8,另一条使用 N=64、L_{\max}=4,即使前者性能更好,也不能把全部增益归因于系统 Polar 码或 CRC。一个可复现的比较表至少应包含:
| 类别 | 需要记录的参数 |
|---|---|
| 消息与码长 | A、L_{\mathrm{CRC}}、K、N、R |
| 构造 | 构造方法、\rho_{\mathrm{des}}、信息位集合 \mathcal{A} |
| 译码 | SC、SCL 或 CA-SCL、L_{\max}、LLR 量化范围 |
| 信道 | \rho_{\mathrm{sim}}、噪声方差、是否率匹配 |
| 统计 | 总块数、错误块数、错误比特数、CRC 失败次数、停止条件 |
本篇小结
Polar 码的“容量可达”是码长趋于无穷时的渐近结论,不能直接等同于短码性能。有限码长下,极化过渡区、构造条件、译码列表宽度、率匹配和统计数量都会影响曲线。
系统 Polar 码改变的是信息序列在码字中的呈现方式:它需要通过线性方程求解,让信息比特出现在指定码字坐标中。CRC 改变的是候选的合法性约束:它为 CA-SCL 提供额外的筛选依据。二者经常一起使用,但作用不同。
最后,BER 统计错误比特,BLER 统计错误消息块,CRC 失败统计校验未通过事件。只有先明确统计对象,再固定实验参数,性能曲线才具有可比较性。下一篇将讨论率匹配,解释为什么发送长度 E 不等于 Polar 母码长 N 时,接收端必须以不同方式恢复 LLR。
参考
- E. Arıkan, “Channel polarization: A method for constructing capacity-achieving codes for symmetric binary-input memoryless channels,” IEEE Transactions on Information Theory, vol. 55, no. 7, pp. 3051–3073, Jul. 2009, doi: 10.1109/TIT.2009.2021379.
- E. Arıkan, “Systematic polar coding,” IEEE Communications Letters, vol. 15, no. 8, pp. 860–862, Aug. 2011, doi: 10.1109/LCOMM.2011.061711.110862.
- K. Niu and K. Chen, “CRC-aided decoding of polar codes,” IEEE Communications Letters, vol. 16, no. 10, pp. 1668–1671, Oct. 2012, doi: 10.1109/LCOMM.2012.090312.121720.
- I. Tal and A. Vardy, “List decoding of polar codes,” IEEE Transactions on Information Theory, vol. 61, no. 5, pp. 2213–2226, May 2015, doi: 10.1109/TIT.2015.2410251.
- Y. Polyanskiy, H. V. Poor, and S. Verdú, “Channel coding rate in the finite blocklength regime,” IEEE Transactions on Information Theory, vol. 56, no. 5, pp. 2307–2359, May 2010, doi: 10.1109/TIT.2010.2043769.
- 牛凯,《极化码原理与应用》,科学出版社,2021,ISBN 978-7-03-071269-1。