我的超采样模拟为什么会出现负偏差?

6
我正在开发一个小的测试程序,以确定向ADC添加噪声以获得过采样位是否可行。在我们开始之前,先讲一下一些理论知识。奈奎斯特采样定理表明,增加1位分辨率需要四个额外的采样,通常情况下,增加n位需要2^(n+1)个额外采样。我正在模拟一个完美的10位ADC,它从0..1023单调地返回一个值,并且对于0-2V的输入没有噪声。
为了获得更多的位数,必须添加随机分布的噪声(它不必实际上是随机的,但它必须看起来像白噪声)。我的问题是,尽管分辨率正在增加,但实际读数偏移了一些小的负值。这是一个输入1伏特时的样本输出(参考电压为2伏特,所以对于单调ADC,计数应该正好减半):
10 bits:  512         volts:  1.0
11 bits:  1024        volts:  1.0
12 bits:  2046        volts:  0.9990234375
13 bits:  4093        volts:  0.999267578125
14 bits:  8189        volts:  0.999633789062
15 bits:  16375       volts:  0.999450683594
16 bits:  32753       volts:  0.999542236328
17 bits:  65509       volts:  0.999588012695
18 bits:  131013      volts:  0.999549865723
24 bits:  8384565     volts:  0.999518036842
28 bits:  134152551   volts:  0.999514393508

事实上,无论我运行多少次模拟,最终结果总是约为0.9995,而不是1;最后一个值应该是134,217,728,而不是134,152,551,相差约65,771个单位,大约是额外18位分辨率的1/4(巧合吗?我除以4了)。我怀疑我的伪随机数生成器存在某种偏差,但我正在使用Python默认提供的Mersenne Twister。
#!/usr/bin/python
# 
#  Demonstrates how oversampling/supersampling with noise can be used
#  to improve the resolution of an ADC reading.
#
#  Public domain.
#

import random, sys

volts = 1.000
reference = 2.000
noise = 0.01
adc_res = 10

def get_rand_bit():
    return random.choice([-1, 1])

def volts_with_noise():
    if get_rand_bit() == 1:
        return volts + (noise * random.random() * get_rand_bit())
    else:
        return volts

def sample_adc(v):
    # Sample ADC with adc_res bits on given voltage.
    frac = v / reference
    frac = max(min(frac, 1.0), 0.0) # clip voltage
    return int(frac * (2 ** adc_res))

def adc_do_no_noise_sample():
    return sample_adc(volts)

def adc_do_noise_sample(extra_bits_wanted):
    # The number of extra samples required to gain n bits (according to 
    # Nyquist) is 2^(n+1). So for 1 extra bit, we need to sample 4 times.
    samples = 2 ** (extra_bits_wanted + 1)
    print "Sampling ", samples, " times for ", extra_bits_wanted, " extra bits."
    # Sample the number of times and add the totals.
    total = 0
    for i in range(samples):
        if i % 100000 == 99999:
            print float(i * 100) / samples
            sys.stdout.flush()
        total += sample_adc(volts_with_noise())
    # Divide by two (to cancel out the +1 in 2^(n+1)) and return the integer part.
    return int(total / 2)

def convert_integer_to_volts(val, num_bits):
    # Get a fraction.
    frac = float(val) / (2 ** num_bits)
    # Multiply by the reference.
    return frac * reference

if __name__ == '__main__':
    # First test: we want a 10 bit sample.
    _10_bits = adc_do_no_noise_sample()
    # Next, create additional samples.
    _11_bits = adc_do_noise_sample(1)
    _12_bits = adc_do_noise_sample(2)
    _13_bits = adc_do_noise_sample(3)
    _14_bits = adc_do_noise_sample(4)
    _15_bits = adc_do_noise_sample(5)
    _16_bits = adc_do_noise_sample(6)
    _17_bits = adc_do_noise_sample(7)
    _18_bits = adc_do_noise_sample(8)
    _24_bits = adc_do_noise_sample(14)
    _28_bits = adc_do_noise_sample(18)
    # Print results both as integers and voltages.
    print "10 bits: ", _10_bits, "  volts: ", convert_integer_to_volts(_10_bits, 10)
    print "11 bits: ", _11_bits, "  volts: ", convert_integer_to_volts(_11_bits, 11)
    print "12 bits: ", _12_bits, "  volts: ", convert_integer_to_volts(_12_bits, 12)
    print "13 bits: ", _13_bits, "  volts: ", convert_integer_to_volts(_13_bits, 13)
    print "14 bits: ", _14_bits, "  volts: ", convert_integer_to_volts(_14_bits, 14)
    print "15 bits: ", _15_bits, "  volts: ", convert_integer_to_volts(_15_bits, 15)
    print "16 bits: ", _16_bits, "  volts: ", convert_integer_to_volts(_16_bits, 16)
    print "17 bits: ", _17_bits, "  volts: ", convert_integer_to_volts(_17_bits, 17)
    print "18 bits: ", _18_bits, "  volts: ", convert_integer_to_volts(_18_bits, 18)
    print "24 bits: ", _24_bits, "  volts: ", convert_integer_to_volts(_24_bits, 24)
    print "28 bits: ", _28_bits, "  volts: ", convert_integer_to_volts(_28_bits, 28)

我希望你能提出任何建议。我的计划是最终采用低成本微控制器来实现高分辨率ADC。速度相当重要,因此我可能会使用LFSR生成PRNG位,这不会像Mersenne twister一样好,但应该足够大多数用途,并且希望对此有足够的贡献。


你能否添加一个关于“ADC”含义的提示? - Svante
我对你的意图也有些不确定。对我来说,“分辨率”意味着“最小可辨别细节”。如果你有低于分辨率的细节,那么你就会丢失这些信息。添加噪声如何能够将这些信息补回来呢?你是在使用一些不同的“分辨率”含义吗? - Svante
@Svante - 模拟到数字转换器。 - Thomas O
@Svante,电压源可以被认为是分辨率无限的。添加噪声会导致ADC上的计数有些跳动,从而增加了分辨率。如果没有信息理论背景(我实际上并没有),很难解释清楚,但这主要是通过书籍和网站收集的知识。 - Thomas O
不是看起来有点偏见,而是你的volts_with_noise()实现方式让我感到有些奇怪:50%的采样将不会添加任何噪声。这是故意的吗? - Andre Holzner
顺便说一下,这是一个有趣的问题,也可以在http://stats.stackexchange.com/或http://electronics.stackexchange.com/上提问 :-) - Andre Holzner
3个回答

4
sample_adc(..)中,您可能希望进行四舍五入而不是截断(系统地向负无穷大方向舍入),即执行以下操作:
return int(frac * (2 ** adc_res) + 0.5)

替代

return int(frac * (2 ** adc_res))

那么偏差不总是在同一侧:

10 bits:  512   volts:  1.0
11 bits:  1025   volts:  1.0009765625
12 bits:  2046   volts:  0.9990234375
13 bits:  4100   volts:  1.0009765625
14 bits:  8196   volts:  1.00048828125
15 bits:  16391   volts:  1.00042724609
16 bits:  32784   volts:  1.00048828125
17 bits:  65528   volts:  0.999877929688
18 bits:  131111   volts:  1.00029754639
24 bits:  8388594   volts:  0.99999833107
28 bits:  134216558   volts:  0.999991282821

虽然要检查偏差,例如可以调用adc_do_noise_sample(..),例如10,000次(每个分辨率),并计算平均偏差和平均不确定度(并检查其与零的兼容性)。


0

听起来你的噪声源有偏差。我不熟悉Mersenne Twister,但是我确切知道LFSR伪随机噪声发生器总是有一定的偏差。通过扩展LFSR的长度可以使偏差任意小,但偏差始终会存在。


我进行了一个快速测试,从我的随机生成器中取100,000个数字的平均值,结果大约在-0.0001到+0.0001之间,因此偏差不是很大(与预期相当)。问题似乎是对~0.9995有非常一致的偏差。 - Thomas O

0

我不熟悉这个领域,但如果您在使用python2,您可能会遭受意外的整数除法影响。有一些方法可以处理这个问题。

>>> 10 / 3
3
>>> 10 * 1.0 / 3
3.3333333333333335
>>> from __future__ import division
>>> 10 / 3
3.3333333333333335
>>> 10 // 3
3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接