二分类问题的评分值与损失函数

二分类问题是监督学习中最基本的问题之一,本文旨在从评分值、损失函数的角度,对解决二分类问题的诸多机器学习算法做一个梳理。相关算法的详细介绍可以参考相关教材12

loss

  1. 统计学习方法 李航 

  2. 机器学习 周志华 

本来无一物

img

题图是中国当代艺术家宋冬推出的“物尽其用”装置艺术展的一角,宋冬的母亲一辈子舍不得扔东西,将所有物品囤积起来,包括这些牙膏皮。

基于RNN实现加法

知乎上有人问题了个有趣的问题:

深度学习能够学会加法吗?

撇开所谓的哲学意义不谈,已经证明了通过RNN可以模拟实现图灵机,这篇神奇的论文看这里。我的大致理解是,图灵机也是一种模式匹配,而只要有足够多的学习样本,RNN总是可以学会这种模式匹配的。

假设我们要让神经网络学会3位数的加法,比如111+222=333,123+987=110,注意为了简单期间,我们不考虑最高位的进位。

加法可视为两个加数构造的序列来预测作为和的序列,因此可以用RNN来实现。示意图如下:输入序列、输出序列长度均为3,使用一层RNN存储进位信息。输入输出均使用one-hot编码后,输入特征数为20,输出特征数为10,中间层使用RNN,其隐藏节点数设为32。

addtion-by-rnn-1

使用keras搭了一下模型,图示如下:

addtion-by-rnn-2

代码如下:

注意为了画上述示意图,需要安装Graphviz

# -*- coding: utf-8 -*-

from keras.models import Sequential
from keras.utils import plot_model
from keras import layers
import numpy as np

import os
os.environ["PATH"] += os.pathsep + 'C:/Program Files (x86)/Graphviz2.38/bin/'

class CharacterTable(object):
    """
    one-hot encode/decode
    """
    def __init__(self, chars):
        self.chars = sorted(set(chars))
        self.char_indices = dict((c, i) for i, c in enumerate(self.chars))
        self.indices_char = dict((i, c) for i, c in enumerate(self.chars))

    def encode(self, C, num_rows):
        x = np.zeros((num_rows, len(self.chars)))
        for i, c in enumerate(C):
            x[i, self.char_indices[c]] = 1
        return x

    def decode(self, x, calc_argmax=True):
        if calc_argmax:
            x = x.argmax(axis=-1)
        return ''.join(self.indices_char[x] for x in x)

TRAINING_SIZE = 50000
DIGITS = 3

chars = '0123456789'
ctable = CharacterTable(chars)

questions = []
expected = []
seen = set()
print('Generating data...')
while len(questions) < TRAINING_SIZE:
    a = ''.join(np.random.choice(list('0123456789')) for i in range(DIGITS))
    b = ''.join(np.random.choice(list('0123456789')) for i in range(DIGITS))
    key = tuple(sorted((a, b)))
    if key in seen:
        continue
    seen.add(key)

    ans = str((int(a[::-1]) + int(b[::-1]))%1000).zfill(DIGITS)[::-1]
    
    questions.append((a,b))
    expected.append(ans)
print('Total addition questions:', len(questions))

print('Vectorization...')
x = np.zeros((len(questions), DIGITS, len(chars)*2), dtype=np.bool)
y = np.zeros((len(questions), DIGITS, len(chars)), dtype=np.bool)
for i, (a, b) in enumerate(questions):
    x[i] = np.column_stack((ctable.encode(a, DIGITS), ctable.encode(b, DIGITS)))
for i, ans in enumerate(expected):
    y[i] = ctable.encode(ans, DIGITS)
    
print('Split data...')
indices = np.arange(len(y))
np.random.shuffle(indices)
x = x[indices]
y = y[indices]

split_at = len(x) - len(x) // 10
(x_train, x_val) = x[:split_at], x[split_at:]
(y_train, y_val) = y[:split_at], y[split_at:]

print('Training Data:')
print(x_train.shape)
print(y_train.shape)

print('Validation Data:')
print(x_val.shape)
print(y_val.shape)

HIDDEN_SIZE = 32
BATCH_SIZE = 128

print('Build model...')
model = Sequential()

model.add(layers.SimpleRNN(HIDDEN_SIZE, input_shape=(DIGITS, len(chars)*2), return_sequences=True))
model.add(layers.TimeDistributed(layers.Dense(len(chars))))
model.add(layers.Activation('softmax'))
model.compile(loss='categorical_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])

print('Plot model...')
model.summary()
plot_model(model, to_file='model.png')

print('Training...')
ITER = 20
for iteration in range(ITER):
    print()
    print('-' * 50)
    print('Iteration', iteration)
    model.fit(x_train, y_train,
              batch_size=BATCH_SIZE,
              epochs=1,
              validation_data=(x_val, y_val))
    print("10 validate examples:")
    for i in range(10):
        ind = np.random.randint(0, len(x_val))
        rowx, rowy = x_val[np.array([ind])], y_val[np.array([ind])]
        preds = model.predict_classes(rowx, verbose=0)
        question = ctable.decode(rowx[0][:,:10]) + '+' + ctable.decode(rowx[0][:,10:])
        correct = ctable.decode(rowy[0])
        guess = ctable.decode(preds[0], calc_argmax=False)
        print('Q', question[::-1], end=' ')
        print('A', correct[::-1], end=' ')
        print('√' if correct == guess else '×', end=' ')
        print(guess[::-1])

能在较短时间内训练到100%的准确率。毕竟,这个模式识别起来不太复杂。

至于如何让机器理解什么是加法,这就是另外一个问题了,毕竟我们也难以理解人类是如何学会理解加法的,甚至人类是否理解加法都不知道。

停止日更

从今年元旦开始,每天更新一篇博客,已经约280天,如果我愿意的话,可以像行为艺术一般坚持下去,不过我觉得坚持日更的目的已经达到了,它拉动了我思考学习的进程,但是非要每日一更已经成为一个负担,真正有创造性的东西要日更几乎不可能,而片言只语式的更新只能算是微博,如果只是读书笔记又其实不适合用博客的形式写出来。接下来博客的写作主要做以下几件事:

  1. 由日更变为周更,每周1~2篇质量较高的,有一定创造性的文章;
  2. 将过去的博客中原创性较低的读书笔记以gitbook的形式重新整理发布到learn子域名;
  3. 整理过去的博客,修正错别字,有一些内容不完善的补充完善;
  4. 对有价值的内容在主页上做好导航。

学习、创造与分享

今天看到一个说法,说爱学习也可能是一种懒惰的行为,对于国庆七天都在写Python代码的我来说,这是一种很刺激的说法。

对大部分人来说,让他们学新东西是很要命的东西,但也有一部分人,很喜欢学习新的东西,把学习当成一件有乐趣的事情,终身学习,“活到老学到老”是他们的信条,所以我之前一直把“活到老学到老”这句话作为博客的口号。但是今天我认真思考了一下,察觉到这是不够的。

学习这一行为,基于学习者不同的主动程度,会体现出不同的创造性,随便看看公开课视频、朋友圈文章,看看罗辑思维,也可以算是学习,而在教程的指引下写出自己的一个小程序,也是一种学习。学习和创造的界限不是那么分明的,但是总归学习还是偏向于自我修炼,产生变化的主要是自己的脑子,而创造则需要将自己的想法作用于现实世界,所以创造不是空想。

在物质文明越来越发达的今天,精神文明产品创造的意义日益凸显,如果你有好的想法,做成了好的产品,借助于信息时代的传播工具,其复制成本会接近于零,今天游戏产业和电影产业都非常挣钱,其实它们都是精神层面的需求。所以在信息时代,分享是非常简单的,重要的是你的创造。

我没有谈到钱或者价值的问题,因为显而易见,如果创造物是有价值的,那么借助网络效应,很容易获得相应的价值回报。

仔细分辨的话,我的学习中似乎是有一些躲在舒适区的懒惰,但是这都不重要,重要的是更加注重创造,这是三者中最重要的一环。

Peter Norvig的编程建议

Peter Norvig是Google的大牛之一,他在Udacity开了一门课计算机程序设计,用Python语言讲授,这门课程的实践性很强,下面是他在课程中提到的一些编程建议:

  1. 修改程序的几个维度:正确、效率、特性、优雅,需要在几个维度间平衡,更优雅的代码对软件改进没有直接作用,但可以为未来可能的修改打下更好的基础,而重构就是修改代码使其更优雅。
  2. 程序员应当善用别人的代码,提现在两个层次,一个是编程语言的库,应当熟悉常用的并善于查文档,二是一些大的功能模块,比如网页服务器、Email通信组件等。
  3. 学习一些高级的复杂的语言特性,应当去理解这个语言特性解决的痛点是什么,这是它出现在这个语言中的原因;
  4. 好的代码要长还是短,取决于是否清晰的进行了沟通,即别人能否容易读懂你的代码,太长或太短都可能影响沟通;
  5. 自顶向下设计还是自底向上设计,取决于对问题的清晰理解在哪个层次,如果对问题的大尺度理解很清晰,就适合自顶向下设计,不要害怕写一些代码桩。反之,如果大尺度都不清楚,则可以写一些已经分析清楚的小模块,然后进行组合试验。
  6. 现在学习编程的条件很好,但是要注意多和他人沟通,多向他人学习。
  7. 针对问题,可以先找到快速,dirty的解法,并估算算法的复杂度,看是否有必要进一步优化,优化应当建立在分析焦点操作的基础上。
  8. 在必要的时候,要认真读文档。
  9. 读代码要主动,最好在IDE环境下读,改一改看结果会怎样。要有整体读代码,获取架构的能力,也要有读片段,迅速找到所需的能力。
  10. 智力游戏问题定义清晰,而又足够复杂,很适合进行编程训练。
  11. 在三种情况下考虑递归:
    1. 数据结构本身时递归定义的,比如二叉树
    2. 有数学递推式
    3. 能够进行尾递归优化

iPad1电子相框制作

闲置的ipad1基本上没有什么用了,大部分看视频的软件都不再适配iPad1,不过iPad本身就有电子相框功能,所以把iPad1彻底变成电子相框是个不错的主义。

1 硬件准备

首先,买点3M的无痕魔力扣,像这样的,用起来像魔术贴一样。

高曼™ 无痕魔力扣中号

用这个挂iPad绰绰有余,而且不用在墙上打洞,具体方法请参看3M产品的说明书。

还要在适当的位置准备一个电源接口,怎么隐藏一下这就是个人发挥的艺术了,一般来说iPad充一次电还是能用蛮久的。所以可以不必一直插着电用。

2 软件准备

iPad删除所有能删除的程序,连上WiFi,到App Sotre装一个QQ,新版的QQ自然装不上,但是App Store会提示你安装历史版本,这样就行了。

在Windows或Mac系统上挑选自己喜欢的照片,统一存放到一个文件夹中。

iPad连上Windows或Mac系统,点开iPad管理界面,使用iTunes的照片同步功能,选择你挑好的文件夹将其同步到iPad。具体的操作可以参考这里

把iPad电源关掉,然后打开,看到锁屏界面,点一下滑动解锁旁边的电子相框按钮即可进入电子相框模式。此外,在iPad的设置中也有电子相框设置可以调节效果。

将iPad挂上墙

3 后续维护

如果有大量的照片需要更新,可以将iPad取下来(这也是3M无痕魔力扣的方便之处),重新进行照片同步,注意同步会把之前同步的照片先删除

如果只是零星的照片想上墙,直接在其他设备如电脑、手机登陆QQ,iPad也登陆QQ,通过QQ传送文件到“我的平板电脑”即可。

使用netlify改善github pages服务

一直很喜欢github pages,所以一直用着,但是有两个问题一直很难一起解决:

  1. github因为某事件屏蔽了百度的爬虫,博客在百度上搜不到,通过在个人主机或coding pages建立镜像,然后用dnspod分流的方法可以解决;
  2. github pages支持强制https,支持自定义域名,但还不支持自定义域名的强制https。可以用cloudfare的cdn服务解决。

但是上述两个问题很难同时解决,虽然我有个人主机,但是懒得用来托管博客。今天发现netlify服务解决了两个问题。基本步骤如下:

  1. netlify在注册后,可以申请权限接通你的github pages的repo;
  2. 然后简单配置一下部署命令,对jekyll来说,命令为jekyll build,生成的文件目录配置为_site
  3. netlify可以修改子域名为yoursite.netlify.com,然后也可以配置自定义域名,将dns的CNAME记录指向yoursite.netlify.com即可。
  4. 开启https服务(可以用自己的证书,也可以用netlify自带的Let’s Encrypt证书),以及强制的https转换。
  5. 以后,只要你向github提交更新,netlify就会通过钩子自动重新执行jekyll build命令部署站点。

测试了一下,国内访问会被cdn到日本节点,速度还算可以。

《看不见的客人》:多重叙述与真相

Contratiempo

在大荧幕看这部电影,观影感受可以用一个字来形容:爽。

多重叙述是一种非常有意思的电影语言,角色通过语言回忆过去,不同的角色甚至同一个角色不同的时间对同一件事情都会有不同的叙述,而电影可以借助视觉语言多次的迷惑观众,观众则需要在多重叙述中寻找真相,在这个过程中,需要的是不断的体察人性,好的电影会让一切合乎情理。

多重叙述即使在现实生活中,也是一种常态,首先不同的人看事情的视角与重点都不一样,更重要的是,叙事的人可能会说谎,这种对现实的扭曲使得要知道真相很难,即使在历史研究中,也会有类似电影的反转出现。

谎言代表的人性是很微妙的一种东西,一般来说人们都会声称不喜欢说谎,但是又人人都会说谎,以至于《三体》中,说谎倒成了人类的少有的优势之一。

说了这么多,都没有说到电影的具体细节,因为剧透可耻。

家庭wifi组网经验

之前这边的房子比较少住,无线路由器放在电信光猫旁边,位于客厅一角,所以几个卧室的信号都不是太好,好在手头有不少电猫、无线路由器,所以自己再稍微组了一下网,下面是一些经验:

  1. 大部分无线路由器默认是路由器模式,但是一般可以配置成AP模式,中继器模式、桥接模式、Client模式等,具体的配置方法有些路由器可能有明显的指导,有些需要到网上搜索教程,但基本原理和模式的区别大致如下:
    1. 无线路由器通过LAN有线口接到能上网的路由器,那么就成了AP模式。
    2. 如果通过WIFI接到能上网的路由器,就成了中继模式(配置自己的SSID和上级路由器一致)或桥接模式(配置自己的SSID和上级路由器不一致)
    3. 如果WIFI接到能上网的路由器但是不再发射无线信号,而只在有线口接入设备,则成了Client模式(相当于一个网卡)。
  2. 中继模式和桥接模式没什么用,因为正因为信号不好,才需要中继或桥接,那么中继或桥接的这一段始终是瓶颈,导致卧室上网很慢很卡。
  3. 强烈推荐使用AP模式,在信号不好的地方(如卧室)放一个路由器,将它的IP改为和上级路由器同网段但是不同的IP,然后通过自己的LAN口接入上级路由器的LAN口,为了使用时无缝切换,将这个路由器的SSID和密码设置为和上级路由器一致。
  4. 现在的问题是,找一根线连接两个路由器,如果事先没有布过线,就需要走明线,会很难受,这时候电猫就出场了,电猫可以理解为一根网线,但是中间一段是用电线接通的。高档的电猫可以一拖多(一个主机多个从机),甚至从机自己可以是一个AP。我比较穷而且闲置设备多,所以用了一对普通电猫和一个闲置路由器设置为AP模式搞定了。
  5. 如果从头装修房子,还是一间房一个AP比较好,可能的话可以放到吊顶里面。