告别天书:用Python手把手实现卷积码的硬判决Viterbi译码(附完整代码)
从零实现卷积码Viterbi译码:Python实战与核心原理剖析
在数字通信系统中,错误控制编码是确保信息可靠传输的关键技术。卷积码作为一种经典的前向纠错码,以其良好的纠错性能和适中的实现复杂度,被广泛应用于卫星通信、移动通信和深空通信等领域。而Viterbi算法则是卷积码译码中最常用的算法之一,它通过动态规划的思想,在网格图上寻找最可能的发送序列。本文将带您从零开始,用Python完整实现一个(2,1,2)卷积码的硬判决Viterbi译码器,并深入解析算法背后的数学原理和工程实现技巧。
1. 卷积码与Viterbi算法基础
卷积码是一种具有记忆特性的纠错编码,其编码输出不仅与当前输入有关,还与之前的输入相关。一个(n,k,m)卷积码表示每输入k个比特,输出n个比特,编码器的记忆长度为m。我们以(2,1,2)卷积码为例,其编码器结构通常包含:
- 1个输入比特
- 2个输出比特
- 2位移位寄存器
卷积码的核心参数 :
| 参数 | 描述 | 示例值 |
|---|---|---|
| n | 每时钟周期输出比特数 | 2 |
| k | 每时钟周期输入比特数 | 1 |
| m | 编码器约束长度 | 2 |
| R | 码率 (k/n) | 1/2 |
Viterbi算法是一种基于最大似然准则的译码算法,其核心思想可以概括为:
- 分支度量计算 :计算接收序列与可能发送序列之间的距离
- 路径度量累积 :在网格图的每个状态保留最优路径
- 回溯解码 :从最终状态回溯找出全局最优路径
提示:硬判决Viterbi算法使用汉明距离作为度量标准,而软判决则使用欧氏距离,通常能获得约2dB的编码增益。
2. 编码器实现与网格图构建
我们先实现卷积码的编码器,这是理解译码过程的基础。一个(2,1,2)卷积码通常由两个生成多项式定义,例如g0=7(八进制)=111(二进制),g1=5(八进制)=101(二进制)。
class ConvolutionalEncoder:
def __init__(self, constraint_length=3, generators=[0b111, 0b101]):
self.constraint_length = constraint_length
self.generators = generators
self.state = 0 # 初始状态为全0
def encode_bit(self, bit):
"""编码单个输入比特"""
self.state = ((self.state << 1) | bit) & ((1 << (self.constraint_length-1)) - 1)
output = []
for gen in self.generators:
# 计算生成多项式与状态的乘积
product = gen & (self.state | (bit << (self.constraint_length-1)))
# 计算奇偶校验(模2和)
parity = bin(product).count('1') % 2
output.append(parity)
return output
def encode(self, bits):
"""编码整个比特序列"""
encoded = []
for bit in bits:
encoded.extend(self.encode_bit(bit))
# 添加尾比特使状态归零
for _ in range(self.constraint_length-1):
encoded.extend(self.encode_bit(0))
return encoded
网格图状态转移示例 :
| 当前状态 | 输入 | 下一状态 | 输出 |
|---|---|---|---|
| 00 | 0 | 00 | 00 |
| 00 | 1 | 10 | 11 |
| 10 | 0 | 01 | 10 |
| 10 | 1 | 11 | 01 |
| 01 | 0 | 00 | 11 |
| 01 | 1 | 10 | 00 |
| 11 | 0 | 01 | 01 |
| 11 | 1 | 11 | 10 |
3. Viterbi译码核心算法实现
Viterbi译码器的实现可以分为三个主要部分:分支度量计算、路径度量累积与比较、幸存路径存储与回溯。
3.1 分支度量计算
在硬判决译码中,我们使用汉明距离作为分支度量:
def hamming_distance(a, b):
"""计算两个比特序列的汉明距离"""
return sum(x != y for x, y in zip(a, b))
3.2 路径度量累积与比较
这是Viterbi算法的核心部分,我们需要在网格图的每个状态保留最优路径:
def viterbi_decode(received, constraint_length=3, generators=[0b111, 0b101]):
num_states = 1 << (constraint_length-1)
path_metrics = [float('inf')] * num_states
path_metrics[0] = 0 # 初始状态度量
# 存储每个状态的幸存路径
survivor_paths = [[] for _ in range(num_states)]
survivor_paths[0] = [0] # 初始状态路径
# 分组处理接收序列(每组n比特)
n = len(generators)
for i in range(0, len(received), n):
current_r = received[i:i+n]
new_path_metrics = [float('inf')] * num_states
new_survivor_paths = [[] for _ in range(num_states)]
for state in range(num_states):
if path_metrics[state] == float('inf'):
continue
# 尝试输入0和1
for input_bit in [0, 1]:
# 计算下一状态
next_state = ((state << 1) | input_bit) & (num_states - 1)
# 模拟编码器输出
encoder = ConvolutionalEncoder(constraint_length, generators)
encoder.state = state
output = encoder.encode_bit(input_bit)
# 计算分支度量
branch_metric = hamming_distance(current_r, output)
total_metric = path_metrics[state] + branch_metric
# 更新路径度量
if total_metric < new_path_metrics[next_state]:
new_path_metrics[next_state] = total_metric
new_survivor_paths[next_state] = survivor_paths[state] + [input_bit]
path_metrics = new_path_metrics
survivor_paths = new_survivor_paths
# 回溯找到最优路径
best_state = path_metrics.index(min(path_metrics))
decoded_bits = survivor_paths[best_state][1:] # 去掉初始状态
# 去掉尾比特(编码时添加的归零比特)
return decoded_bits[:-(constraint_length-1)]
3.3 路径存储优化技巧
在实际实现中,直接存储整个路径会消耗大量内存。更高效的方法是:
- 只存储前一个状态和当前输入比特
- 使用指针数组来跟踪路径
- 实现滑动窗口式的回溯,减少存储需求
# 优化后的路径存储结构示例
class PathHistory:
def __init__(self, traceback_length, num_states):
self.traceback_length = traceback_length
self.num_states = num_states
self.pointer = 0
self.history = [ [None]*num_states for _ in range(traceback_length) ]
def add_step(self, decisions):
"""记录每个状态的决策"""
for state in range(self.num_states):
self.history[self.pointer][state] = decisions[state]
self.pointer = (self.pointer + 1) % self.traceback_length
def get_path(self, final_state):
"""回溯获取解码路径"""
path = []
current_state = final_state
for i in range(self.traceback_length):
ptr = (self.pointer - 1 - i) % self.traceback_length
decision = self.history[ptr][current_state]
if decision is None:
break
input_bit, prev_state = decision
path.append(input_bit)
current_state = prev_state
return path[::-1]
4. 完整系统仿真与性能测试
现在我们将编码器、信道模型和译码器组合起来,构建一个完整的仿真系统:
import random
def simulate_bsc_channel(bits, error_prob):
"""模拟二元对称信道"""
return [bit if random.random() > error_prob else 1-bit for bit in bits]
def test_viterbi():
# 测试用例
input_bits = [1, 0, 1, 1, 0, 0, 1]
encoder = ConvolutionalEncoder()
encoded = encoder.encode(input_bits)
# 添加信道噪声(10%错误概率)
received = simulate_bsc_channel(encoded, 0.1)
# 解码
decoded = viterbi_decode(received)
print("原始信息:", input_bits)
print("编码输出:", encoded)
print("接收序列:", received)
print("解码结果:", decoded)
print("解码正确:", decoded == input_bits)
def measure_performance():
"""测量不同信噪比下的误码率"""
test_bits = [random.randint(0,1) for _ in range(1000)]
encoder = ConvolutionalEncoder()
encoded = encoder.encode(test_bits)
error_rates = []
for p in [0.01, 0.05, 0.1, 0.15, 0.2]:
errors = 0
trials = 100
for _ in range(trials):
received = simulate_bsc_channel(encoded, p)
decoded = viterbi_decode(received)
errors += sum(a != b for a,b in zip(test_bits, decoded))
ber = errors / (len(test_bits) * trials)
error_rates.append((p, ber))
print("信道错误概率 vs 解码后误码率:")
for p, ber in error_rates:
print(f"{p:.2f} -> {ber:.6f}")
性能测试结果示例 :
| 信道错误概率 | 解码后误码率 |
|---|---|
| 0.01 | 0.000020 |
| 0.05 | 0.000150 |
| 0.10 | 0.001200 |
| 0.15 | 0.005800 |
| 0.20 | 0.018000 |
注意:实际性能会因编码器的生成多项式选择、约束长度以及信道特性而有所不同。通常约束长度越长,纠错能力越强,但实现复杂度也呈指数增长。
5. 工程实现中的优化技巧
在实际工程应用中,Viterbi译码器还需要考虑以下优化:
5.1 度量归一化
为防止路径度量值持续增长导致溢出,可以采用定期归一化:
def normalize_metrics(metrics):
"""归一化路径度量"""
min_metric = min(metrics)
return [m - min_metric for m in metrics]
5.2 滑动窗口译码
对于长序列,可采用滑动窗口技术减少存储需求:
- 设置固定长度的回溯窗口(通常5-7倍约束长度)
- 窗口满时进行部分判决
- 移动窗口继续处理后续数据
5.3 量化与定点运算
硬件实现中常使用定点数来优化:
- 分支度量用3-5比特量化
- 路径度量用8-16比特表示
- 定期进行饱和处理防止溢出
量化实现示例 :
def quantized_hamming_distance(a, b, scale=3):
"""量化汉明距离计算"""
distance = sum(x != y for x, y in zip(a, b))
return min(distance * scale, (1 << 5) - 1) # 5比特饱和
5.4 并行化处理
现代处理器可以利用SIMD指令并行计算多个状态:
# 使用numpy向量化计算示例
import numpy as np
def vectorized_viterbi_step(received_sym, path_metrics, transition_table):
"""向量化的Viterbi步骤"""
# transition_table包含所有可能的状态转移和输出
# 计算所有可能的分支度量
all_outputs = transition_table['outputs']
branch_metrics = np.sum(all_outputs != received_sym, axis=1)
# 计算所有可能的新路径度量
new_metrics = path_metrics[transition_table['from_states']] + branch_metrics
# 对每个目标状态选择最小度量
num_states = len(path_metrics)
min_metrics = np.full(num_states, np.inf)
best_paths = np.full(num_states, -1)
for state in range(num_states):
mask = transition_table['to_states'] == state
if np.any(mask):
idx = np.argmin(new_metrics[mask])
min_metrics[state] = new_metrics[mask][idx]
best_paths[state] = transition_table['transitions'][mask][idx]
return min_metrics, best_paths
6. 算法扩展与变种
基础Viterbi算法可以扩展为多种变体以适应不同场景:
6.1 软判决Viterbi译码
使用信道输出的软信息(如似然比)代替硬判决:
- 分支度量改用欧氏距离或相关度量
- 通常可获得2-3dB的编码增益
- 实现复杂度略高于硬判决
软判决分支度量示例 :
def soft_branch_metric(received_soft, expected):
"""软判决分支度量计算"""
# received_soft是信道的软输出(如:+1.2, -0.8等)
# expected是期望的发送符号(BPSK映射:0->+1, 1->-1)
return sum((r - e)**2 for r, e in zip(received_soft, expected))
6.2 列表Viterbi算法
保留多条候选路径而非单一最优路径:
- 提高低信噪比条件下的性能
- 可用于级联编码系统的迭代解码
- 复杂度随列表大小线性增加
6.3 自适应Viterbi算法
根据信道条件动态调整算法参数:
- 可变回溯长度
- 动态度量归一化频率
- 自适应量化精度
7. 实际应用案例与调试技巧
在实现Viterbi译码器时,经常会遇到一些典型问题。以下是几个实际调试经验:
常见问题1:尾比特处理不当
症状:解码结果的最后几位总是错误 解决方法:确保编码器添加了足够的尾比特使状态归零,解码时去掉这些尾比特
常见问题2:度量溢出
症状:高信噪比时突然出现大量错误 解决方法:实现度量归一化,定期减去最小度量值
常见问题3:路径存储不足
症状:解码性能随序列长度增加而下降 解决方法:增加回溯长度或实现滑动窗口机制
调试技巧 :
- 从小例子开始:使用短输入序列(如3-5比特)手工验证
- 可视化网格图:打印每个时刻的状态和路径度量
- 单元测试:为每个组件(分支度量、路径更新等)编写独立测试
- 黄金参考:与已知正确的实现或理论结果对比
def debug_trace(viterbi_decoder, received):
"""调试函数:打印Viterbi解码的详细过程"""
print("=== Viterbi解码调试跟踪 ===")
print(f"接收序列: {received}")
# 这里可以添加具体的调试打印逻辑
# 例如打印每个时刻的状态度量、幸存路径等
decoded = viterbi_decoder(received)
print(f"解码结果: {decoded}")
return decoded
在卫星通信系统中,Viterbi译码器的实现通常需要考虑:
- 极低的信噪比条件(深空通信Eb/N0可能低于0dB)
- 严格的实时性要求(高数据速率)
- 有限的硬件资源(航天器上的FPGA资源受限)
一个实际的优化案例是NASA在深空探测器中使用的Viterbi译码器,它采用了:
- 8级流水线结构提高吞吐量
- 混合滑动窗口策略平衡延迟和存储
- 自适应量化策略适应动态信道条件
- 选择性状态更新减少功耗
更多推荐

所有评论(0)