为什么我用不同的种子得到相同的数据?
Why do I get the same data with a different seed?
出于某种原因,seed=0 和 seed=1 给出了相同的结果,而我预计它会有所不同。
对于不同的结果,一切都按预期工作,只有 0 和 1 个种子才会出现问题。
是bug还是我没看懂?
重现代码。我在 gcc 和 g++ 编译器上试过了。
#include <vector>
#include <random>
#include <stdint.h>
int main()
{
int32_t length = 100000;
//first generated data
uint32_t seed1 = 0;
std::default_random_engine generator1;
generator1.seed(seed1);
std::uniform_int_distribution<int8_t> distribution1(0, 1);
std::vector<int8_t> sequence1(length);
for (int32_t i = 0; i < length; i++) {
sequence1[i] = distribution1(generator1);
}
// second generated data
uint32_t seed2 = 1;
std::default_random_engine generator2;
generator2.seed(seed2);
std::uniform_int_distribution<int8_t> distribution2(0, 1);
std::vector<int8_t> sequence2(length);
for (int32_t i = 0; i < length; i++) {
sequence2[i] = distribution2(generator2);
}
//check if data the same
bool sameData = true;
for (int32_t i = 0; i < length; i++) {
if (sequence1[i] != sequence2[i]) {
sameData = false;
}
}
std::cout << sameData; // true, but should be false
}
Why do i get same data with diffirent seed
所有种子值不一定产生唯一序列。
在您使用的标准库实现中,您选择的种子恰好与给定的随机引擎产生相同的序列。这可能是因为 0 恰好对那个特定的引擎来说是特殊的。
如果您使用的是 libstdc++,您观察到的行为由 this 实现解释:
template<typename _UIntType, _UIntType __a, _UIntType __c, _UIntType __m>
void
linear_congruential_engine<_UIntType, __a, __c, __m>::
seed(result_type __s)
{
if ((__detail::__mod<_UIntType, __m>(__c) == 0)
&& (__detail::__mod<_UIntType, __m>(__s) == 0)) // <-- true if seed == 0
_M_x = 1; // <-- seed is set to 1!
else
_M_x = __detail::__mod<_UIntType, __m>(__s);
}
最简单的解决方法是选择另一对种子,但这里有一些更普遍有用的建议:
- 如果您只想要两个不同的序列,则使用一个生成器并设置一次种子。这样你就不会意外地得到一对不幸的种子,并且你避免了第二次初始化引擎的开销。
- 除非您想要可重复的序列,否则最好使用可变种子源,而不是对其进行硬编码。一个常见的策略是使用当前时间(在这种情况下,我会通过递增第一个种子来选择第二个种子;由于时钟的粒度,再次获取当前时间可能会导致相同的种子)。虽然这并不能解决所有问题,但不太可能遇到这种异常情况 0.
- 除非你想要可重复的序列,否则你应该使用
std::random_device
来获得一个具有(希望)良好熵的种子。
- 如果您需要良好的随机性属性,或者如果您需要跨系统的一致性,则不要依赖
std::default_random_engine
,它可能是一个 LCG(不良随机性属性)并且可能因系统而异。 Mersenne twister 引擎 - 由标准库提供 - 被认为具有相当好的随机性,并且是一个不错的默认选择,除非你有理由选择另一个。
这种“奇怪”很可能可以简单地通过注意到您使用的 PRNG std::default_random_engine
不是很好来解释。
这又是你的代码,除了现在它编译(缺少 #include <iostream>
)并使用 std::mt19937
代替:
#include <vector>
#include <random>
#include <cstdint>
#include <iostream>
int main()
{
int32_t length = 100000;
//first generated data
uint32_t seed1 = 0;
std::mt19937 generator1;
generator1.seed(seed1);
std::uniform_int_distribution<int8_t> distribution1(0, 1);
std::vector<int8_t> sequence1(length);
for (int32_t i = 0; i < length; i++) {
sequence1[i] = distribution1(generator1);
}
// second generated data
uint32_t seed2 = 1;
std::mt19937 generator2;
generator2.seed(seed2);
std::uniform_int_distribution<int8_t> distribution2(0, 1);
std::vector<int8_t> sequence2(length);
for (int32_t i = 0; i < length; i++) {
sequence2[i] = distribution2(generator2);
}
//check if data the same
bool sameData = true;
for (int32_t i = 0; i < length; i++) {
if (sequence1[i] != sequence2[i]) {
sameData = false;
}
}
std::cout << sameData; // true, but should be false
}
Link to code
输出:
0
另一个小改动是 #include <stdint.h>
=> #include <cstdint>
。
每个 this 页面,如果您 Ctrl+f
对应 default_random_engine
,您会看到它只是说它是实现定义的。这意味着编译器编写者可以为所欲为。
这也意味着对于不同的编译器,它可以有不同的行为,这是允许的。我没有证据证明这一点,因为我从未看过任何源代码,但我相信至少 gcc 和 clang 所做的是实现一个 非常 简单的,而不是所有的随机引擎在玩具示例中几乎不足以掷骰子。一旦我们转向使用具有严格要求的更好引擎,您更有可能看到预期结果。仍然不能保证。
这是因为 std::seed_seq
是确定性的,它是 <random>
中大多数 PRNG 在构建初始状态时使用的步骤之一。
连std::mt19937
也要用好
参考文献:
https://www.pcg-random.org/posts/cpp-seeding-surprises.html
https://kristerw.blogspot.com/2017/05/seeding-stdmt19937-random-number-engine.html
libstdc++,通常与 gcc 一起提供,将 std::default_random_engine
定义为 std::linear_congruential_engine<uint_fast32_t, 16807UL, 0UL, 2147483647UL>
的类型定义。源代码链接:
- https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/random.h#L1607
- https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/random.h#L1558
请注意,此引擎是所谓的“Lewis、Goodman 和 Miller 的经典最低标准 rand0”。该引擎在本文中有描述:https://www.researchgate.net/publication/220420979_Random_Number_Generators_Good_Ones_Are_Hard_to_Find.
在里面,您可以在第 1195 页找到它的算法。请注意该论文中的以下引用:
This generator must be initialized by assigning seed a value between 1 and 2147483646. ... Unfortunately, for most systems this version of Random is fatally flawed.
种子值0因此即使不满足这个要求。
一点调试...
首先让我们注意到
template<typename _UIntType, _UIntType __a, _UIntType __c, _UIntType __m>
class linear_congruential_engine { ...
然后
typedef linear_congruential_engine<uint_fast32_t, 16807UL, 0UL, 2147483647UL>
minstd_rand0;
typedef minstd_rand0 default_random_engine;
所以我们有一个 linear congruential generator,参数 C
设置为零。现在...
|114 template<typename _UIntType, _UIntType __a, _UIntType __c, _UIntType __m> │
│115 void │
│116 linear_congruential_engine<_UIntType, __a, __c, __m>:: │
│117 seed(result_type __s) │
│118 { │
│119 if ((__detail::__mod<_UIntType, __m>(__c) == 0) │
│120 && (__detail::__mod<_UIntType, __m>(__s) == 0)) │
>│121 _M_x = 1; │
│122 else │
│123 _M_x = __detail::__mod<_UIntType, __m>(__s); │
│124 }
因此该实现拒绝使用 0 作为种子,而是使用 1 作为种子。这也是一件好事,因为当 C==0 时,零种子根本不起作用。它将产生一个恒定的零流(在调试器中更改 _M_x
并查看...或只查看公式)。
不能保证不同的种子值会产生不同的伪随机序列。
出于某种原因,seed=0 和 seed=1 给出了相同的结果,而我预计它会有所不同。
对于不同的结果,一切都按预期工作,只有 0 和 1 个种子才会出现问题。
是bug还是我没看懂?
重现代码。我在 gcc 和 g++ 编译器上试过了。
#include <vector>
#include <random>
#include <stdint.h>
int main()
{
int32_t length = 100000;
//first generated data
uint32_t seed1 = 0;
std::default_random_engine generator1;
generator1.seed(seed1);
std::uniform_int_distribution<int8_t> distribution1(0, 1);
std::vector<int8_t> sequence1(length);
for (int32_t i = 0; i < length; i++) {
sequence1[i] = distribution1(generator1);
}
// second generated data
uint32_t seed2 = 1;
std::default_random_engine generator2;
generator2.seed(seed2);
std::uniform_int_distribution<int8_t> distribution2(0, 1);
std::vector<int8_t> sequence2(length);
for (int32_t i = 0; i < length; i++) {
sequence2[i] = distribution2(generator2);
}
//check if data the same
bool sameData = true;
for (int32_t i = 0; i < length; i++) {
if (sequence1[i] != sequence2[i]) {
sameData = false;
}
}
std::cout << sameData; // true, but should be false
}
Why do i get same data with diffirent seed
所有种子值不一定产生唯一序列。
在您使用的标准库实现中,您选择的种子恰好与给定的随机引擎产生相同的序列。这可能是因为 0 恰好对那个特定的引擎来说是特殊的。
如果您使用的是 libstdc++,您观察到的行为由 this 实现解释:
template<typename _UIntType, _UIntType __a, _UIntType __c, _UIntType __m>
void
linear_congruential_engine<_UIntType, __a, __c, __m>::
seed(result_type __s)
{
if ((__detail::__mod<_UIntType, __m>(__c) == 0)
&& (__detail::__mod<_UIntType, __m>(__s) == 0)) // <-- true if seed == 0
_M_x = 1; // <-- seed is set to 1!
else
_M_x = __detail::__mod<_UIntType, __m>(__s);
}
最简单的解决方法是选择另一对种子,但这里有一些更普遍有用的建议:
- 如果您只想要两个不同的序列,则使用一个生成器并设置一次种子。这样你就不会意外地得到一对不幸的种子,并且你避免了第二次初始化引擎的开销。
- 除非您想要可重复的序列,否则最好使用可变种子源,而不是对其进行硬编码。一个常见的策略是使用当前时间(在这种情况下,我会通过递增第一个种子来选择第二个种子;由于时钟的粒度,再次获取当前时间可能会导致相同的种子)。虽然这并不能解决所有问题,但不太可能遇到这种异常情况 0.
- 除非你想要可重复的序列,否则你应该使用
std::random_device
来获得一个具有(希望)良好熵的种子。 - 如果您需要良好的随机性属性,或者如果您需要跨系统的一致性,则不要依赖
std::default_random_engine
,它可能是一个 LCG(不良随机性属性)并且可能因系统而异。 Mersenne twister 引擎 - 由标准库提供 - 被认为具有相当好的随机性,并且是一个不错的默认选择,除非你有理由选择另一个。
这种“奇怪”很可能可以简单地通过注意到您使用的 PRNG std::default_random_engine
不是很好来解释。
这又是你的代码,除了现在它编译(缺少 #include <iostream>
)并使用 std::mt19937
代替:
#include <vector>
#include <random>
#include <cstdint>
#include <iostream>
int main()
{
int32_t length = 100000;
//first generated data
uint32_t seed1 = 0;
std::mt19937 generator1;
generator1.seed(seed1);
std::uniform_int_distribution<int8_t> distribution1(0, 1);
std::vector<int8_t> sequence1(length);
for (int32_t i = 0; i < length; i++) {
sequence1[i] = distribution1(generator1);
}
// second generated data
uint32_t seed2 = 1;
std::mt19937 generator2;
generator2.seed(seed2);
std::uniform_int_distribution<int8_t> distribution2(0, 1);
std::vector<int8_t> sequence2(length);
for (int32_t i = 0; i < length; i++) {
sequence2[i] = distribution2(generator2);
}
//check if data the same
bool sameData = true;
for (int32_t i = 0; i < length; i++) {
if (sequence1[i] != sequence2[i]) {
sameData = false;
}
}
std::cout << sameData; // true, but should be false
}
Link to code
输出:
0
另一个小改动是 #include <stdint.h>
=> #include <cstdint>
。
每个 this 页面,如果您 Ctrl+f
对应 default_random_engine
,您会看到它只是说它是实现定义的。这意味着编译器编写者可以为所欲为。
这也意味着对于不同的编译器,它可以有不同的行为,这是允许的。我没有证据证明这一点,因为我从未看过任何源代码,但我相信至少 gcc 和 clang 所做的是实现一个 非常 简单的,而不是所有的随机引擎在玩具示例中几乎不足以掷骰子。一旦我们转向使用具有严格要求的更好引擎,您更有可能看到预期结果。仍然不能保证。
这是因为 std::seed_seq
是确定性的,它是 <random>
中大多数 PRNG 在构建初始状态时使用的步骤之一。
连std::mt19937
也要用好
参考文献:
https://www.pcg-random.org/posts/cpp-seeding-surprises.html
https://kristerw.blogspot.com/2017/05/seeding-stdmt19937-random-number-engine.html
libstdc++,通常与 gcc 一起提供,将 std::default_random_engine
定义为 std::linear_congruential_engine<uint_fast32_t, 16807UL, 0UL, 2147483647UL>
的类型定义。源代码链接:
- https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/random.h#L1607
- https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/random.h#L1558
请注意,此引擎是所谓的“Lewis、Goodman 和 Miller 的经典最低标准 rand0”。该引擎在本文中有描述:https://www.researchgate.net/publication/220420979_Random_Number_Generators_Good_Ones_Are_Hard_to_Find.
在里面,您可以在第 1195 页找到它的算法。请注意该论文中的以下引用:
This generator must be initialized by assigning seed a value between 1 and 2147483646. ... Unfortunately, for most systems this version of Random is fatally flawed.
种子值0因此即使不满足这个要求。
一点调试...
首先让我们注意到
template<typename _UIntType, _UIntType __a, _UIntType __c, _UIntType __m>
class linear_congruential_engine { ...
然后
typedef linear_congruential_engine<uint_fast32_t, 16807UL, 0UL, 2147483647UL>
minstd_rand0;
typedef minstd_rand0 default_random_engine;
所以我们有一个 linear congruential generator,参数 C
设置为零。现在...
|114 template<typename _UIntType, _UIntType __a, _UIntType __c, _UIntType __m> │
│115 void │
│116 linear_congruential_engine<_UIntType, __a, __c, __m>:: │
│117 seed(result_type __s) │
│118 { │
│119 if ((__detail::__mod<_UIntType, __m>(__c) == 0) │
│120 && (__detail::__mod<_UIntType, __m>(__s) == 0)) │
>│121 _M_x = 1; │
│122 else │
│123 _M_x = __detail::__mod<_UIntType, __m>(__s); │
│124 }
因此该实现拒绝使用 0 作为种子,而是使用 1 作为种子。这也是一件好事,因为当 C==0 时,零种子根本不起作用。它将产生一个恒定的零流(在调试器中更改 _M_x
并查看...或只查看公式)。
不能保证不同的种子值会产生不同的伪随机序列。