七、幸福的烦恼

  还没睡到 3 小时,就被 D 神的电话叫醒:矿池挂了……两人一通排查,认定是使用人数太多导致了内存溢出。不用多说,既然内存不足,就升级机器吧。当初我们选择 DigitalOcean 就是因为它支持快速升级,万一配置不够,点点鼠标,几秒钟就可以升级到更高的配置。重新打开矿池,一切指标都正常,不过我也已经进入了兴奋状态,睡不着了。

  我们一边庆幸狗池选在周六发布,这样周日不管出现什么事情都可以及时修复。另一方面也在考虑,有没有什么方法可以及时找出异常,最好在矿池挂掉之前就能发现苗头,及时解决问题。这时,沉默已久的M神突然出现,秒杀了这个需求。M 神有着丰富的运维经验,他手上的服务器多得需要用两位字母加两位数字编号来命名。M 神为几台矿池服务器安装配置了 Munin,这是一个可以在 Web 端一目了然看到 CPU、内存、硬盘、网络等重要系统指标的监视工具。这款工具还可以方便地配置告警条件,比如硬盘快满的时候会提醒我们。这给我们之后及时发现“幸福的烦恼”提供了大量的便利。

分钱 Bug


矿工账单页面插图
矿工账单页面插图

  看着 X 神画的 gopher 开心分钱的图,心情也是非常的舒畅,但在矿池发布的第二天下午,我们的心情却是非常紧张的。凌晨我们已经见识到了狗池在连续出两个块的时候分钱会有问题,现在随着用户越来越多,出块也越来越快,这个 bug 就发生地更频繁了。这还好,一开始多分了钱,后面就少分点,还是可以慢慢纠正回来的,但是另一个 bug 就更要命了:我们发现,当两个用户同时出块的时候,比特币钱包会告诉我这两个块都成功了……这下就麻烦了,我们以为发出去的钱,其实并没有发出去,后面算的钱就全都错了。一旦有人发现收到的金额不对,就会怀疑狗池在黑他们的钱,这可了得,口碑坏了就什么都没了。

  保证分钱的正确性是狗池的首要目标,我们这帮技术宅什么都没,只能靠着目前看来还不错的口碑让狗池继续壮大。第一个 bug 经过 R 神周日加班终于搞定了;第二个 bug 在我看来是比特币钱包留下的坑,既然钱包里不加锁,只能在矿池里加了,结果我加了一堆锁,总算是把这个问题绕过去了,这时的代码已经丑得我不想再改了……几天后,D 神提出了一个完美的无锁方案,彻底解决了这个问题,当然这是后话了。

新版上线

  上线一天,狗池的代码还充满了 bug,修修补补免不了要重新发布新版本。作为一个网络服务,发布新版本有着与生俱来的优势:只要在服务器上更新一下就可以了。但是作为一个矿池,上线新版本却又不像网站更新这么容易。首先,我们得保证分钱不能错,share 数据都存在内存里,需要及时备份到硬盘,已经分配的钱需要确认写到数据库里了。其次,重启时间要尽可能快,时间就是金钱,停 1 分钟可能会少赚几毛钱,但是如果停了 10 分钟,可能有些矿工就会跑去别的矿池了,所以这里一定要争分夺秒。

  我们最终采用了一种低成本的土办法来上线新版矿池。首先 touch exitpool 发出退出指令,当矿池发现存在“exitpool”这个文件时,就会开始备份各种数据。与此同时,我们人肉观察什么时候矿池结束运行,一旦发现停了,就马上运行新版。新版跑起来之后就要观察指标,万一指标异常,要马上回滚运行之前的版本。回滚命令和运行新版的命令在操作之前都会提前写好,到时候只要直接粘贴就可以了。其实这套方案可以用更自动化的脚本来完成,不过起初我们担心会出错,就在人工监视下一步步执行,后来 bug 少了,矿池也很少更新了,一直没人去写这个脚本,所以直到最后,我们还是这么纯手工地重启矿池。

“DDoS”

  那是一个风和日丽的周一下午,狗池经过昨天的调教,已经稳定运行了大半天了。开完了组会,我回到电脑前,看到群里已经有了 N 条未读消息,就冒了个泡“长求总”,紧接着群里就开始刷屏“报告包工头,我们被 DDoS 了”。

  其实对于 DDoS,我们早有耳闻,论坛上经常看人提到说某某矿池今天被人 DDoS 了,也有一些矿池主打就是抗 DDoS,用着省心。在做矿池的时候我们也考虑过防 DDoS,不过想着这怎么也得两个月之后才需要考虑吧,怎么这不到两天就……

  说回这次 DDoS,其实我们之前都没见过,为什么这么快就认定这是被攻击了呢?回想起下午 1 点多的时候,矿池在线的矿工还只有 1000 人,没想到在之后短短的一小时里,矿工数猛增了 10000 多人,直接导致 CPU 满了。奇怪的是,虽然矿工数多了十倍,但是每分钟 share 数却不见怎么增加。D 神查看日志发现,新进矿池的这 10000 多个“矿工”行为非常异常,一方面大量反复重连,另一方面又几乎不参与计算,因此认定这绝对是被攻击了。

  虽说 DDoS 是分布式攻击,但是经过统计发现,这次攻击的 IP 比较集中,D 神、R 神、S 神经过讨论决定,先封杀连接数最多的 100 个
IP。当然,封杀 IP 这么高大上的功能,是不会存在于我们这么快糙猛做出来的矿池里了。好在 Linux 下,可以简单地配置 iptables 直接封杀来自某些 IP 的所有连接。就这样,经过 D 神的一番操作,矿工数降到了 7000。攻击者似乎也意识到了 D
神的封杀,放慢了攻击脚步。此时是下午 4 点,距离发现攻击刚过一小时。

  但是这种手工封杀的方式毕竟不是长久之策,最好矿池就有识别恶意连接的功能,直接封杀那批伪矿工。于是我开始仔细分析这些伪矿工的行为。又一番统计之后,我惊奇地发现,那些伪矿工其实也在计算,只不过计算性能非常非常的低。之前见到的矿工,大多是职业矿工,他们用 8 核或是 32 核的服务器来挖矿,也有少量矿工可能是用自己的双核、四核电脑在挖。但这批新矿工,不仅在用单核挖矿,而且挖矿效率低的惊人,基本只有普通单核的四分之一……

  那么问题来了,我们要怎么对待这些低效的矿工呢?封杀似乎不一定合理,说不定人家真的就是拿着大量的烂机器,想好好挖矿呢……而且对于我们矿池来说,再弱的矿工也是能给我们带来收益的,我们更应该好好对待他们啊。

  既然暂时选择了要好好对待每一个矿工,那只能在自己身上想办法解决问题了。现在的瓶颈在 CPU 上,那么就只有两条出路了,要不优化程序,要不升级机器。回想起昨天才刚升级了机器,现在就要升级了,这还没怎么赚钱,成本就越来越高了,会不会倒闭啊……

  我们还是先优化一些已知的问题吧。比如之前为了调试,打印了大量日志,现在是时候砍掉大部分了。经过一番简单地优化,上线一看,确实 CPU 使用率下降了,但是下降地非常微弱(图中箭头处)。


CPU占用率
服务器 CPU 使用率变化曲线图(可以看到 CPU 占用在 2 点多开始突然上升)

  实际上最方便的找瓶颈的方法是使用性能分析工具。Go 语言也有(pprof),但是似乎不太给力,我们在线上环境测试了一次,分析报告等了一小时还没出来。但是如果线下测试,由于计算量太小,又找不到瓶颈在哪。所以最后还是只能回到人肉分析。

  于是我们又一个个模块地分析,在分析到 share 验证模块的时候,我突然想起 H 神写完这个模块的时候提了一句,“每次计算需要 70ms”。现在的 share 数量大约是每分钟 2000 个,也就是说,每秒的计算量是 0.07*2000/60 = 2.3 CPU 秒,一秒钟有两秒多的计算需求,这已经超出了双核 CPU 的极限。看来瓶颈就是在这了,但是这部分计算是必不可少的,而且验证 share 的算法也是非常成熟的,我们也不可能一时半会把它优化下来。哎,还是花钱求平安吧,继续升级服务器。

  晚上 8 点,在大家十万个不情愿之下,终于还是把机器升级到了四核,CPU 安静地飘在 75% 的位置上,连接数居然到了接近 2 万;挖矿速度也有了巨大的提升,矿池的算力已经接近数据币全网算力的 50%。看起来之前矿池在双核机器上跑是被严重地压抑了性能。

  矿池终于回到了正常状态,可以暂时歇一口气了。接下来的主要任务是优化程序的性能,避免成本一直上涨。但是在此之前,我们还是很想八卦一下这接近两万台机器,到底是谁的。可以肯定的是,这些机器挖到的矿都是给同一个钱包的,说明这些机器都是一个人的。但是谁会有这么多机器呢?我们想来想去觉得只有黑客手中的肉鸡,才会有这么大的规模。但是从 IP 分布来看,这些机器都集中在几个城市,网段也很接近,像是几个网吧的。不过网吧一般都有还原卡,不太容易中木马……真是越想越绕,不知道到底是何方神圣。还是随他去吧,能让我们赚钱就行。

六、今晚上线——人品、人品和水军

  开发过程看似持续了两周,其实最后几天都在集中测试。最难测试的就是挖到区块之后的提交功能。当时我们所有参与的人都开着机器跑挖矿程序,就算这样,也要一整天时间才能挖到一个块,获得一次珍贵的提交机会。在修复了若干 bug 之后,终于迎来了“今晚上线”的这一天。

  其实这里“上线”更确切地说应该是公开矿池的地址,吸引矿工来挖矿。毕竟要真的说上线的话,矿池已经在线跑了好几天了,还在我们这么多“矿工”的努力下,正常出了两个块。

  对于这次“上线”,大家心里都没底,毕竟我们这帮技术宅完全没有搞营销的经验,在矿工圈里也没有什么熟人。看来只能硬着头皮小心翼翼地贴小广告了。

  首先拟了个发布稿,贴在当时最“官方”的 bitcointalk 论坛(山寨币的发布,都在这个论坛),开头是“我做了个新的 DTC 矿池,欢迎大家来测试……”。其实第一版稿件里用的全是“我们”,后来一方面为了装 B,另一方面为了给矿工一种好说话的感觉,把稿子里所有的“我们”都改成了“我”。这个规矩一直严格执行,直到矿池倒闭。

  等论坛帖子发布 10 多分钟后,我那个潜伏在数据币矿工群的小号终于发挥用处了,发出了这条关键信息。

小白水晶 2013-12-14 22:13:18
好像有新的矿池了?不知道靠谱不,论坛里贴的 https://bitcointalk.org/index.php?topic=370920.0

  这种弱弱的语气也是我们一群人讨论了很久之后的产物。这条消息之后,原本活跃的群突然就安静了……诡异的安静让人有些不安。要知道那时候的矿池,还没网页,只有帖子里的这几句话。要是就这么被认作黑池而没人上钩,我们就要白干一场了。五分钟后,群又恢复到之前热闹的状态了,好几个矿工表示准备试试。

  看后台数据,连上矿池的人在缓慢地增长,发布之初,还是我们自己人的 10 台机器,慢慢就变成了 20、50……大概经过了一小时,已经有 100 台机器连上了矿池。突然,出块了!一阵兴奋之余,我们赶紧分头检查发放的工资、数据库、日志,看看有没有异常现象。一切都好,这才舒了一口气。

  矿工收到了工资,就开始在群里帮我们宣传“一小时挖两个币可以哦,大家快上”。我们其实每个人都已经加了群,虽然看着很激动,但也不敢乱说话,要是被认出来就不好了。这时候就只能靠群里“自干五”们的支持了。分析后台数据,排名第一的矿工用了 14 台 8 核电脑在挖矿,比起他来,我们自己的算力加起来也只是他的零头啊。

  又出块了!这次不是我们看后台数据发现的,而是矿工率先在群里说的。这下群里炸了锅“趁池子还没黑,快去挖”。看了真是哭笑不得啊。连接数 200、300 涨的飞快,远超我们的预期。又一个块!这短短的一个小时,出了三个块,小心脏受不了了……心跳正常之后,仔细算了算,按照期望,这时应该能出一到两个块,出了三个块纯属人品爆发。


blocks

挖矿出块页面插图

  接下来的一小时,我们感受到了什么是人品守恒。出来混,总是要还的。矿工的数量还在增长,但是却一直不出块了。有矿工开始破口大骂,我们只能仔细地排查错误,但是最终的结论是,运气太差。连接数开始缓慢下降,难道矿池才开业两小时就要准备关门了?

  为了缓和紧张的氛围,我们手动统计了所有矿工的欠款,跟帖贴在了发布稿的后面。这一招似乎非常有效,矿工看到池主说还欠自己钱,就暂时相信矿池还没跑路,愿意继续挖着试试了。群里有个矿工看到了这个帖子,猜测是不是池主在手动发钱才这么慢的。潜伏在矿工群里的众神瞬间化身为水军,顺势把舆论导向了这个有趣的中性话题上。

  终于在这个难熬的一小时快要结束的时候,一下子出了两个块,终于可以安心了。这时,R 神也写完了一个显示欠款页面,放出来大家一起检查。所有人同时发现了一个奇怪的现象,怎么有些人的欠款是负数,也就是说,给这些人多发了钱……这是要把自己弄倒闭的节奏啊……

  由于打印了大量的日志,排查起来非常方便。20 分钟之后,终于发现是数据同步的问题:我们最初设计的时候,为了图方便,把“不需要这么实时”的 share 统计数据,通过数据库来传递,反正最后也要入库。没想到在连续出块的时候,数据库还没来得及写入新数据,就接到了读取请求,这就导致矿池用旧的分钱方案又多分了一次钱。还好这个错误只会在连续出块的时候遇到,应该不严重,这真是个幸福的烦恼啊。此时已是凌晨两点,然而大家都非常兴奋,丝毫没有睡意,于是继续讨论怎么修复这个 bug。

  凌晨三点,方案落定,最佳解决方法应该是把关键数据都放到内存里,但是这得明天才能修完了。鉴于矿池运行稳定,大家都准备洗洗睡了。突然,矿池挂了……日志说,是同时打开文件数太多导致的。原来 Ubuntu 默认只能同时打开 1024 个文件(网络连接也算在内),现在已经有 400 多个矿工,再加上一些没释放的资源,已经达到了这个上限。问题倒是很好解决,改一下系统参数就好了。能在醒着的时候遇到这个错误真是幸运。

  到凌晨4点,终于能真的平静下来洗洗睡了。

五、包工头

  到 12 月 1 日,我们已经大致搞明白了比特币的原理,也知道矿池要做的事情,我手痒先设计了一个能用的矿池架构。这个架构主要有四个部分:钱包通信、矿机通信、主逻辑和 Web 界面。这些部分的功能是这样的(请跳过以下无聊的四段):

  钱包通信,顾名思义就是和钱包客户端通信了。比特币是在 P2P 网络上建立的,而其钱包客户端就是其中的一个普通节点,也就是说,这个客户端有着完整的功能。比如转账,比如宣布挖矿成功。我们也能从钱包客户端里面读取到当前虚拟币网络的任何信息。

  矿机通信,顾名思义就是和挖矿程序通信了。功能很单纯,有新的挖矿任务时,就给矿机发放任务;矿机挖到 share 时(下面会介绍什么是 share),就解析数据,并拉去检查提交。

  主逻辑,顾名思义就是主逻辑了。这个模块最初设计的时候是为了打通各模块之间的数据流,确保有新任务的时候可以及时通知到所有矿工。比如矿工算出新块了,要第一时间告诉钱包,及时同步到虚拟币网络。主逻辑的设计思想是尽可能抽象和简洁,随着后续的优化,主循环里面一共只有 20 多行核心代码。

  Web 界面,顾名思义就是个网页了。为了让矿工相信我们不是黑池,得把所有挖到的矿以及每个矿工的收入公开透明地展示出来。这个模块非常独立,上线之后才开始做。


architecture
最早规划的模块结构图(后期经过优化已经有所改变)

  我给自己封了个包工头的职位(史上亲自干活最多的包工头,本章标题就是为了纪念这个唯一出场机会的),由于整个设计思路数我最清晰,所以各个模块之间的协调由我来定。具体在开发过程中,每部分又可以继续拆分出一些独立模块,最后分出了八个独立模块大家一起来写。我写主逻辑以及矿机交互;D 神写分钱机制以及钱包通信;H 神写 share 验证;R 神写数据库接口、share 统计、Web。四个人,两周的业余时间基本都在开发矿池了。

  其实嘛,对于码农来说,编写矿池这几天完全就是“日常”,最没什么可以回忆了。基本都是,提出方案,讨论优化,然后敲代码,测试。不过,有些细节设计还是值得分享一下的。

狗池

  都写到第五章了,还是没说狗池是什么,现在终于要揭秘了:狗池就是我们做的矿池的名字。“池”肯定就是矿池的意思了,那为什么叫“狗”呢?我们在选择编程语言的时候,R 神和 F 神同时建议使用 Go 语言,可以应对“百万级”矿工的连接。D 神:“于是就叫 GoPool,简称狗池。”R 神:“贱名好养活。”于是名字就这么定了……不过后来因为 gopool.net 被注册了,我们只好抢了个 gpool.net,中文名还是不变。

  顺便插播一段漫画调节一下枯燥的氛围。Go 语言的吉祥物其实是 gopher,就是下面左边这个很萌的形象,后来 X 神为 Web 界面上每个功能都设计了一个对应的 gopher 风格的插图,后面不定期插播这套插图,先放一个首页的插图。


gopher首页插图
gopher以及矿池首页插图

share

  前面提到了 share,这是一个在矿池挖矿特有的概念。之前提过,矿池的设计目的就是让小算力的矿工能按照他们出的力分钱,从而得到持续稳定的小额收入。那怎么评估每个矿工出的力呢?其实思路很简单,就是让矿工做一些低难度的“挖矿”任务。比如比特币需要矿工找到一些前面有 67 个 0 的 hash 值,那么矿池可以降低难度到 50 个 0,如果矿工找到一个至少有 50 个 0 的方案,就可以提交,这个难度就小很多了。矿池如果在里面发现有 67 个 0 的,就可以提交给比特币网络了。质数币也类似,只是不以 hash 前面的 0 为难度衡量指标了,而以对应质数序列的长度为目标。质数币网络需要我们找到长度为 10 的序列,矿池就把难度降低到 6。这种矿工提交的低难度方案,就是 share。share 里面可能会有难度符合虚拟币网络要求的方案,就可以变成区块。

  这里还有个有趣的地方。矿工会不会在算到低难度的 share 时,就提交到矿池蹭分红,而在算到难度满足区块的要求时,直接擅自提交独占成果呢?其实这是不行的。还记得最早提过的挖矿其实在计算 hash(“基准字符串”+“随机字符串”)么,里面的基准字符串已经编码了:这个矿挖到之后是分给谁的。所以对于矿工而言,要么参加分红,要么自己挖矿,没法两者兼得。

分钱

  虚拟币矿池的分钱策略有两种现成的方案,PPLNS 和 PPS,具体可以看这个介绍。我们最后考虑到编程实现上的便利,改造了 PPLNS 策略,按照最近一小时的 share 数进行分配。对于矿工而言,PPLNS 手续费更低,只要收入稳定,一般都会选手续费低的。

  对于矿池而言,分钱还需要考虑另一个问题,那就是在哪分钱。这是什么意思呢?在虚拟币世界里有两种方式可以得到钱,一种是挖矿获得,另一种是别人转账过来。前面提到“基准字符串”里面编码了挖矿成功之后钱分给谁。如果是第一种方式,可以直接按照之前矿工的算力投入编写分钱方案,挖到矿时直接分给矿工。如果是第二种方式,则挖矿的收入全给矿池,之后通过转账的方式,把矿池里的钱转给矿工。

  这两种方案其实各有优点,前一种矿工可以及时看到收益;后一种按需取款,避免钱包都是碎银子……可能有人会觉得虚拟币钱包里的钱不就是个数字么,多笔小额的收入有什么关系。其实还真有关系,虚拟币用钱(给别人转账)的时候,需要说清楚,这笔支出的钱,分别是从哪几笔收入里来的。当然这些工作都是钱包客户端自动完成的。不过大量的碎银子会拖慢钱包的速度,甚至有可能导致转账数据包太大,而需要缴纳额外的手续费。

  我们最后采用的是前一种方案,因为该方案挖到的矿都直接分给矿工了,而矿池就不会存有大量虚拟币,也就不会成为黑客的攻击目标了。(后一种方案钱全在矿池,可以携款潜逃 23333)

  简单总结一下整个开发过程中的一些想法,产品设计上,我们第一目标是降低自己的风险,第二目标是为矿工提供稳定的收入。技术设计上,目标就是低成本(代码量和服务器成本),高性能。虽然这个目标不一定能泛化,但是在当时的情形下应该还是比较适合的。

p.s. 作为一个包工头,每天的 push 是必不可少的,那几天我的口头禅是“今晚上线”。然而这并没什么卵用。

四、说干就干——YY 变成现实的重要一步

  经过 D 神连续 4 天的洗脑,大家都已经相信了做矿池是一个发家致富的好路子。但是为什么没人动手做呢?当时没人能搞明白矿池是怎么回事,甚至连比特币的原理也不清楚。这种情况下,从零开始做矿池,技术门槛实在太高。既然我已经率先被洗脑成功,就一定要把这件事干成。

  第一步,拉人。土豪 F 神建了个群,把我们有意向参与开发的人加进了 QQ 群。这居然是个 2000 人的群,大家幻想着事成之后,直接把这个群当作用户群。

  第二步,分工。在所有人都很迷茫的时候,唯一能做的就是学习。先弄明白比特币、矿池的原理再说。

  当时可供学习的素材主要有代码和文档。比特币自然是开源的,也有一些矿池是开源的,比如 P2Pool。文档在当时反而很少,基本只能看比特币最早那篇简短的论文。中文的资料把比特币说得很神,英文也很难找到详细介绍比特币技术的资料,更不用说矿池了。

  配合代码和文档,还有一种很重要的学习素材,那就是数据。矿机(挖矿程序)和矿池的通信数据,比特币客户端存储的数据。于是,大家就开始分头行动,看比特币代码、看矿池代码、看开源矿机代码、分析不开源矿机的通信数据、编译开源矿池测试。这些工作相辅相成,大家有条不紊地进行着。

  和大神们一起讨论与探索,进度还是非常快的:

  第一天,大家就弄明白了比特币的原理(最有效的是看比特币的区块数据),什么是难度,比特币是怎么保证全网 10 分钟只出一个区块的。

  第二天,大概弄明白了质数币的原理,那些质数到底是怎么存的,怎么检验的。

  第三天,矿机与主流矿池的通信已经基本被翻译出来。至此我们终于发现,原来矿池比我们想象中的简单得多,倒是 P2Pool 有很多我们不需要的功能,代码过于复杂了。于是我们停止了为期两天的争论,放弃了修改 P2Pool 的方案,决定自己重写。

 p.s. 这几天所有人的口头禅都是:太 NB 了,怎么设计得这么精妙!

二、故事的开头

  尽管从开篇看来,做矿池是顺理成章的事情,然而故事真实的开头是这样的:

  首先,我们这帮技术宅(D神、R神、H神、M神、F神、S神、X神……)有一个群,平时在群里灌水,偶尔讨论技术。群里的话题是从这天开始变化的——

  • 2013.11.18 早上,R 神发了条微博说,睡觉前买了一个比特币,花了 3000,醒来之后涨到了 3600。见此大家各种膜拜。
  • 2013.11.18 白天,好消息:国内最大的比特币交易平台拿到了风投。于是比特币涨到了 4000 多一个。
  • 2013.11.18 晚上,好消息:美帝公开表示不会取缔比特币。于是比特币一下子飙升到 7000 一个……
  • 此后的若干天,技术宅们一有空就在 YY 怎么从中捞一笔。
  • 2013.11.23 D 神发现了矿池,于是开始天天给我们洗脑做出来可以躺着数钱。
  • 2013.11.26 我终于受不了洗脑,决定推动开发矿池。

三、数据币——走向人生巅峰,要先登上这个小山包

  伟大的 D 神在给我们洗脑要做矿池的时候,心里已经想好了要做“数据币”的矿池。这个数据币又是什么鬼?炒过币的人可能知道除了比特币之外,还存在着很多的山寨币。而数据币只是茫茫币海中一种非常普通的山寨币。

山寨币

  山寨币,其实英文是 alternative coin,其实这两个名字都挺确切的,就看从什么角度去看山寨币了。

  从技术创新角度看,山寨币只是在比特币之上做了点微创新。比特币提出了一整套金融体系,当之无愧是革命性的创新。而山寨币,说白了只是 fork 了比特币的代码,然后改了其中的一些参数而已。比如虚拟币市场中仅次于“金币”比特币的“银币”莱特币,作为最大的山寨币,主要也就三点微创新:改多了货币总额,加速了转账时间,然后把挖矿算法从 SHA-256 改成了 Scrypt。看起来确实挺山寨的。

  但是从应用的角度看,这些参数改动并不是毫无根据的,每个改动都可以成为山寨币的卖点。转账时间更短,可以让虚拟币交易更快捷,提高其实用性。而挖矿算法上,Scrypt 算法更适合 GPU 计算,相对而言更平民化。要知道比特币挖矿就是因为用了 SHA-256 算法,在 2013 年之后算力已经完全被专用集成电路(ASIC)垄断[引用],GPU 矿工根本没法获得收益。算力被垄断,对于虚拟币来说是很危险的,这点上,莱特币相比比特币稍微安全一些。这么看来,山寨币确实也是 alternative 的。

  制造一种山寨币,在技术并不难,难的是推广和运营。比如先要让人相信这个山寨币不是骗钱的(作者没有预挖之类);然后文案上要把“改参数”的故事说好,让人认为这是革命性的创新,值得去投资;最后对于特别山寨的山寨币而言,要想方设法让币上交易平台,上越大的交易平台,大家都认可度就越高。

  对于山寨币,中国比特币首富李笑来也曾公开表示他不会投资山寨币,他认为山寨币的创新和比特币比起来,根本算不上创新,不会长存。其实很多人也都是这个观点,但是芸芸众生就算后知后觉了首富的观点,也不见得就有觉悟不去碰山寨币。因为大家都相信,自己不是最后一个“接盘侠”。

比特币莱特币质数币数据币
btcltc
xpmdtc
SHA-256,ASIC挖矿Scrypt,GPU挖矿质数序列,CPU挖矿质数序列,CPU挖矿

数据币和质数币

  回到数据币,当然也是些微创新。数据币其实是一个在质数币上二次山寨的山寨山寨币。质数币的微创新,主要在其挖矿算法上。它需要矿工挖到足够长的质数链(第一类坎宁安链,第二类坎宁安链,和双坎宁安链)来证明其工作力投入。矿工在投入质数币挖掘的同时,也是在试图挖掘世界上最大的质数序列,有不少世界纪录是质数币创下的[引用1引用2]。从这个角度看,质数币可以算是一种有用的币。

  那数据币又在质数币上加了什么微创新呢?数据币在转账时可以带一句话,或者带个种子文件。以后这条信息就会永远留存在数据币的 P2P 网络中,可以真正的“永流传”,当然前提是一直有人用……

  D 神为什么选了数据币呢?

  第一,质数币或者数据币所用的质数挖掘算法在当时只能使用 CPU 来挖矿。比起比特币的矿卡和莱特币的显卡,CPU 挖矿更难被垄断,更平易近人。其实我们要是去做显卡币,自己都没机器测试。

  第二,数据币一直没有出现公开的矿池。这对于我们来说是个绝佳的切入口。虽说这个山寨币小的连矿池都没人来做,但是从 D 神在数据币矿工群里混了几天所了解的情况来看,矿工的激情以及整个币的体量还是足够养活我们的。

  在干活之前先画个大饼。所有矿池都会宣传自己的挖矿效率,列出最近出了多少区块(区块是挖矿的最小单位,一“块”矿里有 N 个币,N 在一段时间里基本不变)。同时我们也知道矿池的手续费是多少,所以很快就可以算出矿池的收入。当时质数币的第三大矿池(一共就三个),日入过万!YY 着我们从中随便抢点零头,也是非常可观的啊。

零、开坑按

  2016年5月2日有新闻报道称,澳大利亚企业家克莱格·莱特(Craig Wright)拿出了一些证据证明了他就是比特币之父“中本聪”。回想起三年前,在比特币最火爆的时候,自己也曾参与其中。这段经历可能非常独特,故在此与大家分享。

  2013 年秋天,大妈们在跳完广场舞后开始讨论比特币。比特币的价格从年初不到 100 元一个,一路飙升到 7000 元一个,深受投机人士的欢迎。就在这样的环境下,我们几个技术宅开始 YY自己能从这一波中捞点什么。当时我们发现了这些致富大法:1. 炒币,缺钱缺运气。2. 挖矿,缺机器,其实也是缺钱。3. 套利,缺钱。4. 做交易平台,似乎政策风险太大了,不敢做。 5. 做矿池,似乎只要技术。对于没钱没背景只有技术的我们来说,显然第五种致富大法,也就是做“矿池”看起来更值得一试。

一、矿池是什么,能吃吗?

  想知道矿池能不能吃,就要先从挖矿说起。“挖矿”是生产比特币的过程,从“挖”这个字不难猜到,“挖矿”是一件费时费力同时需要一定运气的事情。如果用最原始的手段来挖矿,我们需要先下载一个比特币客户端(通常也叫比特币钱包,毕竟一般只用它来放“钱”),等客户端自动同步完比特币网络上的数据后,就会自动开始挖矿。

  从技术上看,挖矿是个什么样的过程呢?简单的说,挖矿就是在算哈希,地球上所有想挖比特币的矿工一起算哈希。大约每 10 分钟,比特币网络就会公布一个基准字符串。收到这个字符串之后,所有挖矿的机器,都要做同一件事情:在这个基准字符串的后面加上一个随机字符串,并且希望这个合成的这个字符串,通过两次 SHA-256 哈希算法后,得到的输出,前面要有足够多的 0。

SHA-256(SHA-256(“基准字符串” + “随机字符串”)) = 000000000000…01011101…

  到目前为止,SHA-256 还没被破解,也就是说,我们还没有办法通过构造原串,得到期望的输出。所以对于我们勤劳朴实的矿工而言,唯一能做的就是,不断地替换随机字符串,直到人品大爆发,随机到我们想要的结果。比如,现在如果要挖到比特币,需要哈希结果中,前面有 67 个 0。这是个什么概念?大概是说,我们如果不停地随机,不停地哈希,会有 2 的 67 次方之一的概率,随机到我们想要的结果,然后就挖到一大块比特币,里面大概有 25 个比特币(具体数值因时间而异)。简单估计一下,$2^{67}=147573952589676412928$,如果我们的机器非常快,1 秒算 1 亿次哈希,那么……运气正常的话,大概需要46795.4 年才能挖到比特币,这可真不是一般人玩得起的。

  既然单干玩不起,那就大家一起来吧,这便有了矿池。矿池就是一堆人一起挖矿,挖到矿之后,根据大家投入的算力按比例分配收益。只要在矿池挖矿的人足够多,小算力的矿工就可以收到小额但是很稳定的收益,而不像独立挖矿的时候那样(行话叫 solo),平时影子都没,突然有一天变成爆发户。矿池的收入模式非常简单,就是手续费提成。这简直就是一个一本万利的项目啊,成本可能就是几台服务器,做出来躺着数钱就行了。

前前后后写论文也有将近一年的时间了。这个研究的课题到目前还比较热门,在此分享博士论文。希望读者有所收获,少走一些弯路。

论文下载地址:http://pan.baidu.com/s/1jGWmmZO
arXiv 地址:https://arxiv.org/abs/1611.05962

感谢赵老师的指导,以及各位老师同学的宝贵建议!

有什么疑问或者发现什么问题都可以直接在这里评论。

  自认为这是一篇有用的文章,因此在发表之前先放到 arXiv 上,供大家参考,请批评指正。
  论文地址:http://arxiv.org/abs/1507.05523
  实验代码地址:https://github.com/licstar/compare

  准备这篇论文大概花了半年时间,从去年 11 月开始做实验,到今年成文。期间消耗大约 10 万 CPU 小时,然后在几十万个结果里面做人肉数据挖掘,最后得到了一些可能有用的结论。

  看标题就能够猜到论文的大概内容了,就是希望找到一种简单有效的词向量学习方法。怎么会想到做这件苦逼的事情呢?半年之前在我准备上一篇论文的时候(RCNN,强行广告,欢迎引用,文章点此,上面的常用链接里有文章相关信息)有一个意外的发现:选用不同的词向量作为模型的初始值,效果的差异非常大!!!当时正值我已经调了好几天参数,黔驴技穷之时,没想到手滑用错了个词向量,效果突然变 NB 了。这下我就脑洞大开了,要是能搞出个宇宙第一词向量,岂不是以后可以随便灌水?后面的故事肯定就能猜到了,半年之前我掉进这个坑里,只为寻找这个“宇宙第一”词向量。然后我实现了几个主流的词向量模型,遍历了各种语料和参数,榨干了实验室机器的空闲资源跑词向量,从生成的几十万个词向量里寻找规律,最后发现“宇宙第一”词向量应该是不存在的,不过生成一个好用的词向量还是有一定套路可循的。为避免某些读者对后面冗长的文字没有兴趣,我先用最简单的话描述我们发现的这个套路,那就是:

首先根据具体任务,选一个领域相似的语料,在这个条件下,语料越大越好。然后下载一个 word2vec 的新版(14年9月更新),语料小(小于一亿词,约 500MB 的文本文件)的时候用 Skip-gram 模型,语料大的时候用 CBOW 模型。最后记得设置迭代次数为三五十次,维度至少选 50,就可以了。

  似乎结论很“显然”,不过这种简单的策略,真的就这么有效吗?word2vec 在半年前已经是最流行的词向量生成工具了,它之所以能这么流行,除了头上顶着 Google 光环,很大程度上是因为这是一个好用的工具包,而不一定是因为它的效果就是最好的。质疑 word2vec 的效果,是因为它的两个模型(Skip-gram 和 CBOW)相对前人的模型做了大量简化,去掉了词序,又去掉了神经网络的隐藏层,最后变成了一个 log 线性模型。我主观地认为,这些简化是会影响词向量的性能的,毕竟词序很有用(上面提到的我的上一篇论文就是讲这个的,再次广告);然后神经网络的表达能力也比 logistic 回归要强得多。这两部分简化到底会带来多少损失,还是要靠数据来说话。所以我考虑的第一个问题就是,什么模型效果好。前面博客提到过的经典模型(NNLM、LBL、C&W)在 word2vec 出现之前,是最主流的方法。这些方法就保留了词序信息,而且也有隐藏层,都放到一起比较。有了这些模型,还缺一个只保留词序,但没有隐藏层的模型。所以我设计了一个中间模型,叫 Order,保留了词序,但没有隐藏层。最后再加上开源的 GloVe,一共比较了 Skip-gram、CBOW、Order、LBL、NNLM、C&W、GloVe 这 7 个模型。

  那么最严峻的问题来了,怎么样才算好的词向量呢,应该用什么指标来衡量呢?Mikolov 推广 word2vec 的时候,设计了一个经典的指标(king-queen=man-woman),由于有开放的数据集和评测代码,后来大量的工作都只用这一个指标来评价词向量。我觉得只用这个指标肯定是不对的,为什么词向量非得有这种线性平移的特性啊,如果说这是个加分项,还说得过去,但要成为唯一衡量标准,似乎没什么道理。我觉得 GloVe 那篇论文就做的很好,里面比较了一大堆指标。所以我就从根源想起,我们拿词向量是用来干什么呢?①有人拿它寻找近义词或者相关词,直接根据向量空间里的距离远近来判定词的关系。②也有不少早期的工作,直接拿词向量做特征,在现有系统中加入词向量作为特征。特征嘛,就是要个多样性,虽然不知道词向量包含了什么信息,但是说不定就带着新的信息,效果就能提升了。③还有大量基于神经网络的工作,拿词向量作为神经网络的初始值。神经网络的初始值选得好,就有可能收敛到更好的局部最优解。好,就是这三种指标了:语义特性、用作特征、用作初始值。基于这三大类用法,我们具体找了 8 个指标,进行比较,综合评价词向量的性能。(题外话,其实我从一开始就很想找到一个终极指标,用一个指标定江山,但是自从看到这 8 个指标的结果如此不一致之后,我终于放弃了这个念头。)

  为了公平的比较,肯定需要设定一个相同的语料,所有模型都用同样的语料来训练。选个什么语料呢?脑海中的第一反应是:大语料。其实小规模语料(几十兆) Turian 在 2010 年已经做过一些实验了,比较了 C&W 和 HLBL 这两个模型。大规模语料下,结论会不会不一样呢?找了两个大语料,维基百科英文版和纽约时报。此前,主流论点是:语料越大越好。所有语料都堆到一起,不管是什么内容,我语料越大,涵盖的语义信息就越丰富,效果就越好。GloVe 和 C&W 都是这么干的。自然,我也把这两个大语料混合在一起训练了。同时由于好奇心,我还把这两个语料单独拿出来训练词向量。反正就是挂机跑嘛。另一方面,为了验证一下是不是真的语料越大越好,我也在大语料中抽了 10M、100M、1G 词的子集,顺便验证一下别人的结论。说到这里我忍不住剧透了,实验中最意外的结果就是,语料对词向量的影响比模型的影响要重要得多得多得多(重要的事说三遍)。本来我只想固定一个语料,公平比较模型,结果却发现语料比模型重要(抱歉,是四遍)……后来又加了一个小规模的 IMDB 语料,进一步证实了这个结论。实验结果就是这样。

  当然,为了公平比较,还有一些其它的因素需要考虑,比如上下文窗口都开成一样大,词向量的维度也需要统一。还有个很重要的参数,那就是迭代次数。这些词向量模型都是用迭代方法优化的,迭代次数肯定会影响性能。对所有模型固定迭代次数可能不合适,毕竟模型之间的差异还是蛮大的。所以为了省事,我们对所有实验迭代足够多的次数,从 100 次到 10000 次,取最好的那次。做了这个决定之后,人省事了,机器累死了,而且对于每个语料、每个模型、每个参数,还要保留不同迭代次数的词向量,很快就把 3T 硬盘塞满了……一边删数据,一边继续做实验……这种暴力手段肯定是不适合实际应用的,所以我们也努力从实验数据中寻找规律,希望找到一种合适的迭代停止条件。

  最后总结一下,我们认为模型、语料、参数三方面会影响词向量的训练,所以从这三方面入手分析到底应该怎么生成一个好的词向量,论文由此展开。在博客里主要就介绍一下不方便写进论文里的故事和背景。

—————-枯燥开始的分割线—————-

  简单列举一下文章的贡献。
  模型方面,因为所有的词向量模型都是基于分布式分布假说的(distributional hypothesis):拥有相似上下文的词,词义相似。这里有两个对象,一个是我们需要关注的词(目标词),另一个是这个词对应的上下文。所以,我们从两个角度去总结模型:1.目标词和上下文的关系,2.上下文怎么表示。在这种分类体系下,我们对现有的主流词向量模型进行了总结和比较,发现,目标词和上下文的关系主要有两种,大多数模型都是根据上下文,预测目标词。而 C&W 模型则是对目标词和上下文这一组合,打分。实验发现,通过上下文预测目标词的模型,得到的词向量,更能捕获替换关系(paradigmatic)。在上下文的表示方面,我们分析了几种表示方法之后,发现可以通过模型复杂程度对这些模型进行排序。排序之后,实验结果就容易解释了:简单的模型(Skip-gram)在小语料下表现好,复杂的模型在大语料下略有优势。从实践中看,word2vec 的 CBOW 模型在 GB 级别的语料下已经足够好。我前面提到的 Order 模型,加入了词序信息,其实很多时候比 CBOW 更好,不过带来的提升并不大。

  语料方面,很多论文都提到语料越大越好,我们发现,语料的领域更重要。领域选好了,可能只要 1/10 甚至 1/100 的语料,就能达到一个大规模泛领域语料的效果。有时候语料选的不对,甚至会导致负面效果(比随机词向量效果还差)。文章还做了实验,当只有小规模的领域内语料,而有大规模的领域外语料时,到底是语料越纯越好,还是越大越好。在我们的实验中,是越纯越好。这一部分实验数据比较丰富,原文相对清楚一些。

  参数方面,主要考虑了迭代次数和词向量的维度。其实词向量都是迭代算法,一般迭代算法都需要多迭代几次。旧版的 word2vec 只迭代了一次,效果很受限制,换新版就好了(也欢迎用我们论文实验的方法,用 AdaGrad 优化,相比原版 word2vec 的学习速率下降法,这样还能在之前的基础上继续迭代)。然后迭代次数怎么选呢?机器学习里很常用的迭代停止指标是看验证集的损失是否到达峰值,认为这个时候模型开始过拟合了。按照这个方法,我们可以从训练语料中分出一个验证集看损失函数的变化。但是实验中我们发现,这种策略并不好。主要原因就是,训练词向量的目标是,尽可能精确地预测目标词,这一目标和实际任务并不一致。所以更好的方法是,直接拿实际任务的验证集来做终止条件。如果实际任务做起来很慢(比如 NER 任务的开源实现大概做一次要两小时),文章还给了一种参考的方法,随便挑一个任务当验证集用,一般都比损失函数靠谱。
  词向量的维度则只有一些实验结果。做词向量语义分析任务的时候,一般维度越大效果越好。做具体 NLP 任务(用作特征、用作神经网络初始化)的时候,50 维之后效果提升就比较少了。这部分的结果很依赖于具体任务的实现,或许用上了更先进的神经网络优化方法,词向量作为初始值带来的影响又会有新的结论。

—————-枯燥结束的分割线—————-

  完全公平以及全面的比较是很难做到的,我们也在尽可能尝试逼近这个目标,希望这些结论会有用。请批评指正。当然,也非常非常欢迎引用~~~~~

  一直觉得量子计算很神秘。从各种消息看来说什么量子计算机利用平行宇宙来计算,有超强的并行能力,穷举解决一切问题等等。真是越听越玄乎。维基百科和知乎上的一些解答还是看得让人茫然。

  几天前上古神犇JYY推荐《Algorithms》一书的第十章作为量子计算的通俗入门读物。阅后豁然开朗,不敢独享,略做笔记。

  普通计算机一个比特(bit)可以表示 0 或者 1。而量子具有不确定性,这使得一个量子比特(qubit)需要描述成 $ \alpha_0 \left|0\right\rangle + \alpha_1 \left|1\right\rangle $(量子比特的 0 状态记作 $\left|0\right\rangle$,1 状态记作 $\left|1\right\rangle$),其中 $ \left|\alpha_0\right|^2 + \left|\alpha_1\right|^2 = 1 $。也就是表示了这个量子比特有 $\left|\alpha_0\right|^2$ 的概率是 $\left|0\right\rangle$,有 $\left|\alpha_1\right|^2$ 的概率是 $\left|1\right\rangle$。这里面的 $\alpha_0$ 和 $\alpha_1$ 都是复数,所以要先取模再平方才是其概率(感谢 @shuyechengying 的指出)。
  推广到两个量子比特为: $ \alpha_0 \left|00\right\rangle + \alpha_1 \left|01\right\rangle + \alpha_2 \left|10\right\rangle + \alpha_3 \left|11\right\rangle $。同样系数的模的平方和为 1。

  这里就能看出量子计算的 NB 之处了,普通计算机 $n$ 比特可以描述 $2^n$ 个整数之一,而 $n$ 个量子比特可以同时描述 $2^n$ 个复数(一个 $2^n$ 维的复数向量)。

  如果仅仅是有表达向量的能力,传统的向量机就可以搞定。量子计算机之所以能成为量子计算机,更在于其对于量子比特的特殊计算操作。一个很著名的计算单元是 Hadamard gate,输入 $ \alpha_0 \left|0\right\rangle + \alpha_1 \left|1\right\rangle $,输出 $ \frac{\alpha_0 + \alpha_1}{\sqrt{2}} \left|0\right\rangle + \frac{\alpha_0 – \alpha_1}{\sqrt{2}} \left|1\right\rangle $。不要小看这个操作,即使仅仅对 $n$ 个量子比特中的第一位进行了 Hadamard gate 运算,所有的 $2^n$ 个系数都会改变(书上有简单的证明)。这里有个量子纠缠的概念,没去深究了,大意就是量子的特性会让这些运算比向量机的逐元素运算要强大很多。

  最后,薛定谔的猫要出场了。怎么把计算结果从混沌的量子态变成我们可理解的结果呢?这就需要“观测”。每次观测,量子就会塌缩到 $\left|0\right\rangle$ 或者 $\left|1\right\rangle$,按照 $\left|\alpha_0\right|^2$、$\left|\alpha_1\right|^2$ 的概率。反复测几次就知道结果了。但可惜的是,量子存在一个 No-cloning theorem,这就导致了我们不能简单地“反复观测”量子计算的结果了,而需要反复计算+观测。

  借助量子计算机,FFT 的复杂度可以降低到 $O(\log ^2 (n))$,甚至连读一遍数据的 $O(n)$ 时间都不用,因为只要 $log(n)$ 个量子比特就可以描述 n 维向量了。利用高性能的 FFT,因子分解的复杂度可以达到 sub-exponential time [Shor’s algorithm],RSA 加密就失效了。

  但是传说中量子计算机目前还是不能有效地解 NPC 问题,可以参考维基百科 BQP 的介绍。

  总结一下,从我目前理解来看,量子计算的优势主要在因子分解上(借助更快的 FFT)。对于传统的 NPC 问题,量子计算机目前还是束手无策的。当然这种强大的表达能力和计算能力还是非常有潜力的。

  我也就看懂了这些,至少不觉得量子计算这么无敌了。最后,还是建议看原书,写的非常清楚。

  这篇博客是我看了半年的论文后,自己对 Deep Learning 在 NLP 领域中应用的理解和总结,在此分享。其中必然有局限性,欢迎各种交流,随便拍。

  Deep Learning 算法已经在图像和音频领域取得了惊人的成果,但是在 NLP 领域中尚未见到如此激动人心的结果。关于这个原因,引一条我比较赞同的微博。

@王威廉:Steve Renals算了一下icassp录取文章题目中包含deep learning的数量,发现有44篇,而naacl则有0篇。有一种说法是,语言(词、句子、篇章等)属于人类认知过程中产生的高层认知抽象实体,而语音和图像属于较为底层的原始输入信号,所以后两者更适合做deep learning来学习特征。
2013年3月4日 14:46

  第一句就先不用管了,毕竟今年的 ACL 已经被灌了好多 Deep Learning 的论文了。第二句我很认同,不过我也有信心以后一定有人能挖掘出语言这种高层次抽象中的本质。不论最后这种方法是不是 Deep Learning,就目前而言,Deep Learning 在 NLP 领域中的研究已经将高深莫测的人类语言撕开了一层神秘的面纱。
  我觉得其中最有趣也是最基本的,就是“词向量”了。

  将词用“词向量”的方式表示可谓是将 Deep Learning 算法引入 NLP 领域的一个核心技术。大多数宣称用了 Deep Learning 的论文,其中往往也用了词向量。

继续阅读