尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

极化码译码算法拆解:SC、SCL、SSC实现与调参方法

极化码译码算法拆解:SC、SCL、SSC实现与调参方法 简介极化码Polar Code三种经典译码算法——SC、SSC、SCL的MATLAB实现源码包面向通信与编码领域研究者、研究生或工程师帮助快速掌握从基础逐位判决到列表搜索的译码逻辑。压缩包共34个文件包含14个m脚本、8个fig图像、7个txt说明、1个docx文档及md/pdf等辅助材料整体约287KB便于对照代码与文档系统学习。已有897人学习下载。资源内置main函数可模拟AWGN信道支持切换不同译码算法与参数如列表长度L、信噪比SNR并配有树构建、概率更新等关键子程序方便读者研究性能与复杂度权衡也可作为二次开发的基础框架。1. polar-code-master 里的三个译码器SC、SSC、SCL 的分工与常见误解拿到 polar-code-master 工程最直观的入口是 main.m 里那排调用polar_SC_decode.m、polar_SCL_decode.m、polar_SSC_decode.m外加 pencode.m 编码、constructedPolarCode 做信道可靠性排序。三者都在 AWGN 信道下用 BPSK 仿真目标完全不同SC 是递归基线SSC 不改变误码率却能把递归树剪掉一半以上SCL 用列表路径把中短码性能拉到接近最大似然。适合做极化码课程实验、毕设或对比译码复杂度的人。注意题面把 SSC 写成 Successive Cancellation List 是笔误SSC 全称是 Simplified Successive Cancellation简化串行抵消SCL 才是 Successive Cancellation List。这个区分不搞清楚读 polar_SSC_decode.m 会越看越乱。2. SC译码算法拆解f/g 函数递归、LLR 传递与错误传播的上限2.1 为什么 SC 是串行的LLR 递归的两条基本规则SC 译码的本质是把长度 N 的码字按极化结构递归二分先译左半子码再把左半判决结果当作条件信息参与右半子码的译码。所谓 Successive CancellationCancellation 指的就是在 g 函数里用已译比特把干扰从右子码中减掉。设父节点收到的对数似然比LLR向量为 [a, b]a 对应左子节点b 对应右子节点则两个子节点的输入分别为左子节点f(a, b) sign(a)·sign(b)·min(|a|, |b|)。这是两个比特异或操作在 min-sum 近似下的 LLR 更新规则硬件实现里几乎都这么写。右子节点g(a, b, û) (1−2û)·a b其中 û 是左子码的判决结果。û0 时保留 a 的极性û1 时把 a 反相再与 b 相加实现干扰抵消。这两个函数在 polar_SC_decode.m、polar_SCL_decode.m、polar_SSC_decode.m 里是共用的基础单元。我建议动手前先单独验证 f/g给一组已知 LLR手工算一遍期望输出再和函数比对。很多译码器看起来能跑但 BER 曲线下不去多半是 f 函数里的 min 写成了 max或 g 函数忘了乘 (1−2û)。这个自测只要十分钟能省掉后面排查的大把时间。2.2 polar_SC_decode.m 的递归主体与叶子判决polar_SC_decode.m 的常见实现是递归函数叶子节点做硬判决内部节点只做消息传递。核心骨架如下function u sc_decode_node(llr, idx, len, frozenMask) % llr : 当前节点收到的对数似然比向量 % idx : 当前节点在原始码字里的起始位置 % len : 节点长度递归出口为 len1 % frozenMask: 逻辑向量1 表示冻结位0 表示信息位 if len 1 if frozenMask(idx) % 冻结位直接判 0 u 0; else % 信息位按 LLR 符号硬判决 u double(llr 0); end return; end half len / 2; a llr(1:half); b llr(half1:end); % 必须先译左子右子的 g 函数需要左子的判决结果 uL sc_decode_node(f_func(a, b), idx, half, frozenMask); % 右子g 函数把左子判决作为先验信息参与干扰抵消 uR sc_decode_node(g_func(a, b, uL), idx half, half, frozenMask); u [uL, uR]; end function y f_func(a, b) y sign(a) .* sign(b) .* min(abs(a), abs(b)); end function y g_func(a, b, uL) y (1 - 2 * uL) .* a b; end关键在递归顺序左子的 sc_decode_node 调用必须完整返回后右子才能开始因为 g 函数的输入 uL 是左子全部叶子的硬判决结果这一步没法并行。叶子判决用 llr 0 是因为收发约定 BPSK 映射 0→1、1→−1LLR 为负说明收到 −1 的可能性更大判为比特 1。整体复杂度 O(N log N)N 是码长每次递归都新建 a、b 两个临时向量MATLAB 里内存峰值大约 2N log2(N) 量级N 到 32768 以上要注意内存占用。提示f 函数里 min 前的符号位不能省sign(a)·sign(b) 表示两比特异或的极性关系漏掉符号位等于把异或变成了同或整个译码方向就反了。2.3 SC 的天花板错误传播与三种译码器的定位SC 的软肋是错误传播一旦某个信息位判决错误g 函数会把这个错误当作先验传给右子导致后面一串位跟着错。信道极化在码长趋近无穷时能抑制这种效应但在中短码长下错误传播直接限制 BER 性能。SSC 和 SCL 的出发点完全不同算法BER 性能时延存储/计算复杂度定位SC基线高严格逐位串行O(N log N)理论基准、长码场景SSC与 SC 逐点相同低剪枝跳过纯节点O(N log N)常数更小低时延硬件实现SCL优于 SCL 越大越好高维护 L 条路径O(L·N log N)中短码逼近最大似然从表能看出SSC 不是 SC 的增强版而是加速版真正改变性能的是 SCL。这也解释了为什么包里还带了 polar_BP_decode.m 和 polar_SCAN_decode.m——BP置信传播和 SCAN软消除是另外两条软输出路线arctanhTanhPlusTanh.m 是它们共用的 tanh 域运算工具用在因子图的水平/垂直更新里。把 SC 这章的递归和消息传递吃透后面看 SSC 和 SCL 就是在这套骨架上做加减法。3. SCL译码算法实现路径度量 PM 更新、列表裁剪与 CRC 辅助判决3.1 路径度量 PMSCL 比 SC 多出来的那本账SCL 的核心改动是把每次只留一条路径变成同时保留 L 条候选路径。每遇到一个信息位每条路径分裂成 u0 和 u1 两个分支路径数翻倍超过 L 时按路径度量Path Metric, PM排序裁剪。PM 越小代表该路径越可信递推公式是PM_new PM ln(1 exp(−(1−2u)·α))其中 u 是当前位候选值α 是该位的 LLR。浮点仿真里这个公式容易溢出更常用的是近似式PM_new ≈ PM max(0, −(1−2u)·α)近似式的含义很直观候选分支与硬判决方向一致时 PM 不增长不一致时惩罚量等于 |α|。所以 PM 累积的是这条路径偏离最可能路径的总代价。冻结位不分裂直接按 u0 更新 PM。这个递推是 polar_SCL_decode.m 里最核心的数值逻辑符号写反会导致列表排序完全反掉。3.2 polar_SCL_decode.m分裂、排序、裁剪三步循环polar_SCL_decode.m 的实现骨架如下为可读性省略了 CRC 和中间 LLR 的路径复制细节function uhat polar_SCL_decode(llr, N, frozenMask, L) % path 结构: .u 已判比特, .pm 路径度量, .llr 当前节点的 LLR paths(1).u zeros(1, N); paths(1).pm 0; paths(1).llr llr(:); for idx 1:N cand []; for p 1:length(paths) if frozenMask(idx) % 冻结位只有 0 分支 cand [cand, extendPath(paths(p), 0, paths(p).pm)]; else % 信息位分裂成 0/1 两个分支 pm0 paths(p).pm max(0, -llrOfBit(paths(p), idx)); pm1 paths(p).pm max(0, llrOfBit(paths(p), idx)); cand [cand, extendPath(paths(p), 0, pm0), ... extendPath(paths(p), 1, pm1)]; end end [~, ord] sort([cand.pm]); % 按 PM 升序排 paths cand(ord(1:min(L, length(ord)))); % 只留 L 条 end uhat paths(1).u; % PM 最小的路径 end这段代码有两处容易写错。第一llrOfBit 不是直接取值而是要在该路径自己的译码树上用 f/g 函数一路传播到叶子位每条路径的中间 LLR 都不同因为 g 函数的输入依赖该路径自己的 û。真正的实现需要为每条路径维护逐层节点的左右消息数组我这里只是示意。第二排序对象必须是扩展后的全部候选最多 2L 条排序后立刻裁剪如果先每轮只留最优的一半再分裂性能会退化成一个奇怪的 SC 变体。MATLAB 里 [~, ord] sort(...) 是标配写法硬件实现则用比较器网络替代排序。内存开销按 O(L·N log N) 增长L32、N1024 时路径复制产生的中间数组已经相当可观所以工程里常用路径复制时只拷贝本次需要修改的分支的优化。3.3 列表长度 L 怎么选从 1 到 32 的收益曲线列表长度 L 是 SCL 唯一的超参数。下表给出 N256、码率 1/2、Eb/N0≈1.5 dB 下常见仿真结果的量级示意不是本包自带数据用来理解趋势L相对 SC 的增益复杂度倍率适用判断1无退化为 SC1×基线对照4约 0.3~0.5 dB~4×快速验证8接近收益拐点~8×课程实验推荐32逼近 ML 下界~32×追求极致性能从 L1 到 L8 是收益最陡的一段L8 之后边际收益明显变小复杂度却线性上涨。工程上还有个惯例SCL 必须配 CRC 外码才有性价比。polar_SCL_decode.m 输出 PM 最小的路径前先按 CRC 校验逐条检查候选路径通过 CRC 的第一条才是正式输出。注意没有 CRC 的 SCL 在低信噪比下可能选出一条 PM 最小但信息位几乎全错的路径这是列表译码的已知现象不是实现 bug。检索时有人把包名写成 polarsc或把 SCL 打成 SCl字母 i 和 l 不分对应文件就是 polar_SCL_decode.m别下错。4. SSC译码算法rate-0/rate-1 节点剪枝策略与 intial_tree_G 树构建4.1 SSC 剪枝的本质不改变判决只跳过冗余递归SSC 常被误读为SC 的改进版但它对 BER 没有任何提升——判决规则和 SC 完全一致唯一区别是跳过那些不需要递归的子树。极化码的递归树上有些节点的叶子全部是冻结位它们在 SC 里也要一层层跑 f/g最终输出全 0有些节点的叶子全部是信息位它们在 SC 里要一层层算到叶子再做硬判决。SSC 把这两类节点直接坍缩rate-0 节点子树内全是冻结位直接输出长度 len 的全 0 向量O(1) 完成。rate-1 节点子树内全是信息位对节点收到的 LLR 向量一次性硬判决llr 0O(len) 完成。混合节点既有冻结位又有信息位才继续按 f/g 规则递归左右子树。实际效果是递归深度大幅下降。N1024 且信道状态较好时大部分子树被极化成了纯净节点真正递归的只剩少数混合节点。这也是 SSC 适合硬件流水线的原因纯节点可以用组合逻辑一次性算完不占用额外时钟周期。4.2 intial_tree_G.m先建树再译码polar_SSC_decode.m 依赖 intial_tree_G.m 预构建的译码树注意包内文件名拼写是 intial作者笔误不影响运行。每个节点记录起点、长度和类型function node buildSSCNode(start, len, frozenMask) % 返回节点结构.start 起点, .len 长度, .type 节点类型 node.start start; node.len len; seg frozenMask(start : start len - 1); if all(seg 1) % 全冻结 node.type 0; % rate-0 节点 elseif all(seg 0) % 全信息 node.type 1; % rate-1 节点 else node.type 2; % 混合节点继续递归 half len / 2; node.left buildSSCNode(start, half, frozenMask); node.right buildSSCNode(start half, half, frozenMask); end end注意这里的坍缩按任意长度的连续片段是否纯净判断不只处理叶子节点。all(seg 1) 判断整段是否全冻结比逐叶子标记更省事。构建复杂度 O(N)。每次切换 N 或 KconstructedPolarCode 会重新生成 frozenMask树也必须重建复用旧树最典型的症状是索引越界或者误码率高得反常。4.3 polar_SSC_decode.m 的递归译码polar_SSC_decode.m 的主体比 SC 还短因为纯节点不用进入 f/g 递归function u ssc_decode(node, llr) switch node.type case 0 % rate-0全冻结直接补零 u zeros(node.len, 1); case 1 % rate-1一次性硬判决 u double(llr 0); otherwise % 混合节点走 f/g 递归 half node.len / 2; a llr(1:half); b llr(half1:end); uL ssc_decode(node.left, f_func(a, b)); uR ssc_decode(node.right, g_func(a, b, uL)); u [uL; uR]; end endf_func 和 g_func 与第 2 章完全相同所以 SSC 的 BER 和 SC 逐点一致——前提是 rate-0/rate-1 的判定没写反。最容易犯的错是把 all(seg 1) 写成 any(seg 1)那会把混合节点误判成 rate-1直接硬判决覆盖掉真实信息位BER 立刻崩坏。验证方法是跑一组对比SSC 与 SC 的 BER 曲线应当完全重合若出现差异优先检查类型判定。节点类型与复杂度对照节点类型判定条件译码操作复杂度rate-0片段内全为冻结位输出全 0O(1)rate-1片段内全为信息位对 LLR 硬判决O(len)混合节点冻结位和信息位都有f/g 递归左右子树O(len log len)5. main.m 仿真验证与调参AWGN 主循环、LLR 缩放和三个必踩的坑5.1 main.m 的主循环与 LLR 缩放polar-code-master 的 main.m 把整条链路串起来构造极化码、编码、BPSK 调制、加 AWGN、译码、统计 BER。常见骨架如下N 256; K 128; rate K / N; EbN0_dB 1.5; sigma sqrt(1 / (2 * rate * 10^(EbN0_dB/10))); % BPSK 的噪声标准差 [infoIdx, frozenIdx] constructedPolarCode(N, K, 0.5); % 设计信噪比 0.5 dB frozenMask true(N, 1); frozenMask(infoIdx) false; % 由构造结果生成掩码 u zeros(N, 1); u(infoIdx) randi([0 1], K, 1); x pencode(u, N); % (N,K) 极化码编码 rx (1 - 2*x) sigma * randn(N, 1); % BPSK: 0-1, 1--1 llr 2 * rx / sigma^2; % AWGN 下精确 LLR switch algo case SC, uhat polar_SC_decode(llr, N, frozenMask); case SCL, uhat polar_SCL_decode(llr, N, frozenMask, 8); case SSC, uhat ssc_decode(tree, llr); end两点要解释。第一llr 2rx/σ² 是 AWGNBPSK 下的精确对数似然比SC/SSC 只取符号所以系数无所谓但 SCL 的 PM 递推用到 LLR 的绝对值系数虽不影响排序结果却影响近似式 max(0,...) 与精确公式的误差。第二constructedPolarCode 的第三个参数是设计信噪比决定可靠性排序扫 Eb/N0 曲线时代码里常把它固定不跟着扫描点走。5.2 三个必踩的坑第一个坑是冻结位集合。constructedPolarCode 按巴氏参数或高斯近似给 N 个位置排序可靠性最高的 K 位才是信息位不要自己写死 infoIdx 1:K。对中短码来说这等于放弃极化增益BER 曲线会比正确构造差好几个 dB。第二个坑是 SCL 的数值稳定性PM 精确公式里 exp 在 LLR 绝对值很大时趋近于 0log 里出现 0 会得到 -Inf浮点上表现为 NaN改用近似式 PM max(0, -(1-2u)·α) 最省事。第三个坑是随机种子和仿真帧数rng 不固定则曲线不可复现BER 要到 1e-4 量级至少需要 1e5 帧以上只跑几百帧画出的瀑布区是噪声的瀑布区。另外根目录的 ~$lar码基本原理v1.docx 是 Word 残留临时文件11.m、22.m 是作者调试脚本polar-factor.jpg 是因子图调试 BP 时对照着看其余文件不影响仿真。5.3 用 SC 当标尺的交叉验证一个非常实用的自检方法是把 SC 当作标准答案来校验另外两个译码器% 自检 1SCL 在 L1 时必须退化为 SC ber_sc runSim(SC, 0, 256); ber_scl runSim(SCL, 1, 256); assert(abs(ber_sc - ber_scl) 1e-12, L1 的 SCL 与 SC 不一致); % 自检 2SSC 的 BER 必须与 SC 逐点相同 ber_ssc runSim(SSC, 0, 256); assert(abs(ber_sc - ber_ssc) 1e-12, SSC 与 SC 不一致检查节点类型判定);这两条断言每次改完译码器先跑一遍再滚 BER 曲线。L1 的 SCL 理论上完全等同于 SCSSC 与 SC 逐点相同任何一条不满足都说明分支管理或 f/g 函数有 bug。把断言放进 main.m 的调试开关里比肉眼比对曲线可靠得多也方便后续加 BP、SCAN 对照时快速定位是新增代码的问题还是老译码器回归。本文还有配套的精品资源点击获取
返回列表