去年与未来

2018年我终于毕业开始工作了。还好当年家里穷,没有选择物理系,否则就与计算机无缘了。一年年的人生经验积累下来,发现自己干的蠢事、令自己回想起来尴尬不已的事、令别人当场窒息的事越来越少。随着年龄的增长,我也发现自己的计算能力慢慢下滑,还好目前的经验增长完全可以抵消算力的下降。2018年我学会了很多技能,一条条来说。


绘画

创造自己理想的二次元对象是我几年来一直想干的事,苦于时间、设备以及其他因素,一直没有付诸行动。我自认为我是绘画天才,尤其对色彩的把控简直天生精准。缺点是,我是写实主义的,追求精确的形与结构,缺乏想象力,创造性不强。为避免老了学得太慢,我在今年年初,终于找到了时间,开始绘画。借助网络、各路大触的教学,我画了一个月,走完了用笔、复印、抄袭、临摹和创作的过程。总共画了10副图,其中有两幅比较有代表性。左图是我情感想画的,右图是我理智想画的。

新子憧 薇尔莉特·伊芙加登紫罗兰

希望老了还有欲望画这样的图,不得不说,我真的是天才。


拍照

由于要去日本玩,所以搞了相机。不过相机是我大三就想买的,苦于没有时间和金钱,一直拖到了今年。我已经按下了7813次快门,其中10%的照片不错到可以给别人看,另20%的照片自己觉得不错,还有10-20%的照片是全景或延迟摄影的额外文件。这样算下来,也就是约50%的快门是浪费的。我不敢说自己是摄影的,只敢说自己是拍照的。因为我的照片都需要经过修改。当然我不会过分修改,P图的原则是改变色彩、保留结构。照片中的人(无意义的人)不属于结构,属于需要抹去的噪点。这么多照片中,有一组我不得不亮出来。

 

这不是说图3有多好看,而是化腐朽为神奇的过程。不要因为表面的难看,而错过内在的好看。希望我能保持这样的眼光,无论对照片、对风景还是对人,要拓展发现美的能力。


日语

我也抽出了不少时间学习日语,虽然差18分考过N2,我已经心满意足,至少我能知道机器翻译错在哪里,目前来说足够了。明年我希望接触韩语,比较简单一点,顺便学习印地语。我深知自己的语言天赋实在没有,跟绘画天赋一比就是负的。不过,不能躺在舒适区,有天赋的要突破极限,没天赋的要达到平均。韩语是2年计划,印地语是20年计划,印度可能就是未来的中国,或许将来需要跟印度人打交道,准备可以先做起来(开个玩笑)。主要是想对比一下,印度文化和中华文明,为什么会产生差距。


视频制作

爱因斯坦会弹小提琴,我语言天赋没有,音乐就不用说了。我只能朝另外的方向发展,别提书法,写的字就像画的。视频是一个方向,视频就是连续的画,也涉及光影、色彩。今年,我做了几个傻视频放抖音,获得了尴尬的点赞,希望明年能熟练运用AE和PR,获得百赞。


 

以上这些技能基本上都跟目前的工作无关,跟工作相关的基本上就是机器翻译。工作在有条不紊地进行,具体也不便说明。团队中的同事知道自己强在哪、弱在哪,这是值得庆幸的。头条还是很不错的厂,我做着自己喜欢的事、自己想做的事,还可以拿钱,感觉人生已经到达了巅峰。

 

毕业本可以拿上海或者北京户口,但是我没有行动,家里有地只是一个原因。如果没有地,我慎重思考之后,基本上也不会放弃我的农村户口。大家都在随波逐流,大家根本没有思考过户籍制度的不合理性。希望在我有生之年,这个等级划分可以取消。目前,我会拿一些不科学的事实安慰自己,江浙人才辈出,北京呢?等。希望我这个随便做的决定,不会在将来引起太大的麻烦。

另外,今年也到了几个地方,日本、海南、杭州、北京、上海、苏州。明年我想去祖国的西边,如成都、重庆、武汉、长沙,日本肯定要再去一次。

毕业之后,身边的好友、打dota的好友换了一波。这是好事也是坏事,证明我不是一滩死水。

希望明年我的博客能月更。

 

Python再入门

会用一门语言写代码并不能说是掌握了这门语言,尤其是入门容易的语言,掌握起来更困难。你说你会用汇编写程序,我可以承认你掌握了汇编语言,而你说会用Python写程序,十有八九真的只是会写而已。

有基础的哥们,Python能在3分钟内入门,其他解释性语言也是如此,不用花太多时间,就能说我会了,只要能让我查google。我从大一(2011)开始接触Python,几乎每个月都在学新的内容。当然,我新学的内容不是Pythonic。


Python基础库

我很久以前买了一本很厚的《Python标准库》,结果才看了不到100页。最近,我才真正认识到标准库的作用。此处,我不会考虑Python 2,我只会Python 3。

unicodedata

unicodedata对我这样的多语言工作者还是比较有用的,目前这个库只提供了各个unicode字符所属的类别,如’我’是符号类别(‘Lo’),’1’是数字类别(‘Nd’),这样我就不用翻wikipedia了。不过,这个库也没有完全满足我的需求,经常需要查阅unicode各个块(unicode_block)的信息。

collections

提供了除基础容器list,tuple,dict,set以外的容器,我比较常用的是namedtuple和defaultdict。defaultdict就是在赋值的时候不用手动初始化,考虑简单的计数问题(仅仅是实例,可以用counter)。

复杂一点的,如针对各个字符串统计各字符数的defaultdict,也就是defaultdict的defaultdict。

array

与list类似,不过array的数据存储更加原生,而且array中的数据类型得一致,就像C/C++一样。看例子就明白了。

此处我反正是搞不懂为啥a只用87624个bytes,这乘法不对啊。40064就容易理解多利,10000个整数,每个4 bytes,加上数据结构64 bytes,这个空间直接省了1半。当然也可以用numpy。

linecache

随机读取文件行,小文件可以,大文件直接内存爆炸。

timeit

规范的测试几行代码速度的工具,使用也很方便。

operator

我曾经一直不理解operator的用处,跟内置的运算符重复,实在没有,也可以使用lambda。后来我搜索了一下,stackoverflow,stackoverflow2,给了我一点启示。主要出于速度、代码可读性、以及picklable考虑。关于速度可以简单测试如下,

itertools

与operator一样,属于函数式编程模块。借鉴了Haskell等语言的思想,实现了大量高效、内存友好的迭代工具,与operator相辅相成。官方介绍中也提供了很多例子。我用得最多的居然是chain.from_iterable(),我觉得我得改。takewhile这些都很不错。

functools

与上面两个一样,属于函数式编程模块。其中的lru_cache是比较典型的操作,能很好的解释functools。另外wraps也是常用的,其他几个我还没有使用过。

typing

使用type hints可以帮助我们在编码时就发现bug,当然借助的是ide的提示。mypy也可以进行代码检查,可以告知哪里类型有问题,可以及时发现处理。

其他

其他很多库,如heapq,bisect,re,threading,multiprocessing,profile,pdb,json等都是非常常用好用的库。如果能灵活使用这些库,必定能提供编码效率。此处效率包括编程速度和运行速度。估计我也就掌握了1/10左右。


每个半年回过头看自己写的老代码,总感觉非常羞耻,想推倒重来。我觉得这是好事,说明人还在进步学习。希望还能保持这个速度。

链接

  1. Python 3基础库

 

Sequencing——简单的序列到序列学习框架

这个月断断续续自己写了一个seq2seq代码,我也不确定这是否可以算是框架,就姑且定义为框架了。为什么要自己写呢,因为我觉得现有代码组织都写得太复杂,明明是很简单的原理,这样不利于实验室同门入门。我一共写了3700行代码,包括额外的numpy版本的sequencing_np (1000行)和tests (800行)。吸收各个大牛的实现(blocksseq2seq)的优点,个人认为我的实现已经非常清晰简单,贯彻整个项目的准则是“keep simple, stay na(t)ive.”

我以英中机器翻译为实例来介绍什么是sequence to sequence learning以及如何使用sequencing来训练自己的模型。可以参考Github的代码。为方便大家上手,我也准备了预处理过的数据,见百度云。这份数据包含14590228条平行句对,来源于UN数据集。有兴趣的同学可以根据这些数据训练自己的英文到中文的机器翻译模型。


数据准备

数据是必不可少的,我们可以自己爬,比如有道啊,句酷啊各种有双语例句的网站。大约500万就可以开始训练了。然后需要准备单词表,也就是需要把各个单词映射到一个整数id,否则接下去没法搞。对于英文,我们可以使用空格分隔的单词或者流行的BPE。为了简单起见,中文端我们就不分词了,直接以字作为单位,如“我、们、是、中、国、人”。这样词表小,效率高,也不怎么影响翻译效果。这些都准备完毕之后,只要产生平行句对来训练就行了。当然得考虑补零、效率等问题,这些我都写好了,详见build_inputs.py。只需调用如下,


编码器

目前只实现了双向RNN编码器,将来会实现CNNself-attention编码器。编码器是很简单的,见下图。

encoder
编码器展示

首先,我们需要通过代表单词的整数index将单词表示为向量,这都是通过一个Lookup Table(Embedding Table)实现的。说白了,就是把一个M×N的矩阵的第index行取出来,M为总的单词个数(可以试试32K),N为向量的大小,512足够了。

接着,就是通过双向RNN编码这些向量,来形成表示单词在句子中的意思。多层的编码器性能当然更好,训练也更久。


注意力机制

注意力机制是机器翻译必不可少的,可以说就是注意力机制的引入导致了机器翻译的变革。更难得的是注意力机制非常简单,具体来说就是给定查询query(通常是decoder RNN的输出),来匹配编码器的输出。写成代码如下,

当然,这个代码是不精确的,得考虑维度和batch。我们可以根据context翻译下一个词。比如翻译“I love summer”时,“我喜欢”已经翻译出来了,query就是当前解码器的状态,与编码器的输出比对,context就是表示“summer”的向量,据此很容易就能翻译出“夏天”。在sequencing里,可以很方便地调用,如下。


解码器

解码器可以算是最复杂的。因为要考虑反馈,还要使用注意力机制。不过,隐藏细节后很简单,如下,

具体实现不展开,可以见源代码。我也简单地画了一个图。feedback就是要把之前翻译的词反馈给解码器(feedback也包含一个Lookup Table,需要把词表示为向量,这里没有画出来),这里的解码器是一个RNN,起到语言模型的作用。

 

decoder
解码部分的简单展示

训练

光有模型不行,得有loss才行。如上图所示,每反馈一个单词,就会输出一个新的单词,直到输出结尾符。因此,第一个反馈的是开始符(BOS),然后可以产生logits(可以理解为概率),将logits与目标输出(“我”)对比得出loss。cross_entroy是最常用的loss。接着反馈“我”,输出logits与“喜”对比,得出下一个loss,以此类推,直至结束。这个过程,我们以dynamic_decode表示,这类似于seq2seq,但被我简化了,去掉了复杂的操作。

就像上面介绍的,我们定义loss的方式如下

有了loss就简单了,选个优化方法进行优化就行了。最常用的是Adam,也可以使用SGD。


推断

Greedy推断和Beam推断都行。不得不提batched beam飞快,60s解码2000句。详见代码,因为不重要,所以这里不展开。


性能

按默认设定(1层1024的编码器,1层1024的解码器), 经测试,每天能训练3千万平行句对。飞快!结果也不错。batched beam更快,60s解码2000句,同样的训练集和参数,效率和效果貌似比seq2seq好一点,有条件的同学可以自行验证。


只依赖numpy的版本,Sequencing_np

这个厉害了,很有意思。我写了一个相互对应的版本,实现了LSTM等基本单元,也实现了整个sequencing的模型,但只依赖numpy。既可以作为测试,也非常有趣。但不能训练是肯定的,我们需要把训练好的模型传给sequencing_np。原理都是一样的,优点是我们可以print!debug方便很多,更装b的是,可以直接在Android进行翻译!我真是机智。

android
在Android上的机器翻译(模型都在Android上)

 

写得比较仓促,不清楚的地方还请留言指出。