AnyMath 文档
Notebook

抗噪声编码。

在数字通信和数据存储中,干扰是不可避免的,这会使传输的信息失真。 为了对抗错误,使用抗噪声编码-为原始消息添加冗余,这使得在接收端检测甚至纠正错误成为可能。

在本文中,我们将在实践中分析两种经典算法。:

-**汉明码(7,4)**是一个简单的块代码,可以纠正7位块中的单个错误。
-速度为1/2,极限长度为3的卷积码是维特比算法解码的带内存的连续码。

所有示例都是使用内置库使用自动化测试实现的 Test. 您不仅可以看到代码,还可以验证其正确性。

Test –这是一个标准库。 它为编写单元测试提供了简单的宏。:

  • @test -检查表达式是否返回 true.
  • @testset -组测试,给他们的名字,并输出摘要。
  • @test_throws -检查是否抛出异常。
  • @test_broken / @test_skip -对于暂时中断或错过的测试。

在我们的代码中,我们积极地使用 @testset 和 @test 以在各种示例上自动测试编码器和解码器的操作。 这使您能够快速定位错误并对算法的性能充满信心。

In [ ]:
using Test

汉明代码

汉明码(7,4)将4个信息比特转换成7比特码字。 添加3个验证位,使用公式计算:

p1 = d1 ⊕ d2 ⊕ d4
p2 = d1 ⊕ d3 ⊕ d4
p3 = d2 ⊕ d3 ⊕ d4

代码字按以下顺序形成: [p1, p2, d1, p3, d2, d3, d4]. 这种放置允许您立即根据综合征(三次奇偶校验的结果)确定错误位的数量。

@assert-验证条件的有效性。 如果条件为false,则会引发错误。 AssertionError 与指定的消息(如果它已被传输)。

In [ ]:
function hamming_encode(data)
    @assert length(data) == 4 "Для кода (7,4) требуется ровно 4 бита данных"
    d1, d2, d3, d4 = data
    
    p1 = xor(d1, d2, d4)
    p2 = xor(d1, d3, d4)
    p3 = xor(d2, d3, d4)
    
    return [p1, p2, d1, p3, d2, d3, d4]
end
Out[0]:
hamming_encode (generic function with 1 method)

解码时,计算出该综合征 [s1, s2, s3]:

s1 = p1 ⊕ d1 ⊕ d2 ⊕ d4
s2 = p2 ⊕ d1 ⊕ d3 ⊕ d4
s3 = p3 ⊕ d2 ⊕ d3 ⊕ d4

电话号码 error_pos = s1*1 + s2*2 + s3*4 指示错误位的位置(从1到7)。 如果 error_pos != 0,反转该位置处的比特,然后提取信息比特。

In [ ]:
function hamming_decode(received)
    @assert length(received) == 7 "Принятое слово должно иметь длину 7"
    p1, p2, d1, p3, d2, d3, d4 = received
    
    s1 = xor(p1, d1, d2, d4)
    s2 = xor(p2, d1, d3, d4)
    s3 = xor(p3, d2, d3, d4)
    
    error_pos = s1*1 + s2*2 + s3*4
    corrected = copy(received)
    if error_pos != 0
        corrected[error_pos] = xor(corrected[error_pos], 1)
    end
    
    data = [corrected[3], corrected[5], corrected[6], corrected[7]]
    return data, corrected, error_pos
end
Out[0]:
hamming_decode (generic function with 1 method)

下面的测试验证了汉明代码(7,4)在所有可能的16个输入数据变体(从0到15)和所有可能的单个错误位置(从1到7)上的正确性。 对于每个输入向量:

  1. 将其编码成代码字。
  2. 验证解码返回原始数据、代码字本身和错误位置0而不失真。
  3. 然后,对于7个位置中的每一个位置,人为地反转码字中的一个位并解码失真的版本:
    -数据必须与原始数据匹配。,
    -修正后的字必须与原始代码字相匹配,
    -错误的具体位置必须与输入错误的编号相匹配。
In [ ]:
@testset "Код Хэмминга (7,4) — все комбинации" begin
    for n in 0:15
        data = [Int((n >> i) & 1) for i in 0:3]
        codeword = hamming_encode(data)
        decoded, corrected, err_pos = hamming_decode(codeword)
        @test decoded == data
        @test corrected == codeword
        @test err_pos == 0
        
        for pos in 1:7
            noisy = copy(codeword)
            noisy[pos] = xor(noisy[pos], 1)
            decoded, corrected, err_pos = hamming_decode(noisy)
            @test decoded == data
            @test corrected == codeword
            @test err_pos == pos
        end
    end
end;
Test Summary:                       | Pass  Total  Time
Код Хэмминга (7,4) — все комбинации |  384    384  0.1s

共有384项测试-全部成功通过。 代码保证纠正块中的任何单个错误并返回原始数据。

卷积码

与块码不同,卷积码与连续的比特流一起工作。 它使用长度的移位寄存器 K=3 (状态由前两位确定)和两个生成多项式(二进制形式 111 和 101,其对应于八进制7和5)。

为每个输入位生成两个输出位。:

out1 = reg[1] ⊕ reg[2] ⊕ reg[3]
out2 = reg[1] ⊕ reg[3]

要完成代码,将以下内容添加到序列的末尾 K-1 = 2 零位来清除寄存器。

In [ ]:
const K = 3
const G1 = [1,1,1]
const G2 = [1,0,1]

function encode_conv(data)
    tail = zeros(Int, K-1)
    input = [data; tail]
    reg = zeros(Int, K)
    output = Int[]
    
    for bit in input
        reg = [bit; reg[1:end-1]]
        out1 = xor(reg[1] & G1[1], reg[2] & G1[2], reg[3] & G1[3])
        out2 = xor(reg[1] & G2[1], reg[2] & G2[2], reg[3] & G2[3])
        push!(output, out1, out2)
    end
    return output
end
Out[0]:
encode_conv (generic function with 1 method)

维特比解码器利用动态编程原理找到最可能的传输比特序列。 对于每个时间点(接收位对),我们计算所有可能状态(有4个)的度量(汉明距离),并记住最佳转换。 最后,我们选择具有最小度量的路径,并在反向传递中恢复位。

In [ ]:
function decode_conv(received)
    n_steps = length(received) ÷ 2
    num_states = 2^(K-1)
    INF = 10^9
    metrics = fill(INF, num_states)
    metrics[1] = 0
    
    prev_state = Vector{Vector{Int}}(undef, n_steps)
    prev_bit   = Vector{Vector{Int}}(undef, n_steps)
    
    for step in 1:n_steps
        r1 = received[2*(step-1)+1]
        r2 = received[2*(step-1)+2]
        new_metrics = fill(INF, num_states)
        prev_state[step] = zeros(Int, num_states)
        prev_bit[step]   = zeros(Int, num_states)
        
        for curr_state in 0:(num_states-1)
            if metrics[curr_state+1] == INF
                continue
            end
            for bit in 0:1
                s1 = (curr_state >> 1) & 1
                s2 = curr_state & 1
                next_state = bit*2 + s1
                reg = [bit, s1, s2]
                out1 = xor(reg[1]&G1[1], reg[2]&G1[2], reg[3]&G1[3])
                out2 = xor(reg[1]&G2[1], reg[2]&G2[2], reg[3]&G2[3])
                dist = (out1 != r1) + (out2 != r2)
                new_metric = metrics[curr_state+1] + dist
                
                if new_metric < new_metrics[next_state+1]
                    new_metrics[next_state+1] = new_metric
                    prev_state[step][next_state+1] = curr_state
                    prev_bit[step][next_state+1] = bit
                end
            end
        end
        metrics = new_metrics
    end
    
    final_state = findmin(metrics)[2] - 1
    bits = Int[]
    state = final_state
    for step in n_steps:-1:1
        bit = prev_bit[step][state+1]
        push!(bits, bit)
        state = prev_state[step][state+1]
    end
    reverse!(bits)
    
    data_len = n_steps - (K-1)
    return bits[1:data_len]
end
Out[0]:
decode_conv (generic function with 1 method)

下面的测试验证了在长度为5到15位的10个随机序列上,速度为1/2且限制长度为K=3的卷积码的操作。 对于每个序列:

  1. 使用以下方法对其进行编码 encode_conv.
  2. 验证解码完全恢复原始数据而不失真(decoded == data
  3. 引入一个随机错误(反转编码序列中的一位)并解码。 验证代码是否纠正了此错误,并且恢复的数据与原始数据匹配。
  4. 在不同的位置引入两个随机误差并解码。 由于不能保证两个错误的校正,因此只检查恢复的数据的长度与原始数据的长度匹配(即算法继续工作并输出正确的长度)。
In [ ]:
@testset "Свёрточный код (скорость 1/2, K=3) — случайные данные" begin
    using Random
    Random.seed!(1234)
    
    for test_idx in 1:10
        len = rand(5:15)
        data = rand(0:1, len)
        encoded = encode_conv(data)
        
        decoded = decode_conv(encoded)
        @test decoded == data
        
        noisy = copy(encoded)
        err_pos = rand(1:length(noisy))
        noisy[err_pos] = xor(noisy[err_pos], 1)
        decoded2 = decode_conv(noisy)
        @test decoded2 == data
        
        noisy2 = copy(encoded)
        pos1, pos2 = rand(1:length(noisy2), 2)
        noisy2[pos1] = xor(noisy2[pos1], 1)
        noisy2[pos2] = xor(noisy2[pos2], 1)
        decoded3 = decode_conv(noisy2)
        @test length(decoded3) == length(data)
    end
end;
Test Summary:                                         | Pass  Total  Time
Свёрточный код (скорость 1/2, K=3) — случайные данные |   30     30  0.3s

共30次测试,全部顺利完成。

结论

我们已经实现了两种基本的噪声容忍编码算法,并使用内置库确认了它们的正确性。 Test.

我们在实践中学到了什么:

-汉明码对于具有罕见单一错误的通道简单有效。
-卷积码提供更大的灵活性,更适合连续数据流,但需要更复杂的解码。