标签

2015年7月3日星期五

2015春招实习求职总结

拖到这学期期末最后一周才来写这个,只能说是自己拖延症太严重。四月份不再有面试的时候只是想沉淀一段时间再来写,然而拖到现在也只是一个拖延症的借口了。
这学期开始的时候,某个晚上宿舍都在写自己的简历,我自然也在写了。说这点只是想说这学期的所谓面试准备其实是很不充分的,至少是没怎么实现寒假所计划的那个样子。然后短短的寒假也基本都是在美赛后在家里写写代码看看机器学习,各种基础学科都没开始复习。也只是简单地用Word参照网上的模板做了一下自己的简历。
算法,语言基础,网络,操作系统,数据库,这些课在三月初都是重点准备的内容。投简历的时候,面对一直纠结过来的问题:我应该做什么岗位,终于知道了答案。虽然自己唯一做过比较大的项目是用PHP写的一个网站,但其实自己对PHP和Web后台的理解还远远不到半点了解深入的程度,所以基本上都决定投和C++研发相关的岗位,然后打算靠着复习概念重新捡起来。等部分招聘信息开始通过同学转发等渠道传过来的时候,自己也已经知道各大公司的招聘主页才是最直接的投递方式,T厂和A厂的校友内推都投了,然而最后都是没有结果。
最早开始的笔试是本地的企业CVTE,在广工做试卷。印象其中最难的只有用C++实现STL中的vector的两个函数,和怎么找出一个无序数组中的中值。下午就是网易TTT计划的笔试题,比较记得的有判断大端小端的方式,堆排序的代码,一些SQL语句。最后一题是一个POJ上的题,1088。然而这题最后没想对方法硬用动规,其实只要记忆化搜索就好了。网易考完以后觉得发挥得不好,所以很意外能过。
CVTE的技术一面,也就是我的第一次面试,是去萝岗区的他们公司总部,面试官是一个三十多岁的大叔。他看完我的简历问我要不要去后台,觉得我不适合C++岗,我坚持要面,然后后面的表现估计让他很失望,因为真的暴露出自己很多的复习不充分。
首先是要写一个拷贝构造函数,没给定场景,于是我直接写一个很简单的给他。写完以后气氛是尴尬的。他一副“就这样了吗”的眼神,然而我并不觉得有太大问题。但其实想一下,拷贝构造函数在遇到指针拷贝的时候,其实是存在深拷贝和浅拷贝的问题的,那时的我当然知道,但一方面出于对自己的掌握程度不够自信,另一方面不如说是以前读书做题一个很自然的习惯,题目条件不充足时,按简单的写,防止自己写多错多(高考的一个解题策略),所以只是设想一些简单的变量赋值。这就涉及到面试的一个特点。面试既然本质只是一个考察你水平的过程,往往你不是答出一个“没说错”的答案就够的。如果场景不给具体你,你就应该以一个有工程素养的人的角度去思考,在不同情况下,应该是怎么样的。就像这题其实考察你对深浅拷贝的理解的意图很明显,只是我实在图样图森破。
还有印象的问题有,你觉得C++适用于什么场景,STL有什么性能限制,Deque和List的区别,红黑树的特点,这些我基本都没答好或者没答出。数据库里,B树和B+树又有什么不同呢?答错。C++一般有什么主流的编译器呢,只说出两个。Linux命令问出一个应用场景也问倒我了。带着不可能过的确信,我的第一次技术面结束了。
心理上,我了解到其实面试没有我想象中紧张,因为会和不会我都能从容地应对,包括我答错的时候,面试官的那种眼神也没给我带来太大压力。我那时复习得并不充分也略盲目,反而给了我一个复习的方向。
去科韵路网易总部的一面,面得更顺利,最后也是没过。印象问了机器学习和网络爬虫的一些问题,所以也讲到了美赛的题目。面试官手上有你的笔试卷,笔试的选择题有几题犯了低级错误也被挑出来问了,这大概有很大的扣分。
室友在CVTE的二面那天中午接到了神马的电话,我预感下午或者傍晚也会有电话打来一面,果不其然。问到设计模式,我说我最近在看四人帮的那本书,他让我举例。于是我讲到MVC和单例。然后引下去问C++中单例模式如何实现。面试官对编译原理的那个词法分析器和数字图像处理的雾霾处理都很有兴趣,于是我也讲了很久。还有就是C++的delete[]操作符是如何知道数组大小的,多态动态绑定底层是怎么实现的,进程通信的方法。算法很走运地被问到一个很熟悉的问题,是找出大数组的第K大个数。我讲了分别在堆排以及快排上的两种改进。这次的面试感觉发挥得挺好的。
感觉面试官其实很少会对编译原理的那个SQL解释器有兴趣的,然而这个面试官不但很有兴趣,似乎他本科这门课也学得很认真。至少他还记得龙书上词法分析器的优化,我讲的是双缓冲区的优化,他告诉我书上有另外的方法。关于去雾霾的那个算法他也提出了自己的看法。在这个交流过程中,如果你能让对方感受到你不但是一个能解决问题,而且对解决问题和获取新技能都有热情的人,那么真的会是一个大大的加分项。我想项目经历的意义更多在于此吧。正如曾坤老师所说的那样,重点不一定是你做了什么,更重要的是你怎么实现。在讲项目经历的时候,如果把重点放在你的优化过程是能够加分的。而这一点只要你都有认真做过那个项目的话,面试前回顾一下就足够了。
神马的技术二面是在星期五下午。因为在上图形学,只好约一个晚点的时间然后中途出教室在隔壁教室等电话。第一次进二面还是挺紧张。面了两次,每次都一个多小时。
都是一些更深入的问题,第一次问了预编译的命令里定义宏的局限性,一个项目的整个编译的过程分为什么,你用过什么调试工具和测试工具,在大项目中如何使用。讲到SQL解释器,因为我说到用map来实现数据库表,他便继续问红黑树的原理,和Hashmap和map的区别,如果要在我们的项目上添加Group By操作要怎么做,TCP的四次挥手是什么,Linux下文件去重的命令,还有一些爬虫的实现。
第一次还问了两个算法题,一个是在分布式系统下利用MapReduce找出重复的数,还有在大的整数数组中找出和绝对值最小的三个数。第二题我最后在面试官的引导下一步一步地想出了一个并非最优的解法:先排序,对于每两个数的组合,二分查找出他们和的相反数。第一次的一些知识性的题我其实答得模棱两可,因为有一部分是不太懂的,比如我甚至不知道C++有Hashmap,不确定MySQL是否能添加多字段索引,出了大洋相。
第一次过后从教室回到宿舍,还没歇下来电话就来了。新的面试官还是对编译原理的那个项目有兴趣,顺着我讲的项目经历,问文件IO的时候使用文件流读写的缺陷。基类指针转换为子类指针时的注意事项,一道估算一座跨珠江桥的车流量的思维题,一道概率的计算题。在MapReduce模型下,找出URL相同而排名不一致的搜索记录。他还问了一下我对机器学习的了解,于是我还是举美赛中K均值的那个例子。
面完下来已经快八点了。第二次感觉自己虽然面的时候很蛋疼,但发挥得不坏。思维题那里,题目的参数,比如说桥的长度,车速,他都要求我估算出来,而这是我始料未及的。后来我才想到条件的不足,因为需要加入安全行车距离。(这不就是上年美赛那题么=-=)我想我最后思路都讲清楚了。并且我也通过二面的几个算法题能够确认这是要做搜索引擎相关的程序。
结束以后我发现我阿里的状态已经变了,也就是神马和阿里其他的主干校招是一起的。后来就是在周末某晚俱乐部聚餐的时候突然打来的HR面,最后是过了。
腾讯没过的一面是我这次招聘季的最后一次面试。一开始讲进程通信,CPU调度的方法,感觉很顺利。但当问到MySQL有什么引擎,我只能列出两个而且不知道区别。数据库的事务讲不清楚。算法题,双向链表排序,使用O(1)空间,O(nlgn)平均时间,避免O(n^2)最坏,要怎么做?这道我现在还不懂问人也不懂的题大概成为压倒骆驼的最后一根稻草。腾讯也是妥妥地跪了。
总结下来,对通过BAT级别的研发岗面试应该做什么准备心里有了底:

  • 你说得过去的项目经历。不是说做得多就好,而是能够体现你技术能力与素养水平的项目。从这个意义上,如果你只有课程设计的项目也没什么大不了的。

  • 基础的知识。包括一些What型知识和其衍生出来的Why型知识,主要是网络,操作系统,数据库,设计模式和编程语言的知识,通过牢固基础和复习应对。

  • 算法。也就是How型知识,我想也只能通过多思考多练习来提高。进微软的室友整个面试过程几乎没有问What型知识,而是很多很多的直接写码的算法题。因为其实What型的知识你只要能复习得好都没什么问题,有些还只有那个答案,除非再问深入否则不能很好地反映你解决问题的能力。我想这是Google微软级别的企业和BAT的一个区别。

  • 使用工具的经验与能力。比如说测试工具,调试工具,Linux命令行工具等等,这些也是要靠平时积累的。感觉总体并不占特别大的权重,微软的面试也没有问。

  • 让对方感受到你能力水平的表达技巧。比如说你讲自己看某本书,某篇论文,某个课程上学习到了什么,你怎么把它们运用起来。在遇到不会或是不确定的问题时也不沉默,而是让对方看到你的思考过程。有人是能用实力把这个因素盖过去的。

  • 运气。当然不能说没。比如说我刚好碰上一个对编译原理有研究的面试官,或者刚好碰上一题思考过很久的算法题。有人是能用实力把这个因素盖过去的。

准备的书目除了基本的课本,对于C++来说,Effective,More Effective和深入探索对象模型是极好的。我反而没怎么看面试金典和编程之美。
总算没给软院拖后腿了。自己总结下来也是感触良多的。通过率奇低的这次招聘季,同时打击了自己的自负与自卑。离目标还有很远,还得看接下来的锤炼吧。
写于去杭州前,和两门科目考试修罗场之前。

2015年4月18日星期六

《丝路审判过后,Tor必须受到保护》

最早知道“丝绸之路”这一网站,是在刷Solidot的时候看到的。这起案件虽和互联网自由有关,但并没有像SOPA法案那样引起很大的公开争议与社会运动。原因大概是丝绸之路是一个犯罪网站(当然,有争议认为提供一个非法交易的平台不能视为非法)。
这是一篇来自英国报刊卫报的社论我的译文最早发表在译言网。

译文:

“丝绸之路”的策划嫌犯罗斯•乌布利希在纽约受审的法庭速写。图片:路透社

世上第一条信息高速公路并不是互联网,而是丝绸之路。自汉朝以降,这条四千英里长的贸易线路网络从中国向波斯和欧洲带来香料,调味品以及来自远方文明的前所未料的知识。二十五个世纪后,它却和一场关于数字隐私的白热化讨论牵涉到了未料的联系。
这个“丝绸之路”的名字,当然是和现在臭名昭著的在线非法商品及服务交易平台挂钩了。而它的核心策划创造者罗斯•威廉•乌布利希,近日在纽约联邦法庭因运作互联网上最大的毒品黑市而被裁定有罪。尽管丝绸之路现在被视为一家犯罪企业,对当局部分人来说更令人烦恼的是Tor,一款使得乌布里希的顾客们能够在不留下太多数字足迹的前提下访问他网站的匿名化软件。
Tor这款由非营利组织开发出的免费应用,在世界各地被公民用于规避来自政府的窥探和审查。像最早的丝绸之路一样,它给诸如阿拉伯之春中的活动家。自从其当局在上年封锁推特后的土耳其公民,和寻求跨越防火长城并连接到更广阔世界的中国公民的通信提供了一种另类方法。新闻工作者同样能使用Tor安全地接触消息来源、异议者和泄密者。
正如很多有益的技术一样,Tor孵化于政府的实验室——美国海军研究实验室,且联邦政府持续支持着它。根据Tor网站的财政声明,2013年其接受的拨款中约90%来自三家联邦机构,包括来自美国国防部的830,269美元。
但正如丝路审判所体现的,Tor同样可以被恶意目的的使用者将其隐私保护的一面变为黑暗通信的保护盾。因此,爱德华·斯诺登解密的文档显示,在美国资助Tor的同时,美国国家安全局以及其盟友情报机构也着力于破解它。
Tor并不是最近唯一受到攻击的隐私工具。政府官员也曾严惩在产品中植入加密技术的美国技术公司,如苹果最新的iPhone便使得用户数据可不受法律施行的影响。Google在安卓设备中也采纳了类似的技术。据报道,雅虎也在着手于一项加密电子邮件的项目。这些产品只是未来似乎将要出现的新一波先进隐私技术的顶峰。
Tor,加密型手机或即将出现的其他类型技术的先进特征满足了消费者在斯诺登的解密事件后,日益增长的对数字隐私权的需求。随着近期对植入诸如计算机硬盘等消费者设备的监控软件的报道出现,这种需求也许将会变得迫切,更不用说物联网使得监控的可能范围由笔记本电脑和智能手机扩大到汽车,家庭安防系统等数不清的个人装备这一事实。
可料想的是执法界并不迫切想见到隐私工具的实现。时代广场未遂爆炸案的袭击者,费萨尔·沙赫扎德,承认他通过加密技术来和他位于巴基斯坦的共犯联络筹划2010年的这场未遂袭击。
在这样的背景下,英国情报机构GCHQ的主任罗伯特·汉尼根[1]便表示美国的技术公司“已经成为恐怖分子和犯罪分子选择的指挥和控制网络,在他们发现自己的设备能和我们的一样转换身份后”。
英国首相卡梅伦,就其本人来说,提出了“不让恐怖分子有安全通信的渠道”的要求。如果预防犯罪是我们唯一看重的事情,这个呼吁会是很有吸引力的。
然而退一步讲,我们应该看出关于禁止存在积极意义的隐私工具的讨论是多么荒谬。其中的积极意义便包括对最早由美国最高法院大法官路易斯·布兰迪斯提出的“不受干扰的权利”的巩固。
理解执法机构面对的挑战,终究不代表我们需要放弃可能被违法者使用的任何设备。难道我们应该因为可能被毒枭利用而禁止传呼机,手机和笔记本电脑吗?
在对恐怖主义极度敏感的时期,我们仍应该同等地警惕使得政府监控项目得以渐进的对隐私的侵犯。在2010年曾被禁止使用黑莓通信的沙特阿拉伯及阿联酋公民会同意这点。最近意识到美国禁毒署实时监控全美车辆动向的牌照追踪计划的美国车主同样会同意。
公元前200年的丝绸之路,如同现代的隐私工具一样,都带有被误用的风险和可能性。它会在沿路携带传染病,或是吸引以珍贵货物为目标的盗贼。去年,它被评为联合国教科文组织的世界遗产。若没有丝绸之路,世界会变成怎样似乎是难以想象的。

作者Craig A. Newman,Richards Kibbe & Orbe LLP的任事股东,系Freedom2Connect基金会的CEO——一个以华盛顿为总部的主张通过技术来促进网络自由的非营利性组织。

[1] 原文疑误写为Roger Hannigan,实际现任GCHQ主任为Robert Hannigan(译者注)



2015年2月24日星期二

最好理解的贝叶斯分类——朴素贝叶斯分类器

《机器学习》里说的是贝叶斯学习。不过其实数据挖掘的Classification本质就是一种机器学习。这里参考《数据挖掘》一书居多。
贝叶斯分类基于概率论中与条件概率相关的贝叶斯定理,根据条件概率的计算方法:
贝叶斯定理的公式通过两个事件相互间的条件概率消去了依赖于时间独立性的难以计算的
 

这样一来,如果我们定义B是我们已知的一系列属性,视为“证据”。我们要计算带有这系列属性的记录的分类结果,可以先算出每个可能分类在已知条件(即这些证据)下的后验概率(Posterior Probability)。
贝叶斯定理提供了根据一系列已知概率来计算后验概率的方法。一系列已知概率,根据大数定理,我们可以用已有的统计样本(训练集)的频率来估计。因此准确性其实有赖于训练集的规模和代表性。

朴素之处
假设每条记录(元组)用一个n维向量表示其属性,假设所有可能分类为,则我们需要对所有分类,为这个元组计算其属于某一分类的概率:
最后我们选择有最高后验概率的类,作为这个元组的分类预测。也就是其概率比其他分类都高的分类。
朴素贝叶斯分类之所以被称为朴素(Naive,幼稚的),是因为它对于很难计算的后验概率,它作了以下假设:所有属性之间都是条件独立的,也就是说,一个议员在财政议题上的态度,与他在外交政策上的态度(没错还是用美国国会数据的例子)是无关的。这个假设简化了该后验概率的计算。之所以说后验概率难算,是因为我们很难在训练集中找到和待分类元组的属性完全吻合的记录,特别是当训练集规模不够大的时候(很难找到分类为,且属性向量为的记录)。当我们假设属性间条件独立的时候,便有:
一个属性向量的后验概率是其各维的乘积。
这种假设有其局限性,因为属性之间条件独立是很不可能的,比如议员在议题A上的取态不太可能和议题B的无关(当然其中往往没有直接的逻辑关系,但数学上定义的条件独立要求两个事件共同发生的概率等于两者概率的乘积)。因此这个假设虽然方便,但带来误差,但这不影响这种“幼稚”的方法的实用性。

先验概率估算
如果不存在可用的训练集,最好的方法是遵循信息论中的熵最小原则,假设所有的类是等概率的。但其实我们往往都有办法得到训练元组,前面说过,离散属性的概率可以通过其频率获得,而对于连续属性,我们进行一个比较依靠经验法则的假设,我们假设连续属性都满足正态分布,根据训练集作为统计样本得到均值和标准差,定义出该属性的分布公式:
那么我们只需将待分类元组的连续属性值根据公式算出其概率。
只要掌握了上述的思想,我们可以发现贝叶斯分类的数学模型是非常简洁好懂的。

拉普拉斯估计
根据类条件独立的假设,某属性向量的概率由其各维乘积得到,但如果遇到其中一个维的概率为0,那么估算出来的后验概率都会为0。如此一来该类的估算概率便为零。但这很可能只是因为训练集的局限性所致(因为规模不够大,或是不够全面)。这时我们引入另外一个假设,假设训练集很大,以致对每个计数结果加上1不会对估计概率产生不可忽略的影响,我们就可以用这种方法来避免零概率值的出现。
当然,在对k个技术结果都加一后,计算频率的分母应该加上k。
另,我们如果只是需要分类结果,因为我们是找具有最大后验概率的分类,而对于某个特定的属性向量X,其概率P(X)只是起到了归一化因子的作用,因此计算时只需要计算分子然后进行排序即可。(不对浮点数归一化同时可以避免精度丧失)

测试
以下是对美国国会数据集进行朴素贝叶斯分类的代码:
import random
class instance():
    def __init__(self, party, votes):
        self.party = party
        self.votes = votes
dataFile = open('dataset.txt', 'r')
datas = dataFile.readlines()
database = []
for line in datas:
    line = line[:-2]
    attrs = line.split(',')
    member = instance(attrs[0], attrs[1:])
    database.append(member)
partyName = ['democrat', 'republican']
trainset = []
checkset = []
for i in range(len(database)):
    if random.random() > 0.9:
        checkset.append(database[i])
    else:
        trainset.append(database[i])
stats = {}
for name in partyName:
    stats[name] = {"number" : 0, "votes" : []}
for k in stats:
    for i in range(len(database[0].votes)):
        stats[k]['votes'].append({"y" : 0, "?" : 0, "n" : 0})
for record in trainset:
    stats[record.party]['number'] += 1
    for i in range(len(record.votes)):
        stats[record.party]['votes'][i][record.votes[i]] += 1
wrong = 0.0
for record in checkset:
    probability = {}
    for name in partyName:
        probability[name] = 0.0
    for k in probability:
        p = 1.0
        for i in range(len(record.votes)):
            p *= (float(stats[k]['votes'][i][record.votes[i]]) / float(stats[k]['number']))
        probability[k] = p    
    if probability['democrat'] > probability['republican']:
        predict = "democrat"
    else:
        predict = "republican"
    print record.party + " , " + predict
    if record.party != predict:
        wrong += 1.0
print "correct rate: " + str(1 - wrong / len(checkset))
每条记录有90%的可能选作训练集,剩余用于检验分类正确性,最后打印出正确率
无耻地选了难得的全对结果,实际的正确率大约稳定在85%以上。
以下观点摘自《数据挖掘》:
理论上贝叶斯分类具有最小的错误率,但实践中容易因缺乏可用的概率数据和其假定的不确定性影响。

2015年2月22日星期日

女孩,当他们说爱你的时候——读《牧师的女儿》

奥威尔的六部小说中有两部他认为烂到不应该再版,《牧师的女儿》是其中一本。两部都是在生活窘迫时为了赚钱而出版的实验性书稿(这部书的第三章干脆直接用戏剧的形式来写)。作者不成熟地描写了一个女主角,故事也讲得很蹩脚,很大程度就照搬他结束流浪后在英国的不同职业的生活经历作为素材。
Dorothy是个失去自由的女孩。在这个单亲家庭,她的父亲是个骨子里懒惰而又爱面子的大男人。千篇一律的生活和疲惫不堪的劳动,令她几乎感受不到任何的乐趣和幸福。而她坚守自己从不质疑的信仰,甚至通过自我体罚的方式。失忆后的流浪令这个女孩得到了前所未有的自由,尽管正因为失忆使她无法感受这种自由的变化。流浪的代价是艰苦的生活。而当她恢复记忆后,又面对着对自己名声被毁以及突然发现自己无依无靠的两重恐惧。她艰难地坚持着亲戚介绍的教书的临时工作,一边寻找新的可依赖人物——为了不丢掉工作而依赖苛刻的校长Mrs.Creevy,为了能回家依赖来接她的Mr.Warburton。Dorothy是个不幸的女孩,甚至我觉得她失忆时的流浪生活几乎可以说是她一生最难得的快乐时光。

奥威尔在描写流浪和打短工摘啤酒花(作者曾经做过这种工作并记下大量日记)的时候,也许有意强调了这种生活苦中作乐的一面。恰恰是这种贫困生活下的自由反衬出Dorothy之前禁锢式生活的可怕。但我们又都知道,当你身无分文时,你是不可能自由的。因为你只能利用自己的劳动力来获得维持生计的必需品。从这个角度看她的这段经历又是乏善可陈。即使在她这个阶段,这个流浪界的新人还是一直依赖着身边经验丰富的流浪者才得以顺利度过难关。不过这种依赖更多的是因一个群体对弱者很自然的关照。
人如果缺乏独立的能力而对别人产生了依赖,也就一直处于了被动的位置。这也是争取性别平等的人们主张女性要能经济独立的原因。不仅性别上,其他方面的问题实际也是相同道理。实际上我快看到结局的时候是以为Dorothy会答应Warburton的求婚请求的。如果如此,其实Dorothy也不过是换了一个依赖的对象继续过无法自己把握的生活罢了。但作者并没有这样写。经历了太多的Dorothy始终不愿再面临生活的大变化,失去了原来本就不坚定,毫无基础的信仰后的她,很恐惧再次的变化。她更愿意选择回自己尽管已经毫无感觉,但却能给她安全感和稳定感的最原本的生活,回到父亲身边继续打理繁杂的事务。
神父和Warbuton几乎是男权社会中的两类经典负面男性形象。神父虽为鳏夫,却没有尽自己作为一个父亲的责任给与女儿足够的关爱,反而因为他的大男子主义给女儿承受更多的压力,而这一切他亦视为理所当然。Warbuton则是玩世不恭的中年男人,不认真对待感情而又对此很享受。
作者借这本书指出了好几点,一是经济不平等所产生的贫困的可怕,正如他很多作品反映的那样;二是教育制度的问题,这点大概是作者在下三烂的学校当教师的时候所深深感受的,通过Dorothy做教师的经历反应;三是基督教保守传统下女性受到的压迫,尽管作者可能并没有打算局限在第三点。
Dorothy的不幸源于她一直处于无法主宰自己的命运的被动位置。我们不知道Dorothy回到家后的故事。也许神父会在数年后急切地想将Dorothy嫁出去,这个女孩的一生也就完成了所谓“女儿”到“妻子”的角色转变,而后是“母亲”。人需要有安全感,无论这种安全感是来源自己还是他人。产生依赖也只是因为自己给自己的安全感不够。即使到了最后,也没人和这个女孩说过“我爱你”,即使是要求婚的Warbuton。但为什么她非得屈就自己,去依赖甚至对她没有关怀的人?因为不够强大。
所以我始终同意无论性别或是家庭背景,人总要通过独立而获得自由。这只是这本书能够带出的思考。总体来说这本小说虽说不好,还是有看点的。
Extrations:
"Think of life as it really is, think of the details of life; and then think that there is no meaning in it, no purpose, no goal except the grave. Surely only fools or self-deceivers, or those whose lives are exceptionally fortunate, can face that thought without flinching?"
Chap II
“试着想想生活的真实状态,想想生活的细节;然后试想生活没有任何意义,没有目的,除了进入坟墓没有任何目标。也只有傻子或者是自欺的人,或者是那些生来就特别幸运的人,才能做到在面对这些想法的时候没有任何退缩?”
"For the rest, she grew used to the life that she was leading - used to the enormous sleepless nights, the cold, the dirt, the boredom, and the horrible communism of the Square. After a day or two she had ceased to feel even a flicker of surprise at her situation. She had come, like everyone about her, to accept this monstrous existence almost as though it were normal. "
Chap III
“至于其他,她已经习惯了她现在过的生活——习惯了漫长的无眠夜晚,那种寒冷,那种肮脏,那种乏味,和广场那种可怕的公共生活。一两天后她已经不再对自己的出境有即使那么一点的惊讶。她向其他人一样,像这很正常一样接受了这种可怕的生存。”
"It is a mysterious thing, the loss of faith-as mysterious as faith itself. Like faith, it is ultimately not rooted in logic; it is a change in the climate of the mind."
Chap V
“这是神秘的,丢失信仰——正如信仰本身一样神秘。像信仰一样,它终究不是植根于逻辑;它是你思想氛围的改变。”

2015年2月20日星期五

基于SVG矢量格式的世界地图染色工具

想写这个是因为在美赛期间做D题的时候,为国家的数据进行聚类分析后,我们想插入一幅世界地图作为插图,为不同聚簇的国家染上不同的颜色。因为对维基百科上的SVG格式世界地图印象比较深,所以我通过下载原始素材,然后用SVG格式的编辑器Inkscape手工染色。实际上网上有一些利用数据绘制地图的工具网站,功能很齐全,只是创建出的图片是带水印的。

SVG格式
SVG(Scalable Vector Graphics)乃可缩放矢量图形。以前学Flash的时候看教程书了解过矢量图和位图的差异。位图(Bitmap)是比较普遍的图片表示形式,通过像素(每个位置的色彩点)的集合来表示一幅图像,因此位图的清晰度受其分辨率(Resolution)的限制。矢量图则通过存储图像的图形学特征来表示一幅图,比如矩形,折线,曲线的各个关键点的特征,因此矢量图的质量不会因放缩受影响,其相较下的缺点便是存储的复杂性。
位图与矢量图的放缩效果对比,这幅示例图是SVG格式的(源自维基百科)
SVG格式通过XML(eXtensible Markup Language,可扩展标记语言)定义,XML和HTML同属SGML(Standard Generalized Markup Language,标准通用标记语言)这一大类的标记语言。有趣的是这种格式的标准是有W3C制定的Web标准。因此,当我们用浏览器打开一幅SVG图像时,我们可以用其开发者工具查看它的每个元素。
用文本编辑工具打开一个SVG文件同样能查看它的文件结构,实际上它很像一个HTML页面,只不过标签不同。正因为SVG的文件结构特点,往往对其操作最方便的就是利用Javascript这一门前端语言。

SVG基本标签
SVG 代码以<svg>元素开始,用开启标签<svg>和关闭标签</svg>包括。<svg>是SVG XML的根元素,如同HTML的<html></html>。<svg>标签的width 和 height 属性设置SVG文件的宽度和高度。
SVG的形状通过<rect>(矩形)、<circle>(圆形)、<ellipse>(椭圆)、<line>(线)、<polyline>(折线)、<polygon>(多边形)和<path>(路径)这些标签定义。打开维基开放媒体提供的世界地图素材后,可以发现上面的国家都是通过<path>标签定义的。
文字通过<text>标签插入。上面那幅示例图即通过这种方式插入文字。
<g>标签指定元素的组,在同个<g>标签内的元素位于同一组(这一点可以在用编辑器打开时体会)。比如地图素材中的美国由于有多个不相连的块组成,就体现了建组的作用。
SVG标签的样式同样是由CSS定义的,这为绘图提供大大的方便,标签的style属性就是用来指定CSS属性的。比如通过指定fill属性即可指定图形的填充颜色。
示例:
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg">
    <line x1="0" y1="0" x2="300" y2="300" style="stroke:rgb(99,99,99);stroke-width:2"/>
</svg>
这段SVG XML代码定义了一幅从图片左上角画到左下角的直线。
世界地图的XML代码非常复杂,因为都是一堆<path>标签对国界线的定义,这种图形是通过编辑器画出来的。
关于SVG的更多标准可以查看W3C的官方文档。

代码
import re
def getPaintedMap(classification, colorScheme, filename="untitled.svg"):
    f = open('test.svg', 'r')
    content = f.read()
    stylePattern = re.compile(r'<style id="style_css_sheet" type="text/css">([\s\S]+?)</style>')
    styleText = stylePattern.search(content).group(1)
    newStyle = styleText
    for country in classification:
        selectorStr = "." + country + " \n{\n\tfill: " + colorScheme[classification[country]] + "\n}\n"
        newStyle += selectorStr
    content = content.replace(styleText, newStyle)
    f.close()
    f = open(filename, "w")
    f.write(content)
    f.close()
实际上代码的函数只是很简单的在原SVG文件的style标签中加上对应的css fill属性。素材中的每个国家都通过其对应的ISO 3166-2标准的国家代号(中国是cn,加拿大是ca等等)进行了标记。只需要用CSS选择器选择即可。
函数接受三个参数,classification是一个从ISO 3166-2国家代号字符串映射到分组号的字典,colorScheme定义着色方案,是分组号映射到颜色号的字典,filename定义输出的文件名。

简单应用
根据Opennet组织对73国的网络自由度评估,从组织整理的四项参数(的五级评分中,先做K-均值聚类而后绘制出这些国家的网络自由度程度评级,地图效果如下:
颜色越深的代表审查程度越严苛,灰色是原素材默认颜色,表示没有包括在数据内的国家。聚类结果比较符合预期,而画图的代码很直观地表示出分析信息。Python有一个第三方的pycountry模块,其源码中有很充足的ISO标准数据库,以xml格式存储。可以直接使用模块的函数或是自己抓出国家代号的信息。而Opennet官方也提供.csv格式的自由度评分数据。

题外话,Opennet的评估局限性在哪?除了其国家数量不足以外,更重要的一点是其评估的公允性。根据维基百科综合Opennet,无国界记者等的评估,结果如下:
无国界记者组织2014年列出的“互联网公敌”名单(图中粉色)新增了数个国家,包括美国和英国,很大程度即因为棱镜门丑闻的爆发。

2015年2月17日星期二

躺在香榭丽舍大街的杂牌饭店——读《巴黎伦敦落魄记》

“奥威尔式”(Orwellian)这个词伴随着《一九八四》和《动物农场》的主题,被人们用来代指一个极权制度的管制手段。人们会联想到“电幕”、“老大哥”和“新话”。但我同意张佳玮在《代表作和被代表作》里指出的:
如是,连作品带人一起被误读,简直是一个创作者的义务。因为世界贪图方便,人们才需要标签和流派,用以给伟大的创作者分门别类。夸张的时候,他们还会用一两个名字或词语,统摄整个时代。比如,一个身处媒体标签时代的中国青年,如果试图去深入一下马尔克斯,必然眼界大开:首先,他会发现此人除了《百年孤独》,还写过那么浩繁的作品;其次,他会发现此人绝大多数的作品,都毫无媒体拼命标签的“魔幻现实主义”色彩。
所以对我来说,“奥威尔式”如果要成为一个标签,更像是奥威尔叙事叙理的那种Plain Fact的平实风格和冷静触感。他思想转折点间的不同时期讲述着不同主题的内容,又都很真切地反应着自己对所见所闻的思考和感受。犹如香港时评人李怡的经历转折给人带来的感觉,“我回顾过去一生,我觉得我冇试过不忠于自己”。在这里并没有否定两大名作的突破性,只是觉得人们太习惯忽略一个作家的思想历程和人生经历对其作品的影响。
企鹅出版社现代经典系列的封面

这部非虚构作品可以说是对这个作者的形象的一大补充。他放弃尚得体面的稳定生活而投身两个欧洲大国首都的不堪入目的核心。在这里有形形色色的三教九流,讲着荒诞的故事。作者会选择投身于此不是因为他疯了,这个在缅甸当过警察却又放弃殖民地生活回到英伦三岛的年轻作家,抱着对建制的反感抵触和质疑,想去寻找自己内心问题的答案。
他终究是个英国佬,因此他的绅士性格和英式幽默贯穿着整个经历。这段堂吉诃德式的探险不是一场带着猎奇的心态的计划旅行,而是一连串偶然下的体验。遭遇偷窃,典当身物,流浪街头,食不果腹。巴黎的X宾馆令人想起“猪龙城寨”。在Jehan客栈做洗碗工像是如获新生。在伦敦的收容所之间每日不断迁徙似是见证陆地上的吉普赛生活。向一群流浪汉取“伦敦东区露宿攻略”的经,而后各奔东西的很像某部江湖电影的脉络,只不过游走的江湖一点也不潇洒而已。
Boris是这本书中花比较大笔墨写的人。这个沙俄的红潮难民却又在镜头转向伦敦的时候完全从文中消失,甚至没有交代去处,尽管他和作者在巴黎几乎是整个落魄过程中共患难共悲喜的同伴。不知道是不是每个跑龙套的俄国人的名字都会是Boris,Ivan或是Yuri。无常的流浪生活就是这样,与作者相遇的人都挣扎在底层,谁又有兴趣和这个虽形象和他们一样但口音和举止都怪怪的人深交。所有人都是过客,但他们的出镜却都不是轻轻带过。这样一幅一直延续到终章的众生像,在作者回顾整理的时候又是不是像一场蒙太奇般的梦?
1930s的英法两国和很多国家一样面临着大萧条,欧洲大陆的右翼化开始抬头。深度的贫困摧毁人的生活至于摧毁人的希望,乃至摧毁人的思考。可以说作者所描写的就是这种程度的贫乏——人们不仅过得很糟糕,还没有改变现状的希望,更加地,也许幸运也许不幸,人们并没有改变现状的想法。书中所写的这种致人麻木的贫困和其中穿插的思考,几乎是解构极权主义的前奏。
“贫穷是一种专制,它培养自己的奴隶。”,正因我同意这句话,我不认为奥威尔在回到欧洲后到二战爆发这一大阶段的作品与战后他的两大反乌托邦作品脱节。Victory Mansions惨淡的形象,怎么看都像是圣·塞浦里安学校(见《如此欢乐童年》)或是巴黎金鸡街贫民窟糟糕的住宿环境在1984那个时空的折射。也正是有作者在各国丰富经过真诚体验得到的经历,才沉淀出在虚构时空下的那种真实笔触。奥威尔的反极权思想习惯性地被西方在冷战中用作对抗苏联邪恶帝国的意识形态武器,他的民主社会主义追求和对殖民主义、垄断资本主义的批判和解构却被有意无意地忽略。如果没有缅甸的经历,他不会到巴黎伦敦流浪。如果没有巴黎伦敦的流浪,他不会走上维冈码头的“朝圣之路”。如果没有维冈对他的冲击,他不会参与西班牙内战。而最后,也正是西班牙的经历令他转折。唯一不变的是这位作家始终没有放弃自己“圣徒”般的追求。

摘录:
"At present I do not feel that I have seen more than the fringe of poverty. Still, I can point to one or two things I have definitely learned by being hard up. I shall never again think that all tramps are drunken scoundrels, nor expect a beggar to be grateful when I give him a penny, nor be surprised if men out of work lack energy, nor subscribe to the Salvation Army, nor pawn my clothes, nor refuse a handbill, nor enjoy a meal at a smart restaurant. That is a beginning."
——Chap XXXVIII
“目前我并不觉得我看到了比贫困的冰山一角更多的部分。但我还是能指出在艰辛时日中实实在在学到的一两样东西。我不会在觉得流浪汉就是醉醺醺的恶棍,不会期望乞丐会因为我给他一便士而感激涕零,不会为失业的人的萎靡不振而吃惊,不会再向救世军捐款,不会再典当衣服,不会再拒绝传单,不会在高档餐厅吃饭。这就是开始。”
"Within certain limits, it is actually true that the less money you have, the less you worry."
——Chap III
“在一定程度下,你的钱越少你所担心的就越少,这是真的。”
"Hunger reduces one to an utterly spineless, brainless condition, more like the after-effects of influenza than anything else. It is as though all one's blood had been pumped out and lukewarm water substituted. "
——Chap VII
“饥饿使人进入全身瘫软,大脑空白的状态,像是流感之类的病的后作用。就像一个人的血都被抽空,用微温的水代替了。”
"Fate seemed to be playing a series of extraordinarily unamusing jokes."
——Chap VII
“命运像是给人开了一连串特别不好笑的玩笑。”
"Beggars do not work, it is said; but then, what is work? A navvy works by swinging a pick. An accountant works by adding up figures. A beggar works by standing out of doors in all weathers and getting varicose veins, bronchitis etc. It is a trade like any other; quite useless, of course — but, then, many reputable trades are quite useless."
——Chap XXXI
“人们说乞丐并不工作;但是,什么是工作呢?工人的工作是挥动镐。会计的工作是计算数据。乞丐的工作是无论天气在户外站着,从而患上血脉肿胀、支气管炎等等病。这和其他的工作没什么两样;很无用,当然——但,很多体面的工作也都是无用的。”
"He was an embittered atheist (the sort of atheist who does not so much disbelieve in God as personally dislike Him), and took a sort of pleasure in thinking that human affairs would never improve."
——Chap XXX
“他是那种怨恨的无神论者(那种不太相信上帝,个人来说厌恶上帝的),他相信人类不会有改善,并为此感觉优越和欣慰。”

Twitter实时同步到新浪微博

寒假写的这个程序。一开始天真地打算通过纯Python来实现,或者说不借助Twitter和新浪微博的官方API。整个程序的机理可以解释为,每隔一定时间间隔,从Twitter上爬取新的推文(Tweets),筛选出这一段时间内新发的推文,在新浪微博上发送。一开始因为我不打算用官方的API,所以用的是模拟新浪微博登录后调用发送的请求。

新浪微博模拟登录
微博有三个登录的站点,一般的网页版(weibo.com),智能手机访问的网页版(m.weibo.cn)和传统手机版式(weibo.cn),通过Chrome的开发者工具,我们会知道这三个机制在登录时浏览器向新浪发送的报文内容不尽相同。PC端网页版的登录最复杂,其密码和用户名均经过加密,而且在搜解决办法的时候,一些以前可行的方法都失效了,原因是新浪会改变密码的加密方式,目前使用的是RSA方式。
相比之下另外两个站点的登录方式则更简单,因为密码和用户名是不通过加密直接POST请求的,用urllib组织好报文发送即可。响应报文的Cookie可以用来后续的操作(发微博,点赞等等)。
如果只是一段时间的任务,最简单的方法是在发送报文的header中直接设置你的Cookie字段,只要用开发者工具查看你登录或是其他和浏览器交互的报文,就可以看到你的Cookie。因为要让其持久工作,有生存时间的Cookie不在考虑范围。
weibo.cn登录后只能发送文字,而m.weibo.cn支持六张图片的发送,其机制是先调用上传图片的请求,将文件上传到新浪的服务器,上传图片的请求会返回包含图片pid(或pid列表)的响应报文。微博在正式发送时连同正文和pid发送。
上面所述的办法我最终都没采用,因为模拟登录有时需要验证码(验证码的作用就是挡住我们这些机器人程序),我们需要一个能“长治久安”的办法,终究得用新浪微博提供的API。

新浪微博API
《挖掘社交网络》这本书的时候,里面在Twitter等网站上做信息获取也是直接使用官方的API。官方API优势很明显,在于方便,但也有很明显的缺点,比如频次限制,这里便有新浪微博对API频次的说明,微博的API还有等级限制,你要有获得更多数据的权限,或是增加你的频次上限,都需要通过增加认证信息或是付费的方式(一如SAE的风格)。我同步的NBA的Twitter帐号便因为发送频率过高(NBA的更新频率的确很高)而被帐号冻结。
这里讲一下一个很好的应用IFTTT。新浪微博是第一个与其建立连接的国内社交网络,它支持很多样的社交应用网络,用户可以自定义自己的Recipe(其最喜欢举的例子便是“如果明天下雨,发我一条短信”,利用天气预报的信息)。不少牛人,诸如BYvoid和禅叔伍嘉贤等等利用它来同步自己的Twitter到新浪微博。IFTTT的功能还可以很多样。
IFTTT的帐号可以很高频地发送信息,原因就是因为他们申请了较高的API级别。
新浪微博没有官方提供Python的SDK,而是推荐了廖雪峰所写的SDK。SDK的作用是进一步提供基于API的接口,这样一来程序可以更好写。转换短链接的接口很简易,只需要提供你的appkey和要转换的url即可,因此我将这部分放到了Twitter预处理的那部分。
sdk的weibo.py定义的一个很有用的类是APIClient,新浪微博的读写API均需要通过OAuth2的方式,也就是不能通过简单的用户名和密码。我们在微博授权应用访问我们的帐号时,都会出现一个认证页面,实际上就是OAuth机制的其中一环。
正因如此,我为了让程序能自动发送微博,也基于SDK进行了改进,因为SDK仍然需要一个认证页面授权的过程,我的程序则通过爬虫实现自动授权。
关于OAuth及其细节可以看阮一峰的这篇博文

Twitter这边
Twitter的信息获取实质很自由,因为不需要登录就可以访问一个人的主页。在本地测试时使用urllib2模块设置代理的函数,使程序通过GoAgent进行爬虫。
proxies = {'http': '127.0.0.1:8087', "https" : "https://127.0.0.1:8087"}
proxy_support = urllib2.ProxyHandler(proxies)
opener = urllib2.build_opener(proxy_support, urllib2.HTTPHandler)
urllib2.install_opener(opener)
Twitter的开发者帐号我无法申请,因为发现他们无法向中移动的号码发送验证码。无法使用Twitter API中的user_timeline获取推文的方式带来一个问题,如果是从首页进行爬虫,一条转发的推文(Retweet)并不包含转发的时间。如果更新间隔是五分钟,那么一条三分钟前转发的几天前的推文将被判断为几天前的内容而被筛掉,我最终没有解决这个问题的打算。(Retweet和新浪微博的转发很不相同,你不能附加任何信息,新浪微博一条转发可以相当于两条——你转发所说的话和原微博的内容)

部署
代码部署在Google App Engine上,利用GAE的cron服务可以很方便地实现定时执行,这里便引入另一个问题,GAE的cron服务,每一次的计时是在一次任务执行完毕后才开始计算的,也就是说间隔N分钟的任务,如果执行用了K分钟,那么下次检查更新将会跳过前面的时间间隔空隙。而IFTTT的同步频率比较快,却会出现一条动态被发送两次的问题。因此我的选择仍然是不理会这个误差。
这个程序的源代码可以在我的Github上看到。

这个是我同步的Techmeme帐号:
http://weibo.com/u/5521000534