记一次令人沮丧的调试 - 又:为什么 enumerate(set(...))的顺序有时是随机的?

2020-12-29 21:44:15 +08:00
 yanhh

大大前天写了一个神经网络,跑了三天,到大前天重启电脑的时候才发现状态保存没写好,当时以为是有参数变量忘记存了,或者是存储的时候操作错误,查错查了三个小时都没查出问题(还发了个帖子: https://www.v2ex.com/t/739076 ),心情很差,不想调了,就把代码放在一边,跑去做别的事了,每天时不时看看代码,但是怎么看都感觉没问题。

刚刚终于开始单步调试,一调就发现问题了。

问题相当隐蔽:

我的网络输入的是文本,预处理的时候先做了一个字符表:

chars = set(open('...txt').read())
self.char_to_ix = { ch:i for i,ch in enumerate(chars) }

这个字符表看起来每次运行都是一样的,但是实际上不是。

不知道为什么,enumerate(set(...))这个操作每次得到的内容顺序是随机的。大家可以去自己的 Python 里黏贴这一句试试:

{ ch:i for i,ch in enumerate(set('我爱你中国')) }

比比如我第一次运行,结果是:

{'国': 0, '爱': 1, '中': 2, '你': 3, '我': 4}

重启 Python(必须重启),再运行,结果变为:

{'爱': 0, '中': 1, '你': 2, '我': 3, '国': 4}

就是因为这个简单的错误,一旦重启程序,虽然网络参数都保存得很好,但是因为数据源变了,所以训练得从头开始。

最令人沮丧的是,我本来以为这是一个很难调试的错误,所以一直没有去动它;但是真正一开始调试,又发现这个问题如此简单————随后发现,我只是懒而已(= =)

不管怎么样,enumerate(set(...))的这个行为都很奇怪。我简单翻看了一下文档,但是没见到有关解释。打算去 Stackoverflow 问问。

4299 次点击
所在节点    Python
40 条回复
Wincer
2020-12-30 09:47:40 +08:00
楼主这个语句,用字典推导式应该是更好的选择。
HelloViper
2020-12-30 12:03:57 +08:00
set 就是无序啊,需要有序用 list 啊。。。。。。。。

dict 是 2.6 之后默认 orderlist,先 update 进去的会先遍历到
yanhh
2020-12-30 13:10:43 +08:00
@hello2060 哈哈哈
yanhh
2020-12-30 13:14:07 +08:00
@ETiV 巧了,我也干过这种事,哈哈哈哈,谁知道 Python 的 hash(...)函数是它返回自己的东西,不是求哈希 后来才知道 from zlib import crc32 (我就临时校验个东西,md5 比较慢,所以选了 crc32 )
iceneet
2020-12-30 13:14:27 +08:00
set 本来就是无序的啊。。 有序的用 list
yanhh
2020-12-30 13:15:26 +08:00
@Wincer 不懂,{ ch: i ... for ... } 这个就是字典推导式啊
yanhh
2020-12-30 13:16:49 +08:00
@iceneet 主要是它的随机性不是每一次调用都随机,而是每一次重新开 Python 随机,在一个 Python 进程中不是随机的,所以我的程序在一个线程里怎么调用它都一样,我就以为它不是随机的,没意识到它底层是用哈希实现,还以为是 list
yanhh
2020-12-30 13:17:22 +08:00
@knightdf 主要是它的随机性不是每一次调用都随机,而是每一次重新开 Python 随机,在一个 Python 进程中不是随机的,我的程序在一个线程里怎么调用它都一样,我就以为它不是随机的。结果就没意识到它底层是用哈希实现,还以为是 list
Kaciras
2020-12-30 13:27:22 +08:00
6 楼说的是对的,hash 随机化,启动时添加环境变量 PYTHONHASHSEED=0 即可关闭
hitmanx
2020-12-30 13:29:49 +08:00
很多 C++/STL 用久了的人转写 Python 时都会想当然地以为 set 和 map/dict 是基于树结构的 /有序的
yanhh
2020-12-30 13:38:10 +08:00
@hitmanx 自己去实现过数据结构的人容易会这样想
lakehylia
2020-12-30 13:48:50 +08:00
话说你要保存序列的变量,不是应该用数组 /向量 /列表吗?为什么要用集合?集合在处理序列的变量在运行效率以及空间效率上也不是最优啊。要是我,c++上肯定用 vector,Python 上用 list 。
fuis
2020-12-30 13:50:32 +08:00
数据结构是学得有多差。。
lakehylia
2020-12-30 13:54:19 +08:00
如果是需要去重,并且保证输出顺序,那肯定要对结果进行排序啊。不然你都不知道它怎么去重的,内在的去重后的顺序也是不确定的。
zhanglintc
2020-12-30 14:36:23 +08:00
一次沮丧的看帖
Wincer
2020-12-30 15:32:34 +08:00
@yanhh 我意思是 { ch:i for i,ch in enumerate(set('我爱你中国')) } 可以直接使用 { ch:i for i,ch in enumerate('我爱你中国') },字典推导式在生成的时候,如果存在重复的 key,后一个会自动覆盖前一个 key 的值,所以这个 set 的引入实际上对解决并无帮助,反而还引入了新的问题。
yanhh
2020-12-30 20:14:43 +08:00
@Wincer 原来这样!懂了,这个很好
yanhh
2020-12-30 20:15:56 +08:00
@fuis haha
lithbitren
2020-12-30 23:41:13 +08:00
dict 在 py3.6 以后就用链表实现有序了,dict.popitem 可以把最后添加的键值对取出来,虽然内存比纯 hash 大,但遍历速度比 set 快。
araraloren
2020-12-31 08:50:27 +08:00
看了一遍,通通没有说到点上,顺序的 hash 自然可以保证,但是 hash 的随机性 是为了增强安全性,hash 是固定顺序的话很容易被攻击
这有一篇很久之前的文章,https://cry.nu/perl6/secure-hashing-for-moarvm/#hashing-basics

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/740130

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX