Scratch 编程语言 2

很早之前,大约是两千零几年的时候,我曾经看别人演示过 LabVIEW 专为儿童教育,以及乐高玩具开发的特别版本。对于少年儿童来说,图形化编程比文本编程要更有吸引力。可惜的是,LabVIEW 起个大早,赶个晚集。现在再提起儿童教育或玩具领域的图形化编程语言,多数人只会想到 Scratch。

Scratch 是 2003 年才诞生的一个新语言。它能够一出现就挤掉 LabVIEW,迅速占领整个儿童教育领域,主要因为具有以下一些优点:

  • 开源。Scratch 是由 MIT 开发的,它从一开始就采用了开源的策略。儿童使用的编程语言,一个重要的功能是控制各种玩具。反过来说,能被玩具厂商广泛采纳的编程语言,会更容易被推广开来。玩具厂商想把自己的产品与 LabVIEW 结合,甚至直接发布一个定制版的 LabVIEW 成本是非常高的。即便是在工业界,LabVIEW 具有统治地位的测试测量领域,LabVIEW 也主要是与 NI 公司自己的硬件结合使用。玩具行业的利润更低,厂商们自然倾向于便宜的软件。更何况 Scratch 还是开源的,可以很方便的就对其进行改造以适应自己的产品。现在,除了最著名的乐高,很多中国厂商的玩具搭配的也是 Scratch 编程语言,比如小米的玩具。
  • 语法简单。Scratch 的语法与 LabVIEW 有着非常大的不同,它隐藏了更多的编程细节,让初学者可以更容易的入手。LabVIEW 虽然也号称容易入门,但相比比起来, Scratch 才真正算得上是“傻瓜”型编程语言。
  • 采用了更主流的技术。Scratch 是采用了 HTML 5 标准,使用 JavaScript 作为开发语言。LabVIEW 也曾经有过网页版,尽管当时已经可以明显看出 HTML 5 是发展趋势,LabVIEW 却采用了 SilverLight 作为开发平台。后来微软彻底抛弃了 SilverLight ,肯定也会对 LabVIEW 的开发推广造成影响的。

下面是一个具体的示例程序:

这段代码中的积木(一种颜色的近似长方形的一个条形块)分成了三堆,这三堆之间是并行的关系。每一堆积木都从一个事件开始。左边这一大堆是主线程,当接受到用户点击绿色旗帜的事件时开始运行,它在运行过程中会发出一些事件,去启动另外两堆积木。这段程序的主要功能是运行一个循环“repeat 20”,在循环内调用“move”功能,让屏幕上的一只小狐狸(绘制在“costume”里面)向前移动一段距离。同时还让小狐狸发出“喵呜”的声音。

从上面这段程序可以看出来,虽然也是图形化编程语言,Scratch 相比与 LabVIEW 还是有一些明显不同的。

  • Scratch 图形化方面没有 LabVIEW 彻底,它借鉴一些文本编程语言的编程方式,同时在编程时也更依赖文本。编写 LabVIEW 的程序更像是绘图,而编写 Scratch 程序更像是搭积木。
  • LabVIEW 中的基本功能模块多以函数和 VI 的形式存在,它们的外观看上去是一个个的正方形方块。LabVIEW 中的结构的外观会更复杂一些,像是尺寸可变的框架。在 Scratch 中,函数和结构都被称作 block(翻译成模块或者积木),它们看上去都是一个个长条。
  • Scratch 中由于每个积木长得都一样(或者十分类似),它只能用来文字区分不同功能的积木。LabVIEW 中推荐给每个子 VI 都绘制一个有意义的图标,这看起来当然是比 Scratch 的代码美观的多。但是,也有很多程序员非常讨厌这个规范,他们宁可把时间用于改进程序的逻辑,而不是美观程度。
  • 对于程序流程的控制,LabVIEW 使用数据线来控制,按照数据在数据线上的流动顺序来控制程序运行顺序。Scratch 没有数据线。凡是挨在一起的积木,它就是按照顺序从上至下执行每一个积木。没有粘连在一起的积木是可以并行执行的。
  • 因为没有数据线,Scratch 只能使用全局变量来传递数据。
  • 积木使用不同颜色表示不同的功能分类,比如浅黄色的用于发送接收事件;深黄色的用于控制程序流程(比如循环结构,条件结构等);紫红色的用于控制声音,蓝色的控制运动等等。
  • 用不同形状表示不同数据类型,比如数值类型数据放在一个两侧是圆弧的长条里;而布尔型数据放在两侧是尖角的长条里。这保证了程序具有一定的数据类型安全,比如某个积木上有一个两侧尖角的长条凹槽,那么在这个凹槽里就只能嵌入布尔型数据(相当于一个函数,具有一个布尔型的输入参数)。

Scheme 编程语言

Scheme 是第一门我真正系统学习过的函数式编程语言。Scheme 语言的标志是一个 Lambda 字符“λ”,一眼就可以看出这门语言的出处。Scheme 是 LISP 语言的两大方言之一,而 LISP 又是人类开发出的第二款高级编程语言(第一个是 FORTRAN)。

因为太古老,Scheme 的编程思路和现在常见的语言差距还是非常大的。我刚开始接触 Scheme 时的困惑不亚于刚接触 LabVIEW。Scheme 的语法定义是比较简单的,比时下流行的编程语言都简单得多,但毕竟也是一门完整编程语言,不可能写一小段就介绍全面,这里就只能介绍一些最基本功能了。

Scheme 直观上最明显的特点是括号多,它的所有数据(比如列表)和程序结构(比如函数、判断语句)等都被包裹在括号内,因此,一段代码里会有数不清的层层括号。在编程思想上的最大特点就是函数式编程。再 Scheme 程序中,一切都是函数。

在 Scheme 中写 Hello, world 的代码如下: (display "Hello, World!")

在这段中 display 是一个函数,用于在屏幕上打印文字,而后面的字符串则是 display 的参数。单这一句,与常见编程语言的用法差距也不算太大。

在 Scheme 语句中,函数名总是要放在参数之前,运算符也是一个函数。所以,如果要计算 “2+3”,写出来的程序是这样的: (+ 2 3) 。 Scheme 的函数很多是可以跟多个参数的,比如 (max 2 6 3) 或者 (+ 6 4 8) 等等。如果是一组数据,比如一个 list,那么就在括号前加个单引号,比如 ‘(a b c d),这就不再是函数调用,而是一个列表了。

函数的定义一般类似如下:(lambda (x) (+ x 2))。lambda 是关键字,后面跟着函数参数,在后面是函数体。在 Scheme 语句中使用关键字 define 给常量命名,使用关键字 let 给变量命名。函数也可以是一种变量,比如下面的语句就给了新定义的函数一个名字“square”:

(define square (lambda (n) (* n n)) )

Scheme 语言还有一特点就是没有循环,所有需要循环的地方都要使用递归来完成。这和早期的 LabVIEW 正相反,早期的 LabVIEW 不支持递归,所有要用到递归的地方都必须转换成循环。比如,在 Scheme 中计算阶乘,只能采用递归的形式:

(define factorial (lambda (n) (if (= n 0) 1 (* n (factorial (- n 1))))))

我在学习 Scheme 的过程中,最大的收获是把递归彻底弄清楚了。递归有时候还是比较容易绕的,比如把一个列表从头开始归并,和从尾开始归并要采用不同的递归策略。以前都没有深入考虑过,学习了 Scheme 才真正系统的研究清楚了。

Lambda Calculus 编程语言

我刚成为程序员的时候,有一次调试一段 C 语言代码。我一层一层的进入到被调用的子函数中去,想看看一个数据到底是怎么产生的。终于在遇到一个库函数的时候,调试器无法再跟踪进去了。C 语言程序通常会调用很多已经编译好的库函数,程序员只知道这些函数的接口,但看不到它们的实现代码。我知道这是处于效率的考虑,但还是忍不住想,一种编程语言,可不可以不调用任何编译好的库,所有功能都以源代码的形式提供给程序员,方便学习啊。后来进而又想,也许很多关键字,运算符都不是必须提前编译好的,有没有编程语言可以以源代码的形式提供这些关键字,运算符呢?

这些问题,当时也只是一想,没有去研究。多年之后,我在帮老婆做编程作业的时候发现,她们在课上居然学到了这样一种编程语言,叫做 Lambda Calculus。

Lambda Calculus 是一类非常精简的编程语言中的代表。这类语言中还包含 SKI,Iota 和 Jot 等,不过 Lambda Calculus 还是最经典的。Lambda Calculus 小到什么程度呢?只需要用几行文字就可以把 Lambda Calculus 的全部语法描述的清清楚楚,所以这里就介绍一下:

  • Lambda Calculus 中用到的全部字符包括:小写英文字母,英文句号,小括号和一个希腊字母 lambda “λ”。
  • 名字 name:由单个英文小写字母构成,格式为 <name>。 比如 x,y,a 等;
  • 函数定义 function:由“λ”字符跟一个变量名,跟一个英文句号,在跟一个函数体构成,结构为 λ<name>.<expression>。比如 λx.x,这个函数写成数学形式是 f(x) = x;
  • 函数调用 application:由函数定义加另一段表达式构成,格式<function><expression>。比如: (λx.x)a,这表示,把 a 作为参数传递给前面那个函数,运算结果就是 a。
  • name, function, application 又被统称为 expression。
  • 括号用于控制计算的优先级。有时代码里也会加入空格方便阅读。

以上就是这个编程语言的全部语法了。当时的要求是给这个语言编写一个编译器,其中最核心的部分只用了十来行代码就实现了,恐怕很难有比这更简单的编程语言了。可以直观的看出,这个编程语言的一些简单运算规则:

  • λx.x 与 λy.y 是完全等价的,或者可以写成 λx.x ≡ λy.y
  • (λx.x)a 可以简化成 a,或者写成 (λx.x)a = a
  • (λx.y)a = y
  • (λx.(λy.x))a = λy.a

大家已经发现了,这个语言,连循环、判断等结构都没有,对了,这些都要自己编程实现;甚至连数字也没有,加减法也没有,这些也通通都要自己定义和实现。下面就介绍一下如何使用 Lambda Calculus 编写一些基本的功能。

  • 实现多参数函数,这个方法有个专用名叫函数柯里化。比如实现函数f(x, y, z) = ((x, y), z) 可以使用如下的代码: λx.λy.λz.xyz
  • 逻辑运算中的“真”被定义为:TRUE ≡ λx.λy.x。这里对于“真”的定义是:输入两个参数,返回第一个。推算一下 TRUE a b ≡ (λx.λy.x) a b = (λy.a) b = a
  • 逻辑运算中的“假”被定义为:FALSE ≡ λx.λy.y。推算一下 FALSE a b ≡ (λx.λy.y) a b = (λy.y) b = b
  • 判断语句 if,假设我们需要当变量 b 为 真时,返回 t;b 为假时返回 f。那么可以定义 IF b t f ≡ λb.b t f 。 推算一下 IF TRUE t f ≡ ((λb.b) (λx.λy.x)) t f = (λx.λy.x) t f = t ; IF FALSE b t f ≡ (λb.b t f) (λx.λy.y) = (λx.λy.y) t f = f
  • 有了以上的基础,逻辑运算的定义就简单多了,比如:AND a b ≡ IF a b FALSE; OR a b ≡ IF a TRUE b; NOT a ≡ IF a FALSE TRUE
  • 定义数字:
    • 0 ≡ λf.λx.x 可发现 0 ≡ FALSE
    • 1 ≡ λf.λx.f x
    • 2 ≡ λf.λx.f (f x)
    • 3 ≡ λf.λx.f (f (f x) x)
  • 为了方便数字运算还要先定义一个辅助的“后继函数”:S = λn.λf.λx.f((n f) x) 。调用这个函数会得到输出参数的下一个数,比如 S4 = 5。我们可以试一下:S 0 ≡ (λn.λf.λx.f((n f) x)) (λf.λx.x) = λf.λx.f(λx.x f) x) = λf.λx.f x ≡ 1
  • 加法就可以定义为: ADD ≡ λa λb.(a S) b 。 拭一下:ADD 2 3 = (λa λb.(a S) b) 2 3 = 2 S 3 ≡ (λf.λx.f (f x)) S 3 = λx. S (S x) 3 = S (S 3) = S 4 = 5.

以上是 Lambda Calculus 一些最基本的功能,作为一个图灵完全的语言,它能做的远不止这些,其它编程语言能做的,它基本也都可以做。但是我们也发现了,如果一个语言什么预先定义都没有,一切都需要开发者自己从头做起,那么在实际应用中就效率太低了。

Lambda Calculus 的发明人是 Alonzo Church,他有个大名鼎鼎的学生,图灵。Lambda Calculus 对于后来编程语言的发展产生了深远的影响。函数式编程就是受此启发而来。如今,Lambda 函数更是成了主流编程语言的标配。

训练一个黑白棋模型

之前用 LabVIEW 编写了一个黑白棋程序,作为学习 XControl 的示例。那个程序基本完整,但是缺少一个 AI 自动走子的功能。最近抽空尝试训练了一个网络模型,添加到了演示程序中。

所有AI下棋的思路都是非常类似的:根据当前的盘面,给每个可以落子的位置打分,选取分数最高的那个走法。这里的关键就是设计一个最为合理的打分算法。

黑白棋最终判定胜负的标准是看谁的子多,所以最符合直觉的打分方法是:在一个位置落子后,把己方的棋子数目作为这个位置的分数。这个算法有个缺陷,就是当前棋子数最多的走法不见得就能保证将来也棋子数最多。

解决这个缺陷的思路有两条:其一是,不要为当前这一步打分,而是向后预测几步。比如,把双方各下三步棋(也就是总够六步)后的所有可能出现的局面都列出来,然后看那个局面得分最高。考虑的步数越深,棋力也就越高,极端情况,如果预测的足够深,甚至可以找到必胜的下法。这个方法的缺点是预算量非常高,增加预测的深度,计算量会指数级增加。可以通过剪枝做一些优化,但效果有限。

第二条思路是采用更复杂的打分算法,如果只考虑棋子数量还不够好,那么就也同时考虑落子的位置,稳定的棋子的个数,周围空间的个数等等。在这众多的因素中哪些更重要,如何分配权重,对于我这种非专业棋手来说是很难做选择的。不过这部分可以利用机器学习算法,让电脑自己找到最优解。

当然以上两条思路可以结合使用:先找到一个最优的打分算法,再尽量预测到更深的步数。

我尝试训练了一个基于神经网路的打分模型。

我采用的是一个只有一层隐藏层,64节点的全链接神经网络。单隐藏层64节点的神经网络对于解决黑白棋来说,有点太小了。我也不能确定多大才合适,但估计至少也应该采用一个七八层的CNN,才能达到不错的效果。不过,我的目标不是最好的效果,而是只要试验一下可行性。并且,我需要把训练好的模型移植到 LabVIEW 代码上,复杂模型移植起来太麻烦。所以我选择了这个最简单的模型。

模型的输入数据是当前棋盘状态(每个位置棋子的颜色)和一个准备落子的位置,模型输出一个实数表示得分。

训练模型的大致思路是:让模型分别持黑子和白子,自己跟自己下棋,并且记录下每一步的走法。最终胜利一方所有走过的位置都获得正标签,失败一方所有走过的位置都是负标签。用这些标签训练模型,然后重复以上过程。

在训练模型的过程中,我遇到了一些以前从未考虑过的问题。比如,采用何种激活函数。我开始采用的 ReLU 函数,但模型始终无法被训练到预期效果。调试后发现,是 ReLU 的神经元坏死造成的。ReLU 在输入值小于零时,梯度永远为0,这个神经元很可能再也不会被任何数据激活了。这对于目前常见的有着几万乃至几百亿节点的大规模模型来说,根本不是问题。但是对于一个本来就没几个节点的小型模型来说,损失神经元是非常致命的。我把激活函数换成 Sigmoid 之后,效果就好了很多。

我训练模型的方法效率非常低下。在下棋过程中,大多数的步骤可能都不是太重要,不论走这还是走那,对最终结果影响都不大。关键的就那么少数几步,可以决定输赢。但是在训练时候,我也不知道每一步的权重应该有多大,只能假设所有步数都是一样的权重。那些垃圾步不但占用资源,也会影响训练结果。

模型训练好之后,我把它移植到了之前编写好的 LabVIEW 黑白棋 VI 中。棋力算不上很好,但至少是明显优于随机落子。等我空闲的时候,看看还能不能进一步优化下。

程序在: https://github.com/ruanqizhen/labview_book/tree/main/code/%E7%95%8C%E9%9D%A2%E8%AE%BE%E8%AE%A1/Xcontrol/Othello

DALL·E 绘图

申请到了 DALL·E 的账号,赶紧玩了几下。DALL·E 是个人工智能绘图程序,可以根据输入的文字绘制图片。它的水平肯定比不上经过训练的画师,但是对于我这种没有绘图能力的人来说,可能会有些帮助,比如,以后制作文档时,也许可以让它帮忙绘制一些插图。

我先画了一幅程序猿自画像:

Photo of a chimpanzee sitting in front of a computer and programming

儿子也画了一幅自己喜欢的图画:

Cartoon of apples, peaches, grapes and many kinds of fruits growing on one tree under a clear sky with the moon shining on the background

福特皮卡为啥卖的那么好

最近开车自驾游了一圈,总共开了4千公里,最远到了密西根州的北部。一路上明显感受到了不同地区居民对于汽车品牌的偏好是非常不同的。我居住的波士顿附近,日韩品牌的汽车是最常见的,其次欧洲品牌,美国本土品牌的占有率反而不高。等我到了密西根境内,日韩品牌的车居然成了极少数,路上看到的不是福特就是通用。

以前看统计数据,在美国市场,家用轿车卖的最多的是丰田家的;而皮卡卖的最多的确是福特f150。我当时还在想,为什么福特做轿车不行,皮卡却比丰田做得好?现在有点想明白了:皮卡的客户主要分布在美国中部以及农村地区,这些地区的消费者明显比城里那些更爱国,它们会首选美国品牌,于是成就了福特的皮卡。

禁枪还是拥枪的一些想法

最近的校园枪击案又引发了关于是否应该禁枪的讨论。先说一段我自己的经历:

很多年前,我在上海居住的时候,有一次家里招贼了。那是个普通工作日的晚上九点左右,我听到客厅窗外有一些声响。我客厅的窗帘是拉上的,看不到窗外。我住在4楼,所以根本没有想到窗外可能有人。谁知道,拉开窗帘后,赫然发现一个黑影站在窗外的空调挂机上,正在撬我家的窗户。(我当时记录的 blog

幸好发现及时,贼还没进来。我立刻打电话报警,但那个贼逃掉了,没抓到。这件事让我考虑了很多如何保护自身安全的问题。晚上九点,那正是大多数人在家又没睡觉的时间,更何况我家里还开着灯,看来那贼并只不是小偷,而是做好了入室抢劫的准备的。当时,我们那一带有过一些入室抢劫,而后杀人灭口的案例,想想还真恐怖。

假设,我和劫匪都没有任何武器,那么我100%会立刻被劫匪治服。劫匪能在4层楼外的窗台间跳来跳去,可见其身手;而我,一个程序员,主要的体力运动就是敲键盘,与匪徒肉搏没有一丝取胜的可能。

实际上,匪徒是带有武器的,至少我看到的就有一根一米多长的铁撬棍;我也很有机会可以拿到家中最致命的武器,一把菜刀。不过菜刀的杀伤力实在太弱:假设我一刀砍去,匪徒用胳膊硬挡,如果他穿的厚点,衣服都不见得能砍透。但是撬棍威力可就大多了,一棍子轮来,我若躲不开,当场就会重伤。家里有没有菜刀,对结果影响不大。

倘若我能有一件比较趁手的冷兵器,比如一把军刺,虽然我还是干不过匪徒,但这时,我只要自己不怂,抱着同归于尽的决心,就已经有很大的概率可以重伤匪徒了。考虑到我毕竟没怎么练过,就算我有10%的可能性可以重伤匪徒吧。

进一步假设,我有枪。如果我能有枪,匪徒很可能也有枪。就算匪徒是有备而来,但我占尽地利,可以躲在有利位置打黑枪。所以这时,我甚至有机会在自己不受伤的前提下就击毙匪徒。保守的估计一个数字好了:匪徒有60%的可能会击毙我;我有40%的可能会击毙歹徒。虽说仍然是歹徒的胜率高,但只要是为寻财而非复仇,有歹徒愿意冒着40%被击毙的可能性去抢个千把块钱吗?

其实,一个人遇到入室抢劫之类的恶性犯罪的概率还是相当小的。相比之下,被政府欺负才是大概率的,尤其当人民没有反抗能力的时候(当然还有个小概率就是可以加入政府去欺负别人)。歹徒可能会入室抢走你的财物;政府却可以扒了你的房子。最严重的黑帮犯罪行,可能会一次杀害几十上百条人命;但政府一句话就可以屠杀六百万犹太人,一个命令就可以枪毙七十万“坏分子”,一个政策就能饿死几千万农民,再一个运动又可以整倒几千万人。

我移居到美国不久之后,在收音机里听到一个新闻,跟踪报道好几天。事件大致是:政府看中了内华达州的一块地,打算在那建造一座太阳能电厂。征地时遇到了麻烦,一户农场主就是不肯卖地。政府软的不行就来硬的,他们查到了这家农场所在地也是某个濒危动物的栖息地,于是判处农场非法放牧,要没收农场的几百头牛。农场主一家不服,于是亮出枪来,阻拦前来执法的警察。十里八村的乡亲们听说这件事,纷纷支持农场主一家,有枪的也都带着枪来了,跟警察们对峙。最后,警察不敢硬攻,只好撤退,拆迁的事不了了之。

经常有人问,人民有枪就能反抗政府了吗?政府有飞机坦克。这个问题的答案就好比:即便我有枪,也还是打不过歹徒,但我却因此极大增加了歹徒的行凶成本,从而让歹徒放弃抢劫的念头。歹徒抢劫要考虑成本,政府抢劫同样要考虑成本。警察去拆迁跟上战场杀敌显然不会是同样的心态。战场杀敌,死了也落个好名声,光宗耀祖;在老百姓家里被打死了,能落个什么?政府打手就是这样,对于软蛋,他们可以任意欺负;遇到硬茬,立马就怂。所以,绝对不是必须有压倒性武器才能致胜。作为防守方,只要你的武器能够给对方增加足量的成本,超过对方能够获取的利益,对方就不会进攻了。

对于警察来说,工作不仅有维稳,还有保障市民的财产和安全。虽然第二项听起来更高尚一些,但对于大多数警察来说,它们都只是工作而已。我不否认,有警察不惧危险,冒死救护市民的,我们也常看到这样的宣传。但之所以宣传他们,就因为他们是典型,而非普遍现象。如果完全指望警察为了你的利益去拼命,那恐怕一定会失望的。要是你自己都不愿意为了自己的利益拼命,怎么可能指望别人为你的利益拼命?

拥枪也是尚武精神和反抗精神的体现。拥枪的人,本质上是要自己保卫自己的安全和利益,生死看淡,不服就干。反枪的人是要把自己的安全和利益托付给政府,也就是托付给一些根本不认识的人。中国历史上很多时期,不要说枪支这样的武器,就连菜刀也是被限制的。结果百姓并没有过上安全的日子,反而每每有外敌入侵,就只有被屠杀的命运。我常常想,如果当年中国人人有枪,人人都愿意有枪,抗日战争还会打那么多年吗?

这世界上任何选择都是有代价的。是否允许人民合法持枪都有高昂的代价,但这两种选择的代价却显现除了巨大的差异。

首先,允许人民合法持枪的代价都是显而易见的:那些在历次枪击案中受害的无辜平民。但禁枪的代价却是隐性的、间接的。有多少潜在的犯罪分子因为担心对方有枪而放弃了实施犯罪?我不知道,因为犯罪没有发生。如果几百万的犹太人,人人有枪,还会被送进毒气室吗?我也不知道,因为历史不能假设。

其次,差异也体现在代价由谁来付。禁枪的代价主要由人民承担,禁枪也就意味着放弃了反抗,那也就只能政府要什么就给什么了。政府管理成本因此大大降低。拥枪的代价,除了枪案的直接受害人,政府也要承担部分。每有枪案发生,政府必然被问责。

政府倾向禁枪是很自然的。美国现在还没禁枪已经非常难得了,将来对于枪支的控制肯定是越来越严格,直到彻底禁掉。

古诗生成模型

去年学习语言模型的时候做了个练习,训练了一个生成古诗的模型,可以在这里测试: http://www.qizhen.xyz/
虽然已经有一段时间每碰它了,但还是抽空总结一些相关的想法。

为什么采用诗而不是其它文体做练习?

因为对于机器学习模型来说,写诗的难度远低于写其它文体。总的来说,文体的长度越短,越容易使用模型来创作。目前常见的机器学习模型的记忆力都比较有限的,生成的文字越多,可能就会越偏离最初的主题。
一首古诗一般就几十个字,用我的个人电脑就可以训练出一个效果还不错的模型。一篇散文常常要几百字,若想训练一个能产生满意结果的模型,就需要分布式运算了。至于写小说,设置一个悬念,几十万字之后再来解开悬念,机器学习要达到这种能力恐怕还需要些年头。
选择古诗做练习的另一个好处是,训练集数据丰富,而且没有版权问题。

诗词在当今社会几乎没有什么影响力了。诗词的黄金年代无疑是唐宋。即使是之后的朝代,民间对诗词的推崇有所下降,但诗词仍是练习科举作文的基本功,依然有刚性需求。然而当今这个年代,诗词的空间几乎都被其它文艺形式占据了。诗词成了只有极少数人还在关注的小众爱好。更糟糕的是,在这少数人中,年轻人的比例更低。
被大众接受的文艺形式必定是欣赏的人远多于创作的人。典型的例子是,视频网站多是靠观众养活的,或者收观众的会员费,或者让观众看广告买东西等。但诗词网站却是靠作者来养活的,收作者的会员费。曾经写诗是创作,而如今写诗却成为了一种消费。一个缺少观众的文体,创作者队伍也很难壮大。
诗词已经没有什么不可替代的应用场合了。如果一定列出一些应用价值,那大概就是在庆典活动中用来做点缀吧。作为点缀,观众甚至都不太会注意到诗词的质量,更别提欣赏了。还有就是社交价值,如果恰好遇到诗友可以聊聊天,就好像球迷们一起聊球。 至于鉴赏,海量的古诗词还鉴赏不过来呢,新诗词在艺术价值上完全没法与古诗词相提并论。

诗词没有市场需求,大型文体目前的模型又处理不了,那么语言生成模型还有啥适合的应用吗?还是有的,比如我现在最烦的是要定期写工作总结,还要给同事领导写评语。其它单位或者公司也都有定期的提交各种心得体会、思想进步之类的硬性要求。如果AI可以在这方面创作有所提高,必然前景不错。我如果有机会得到这方面的训练数据,肯定会尝试一下的。
再拓宽一点,文字创作的需求可能不如音乐创作的需求更强烈。现在哪个视频节目需要配乐呢,专业的视频内容可能无法自动生成,但配乐没什么特殊需求的,还是可以靠AI来大量制作的。

计算机作诗的各种算法

一、基于规则的写诗算法
人们最初使用计算机解决各种问题的时候采用的都是同样的思路,就是列出解题的规则,然后按步骤翻译成计算机指令一步一步执行就行了。很多诗词还是一些比较严格的规则的:篇有定句,句有定字,字有定声,联有定对。比如五言绝句必须四句,每句五字;第一句若是平平平仄仄,则第二句一般为仄仄仄平平;第二句若以韵母eng结尾,则第四句也应当是eng结尾等等。
规则是最容易被编程实现的,因此也是被早期写诗程序广泛采纳的算法。然而基于规则的写诗算法却几乎写不出任何通顺的诗来。比如,这一句就是在基本规则指导下产生的句子:“音挑山落但,下剩物拼贪。”没人会喜欢这样的诗句。
这首先是因为写诗仅仅遵循以上列出的几条规则还远远不够,写诗还要遵循词法语法的规则,比如声音洪亮可以,跑步洪亮不可以;还要遵循自然的规则,比如乌云密布就不可以同时晴空万里;还要遵循情感的规则,比如见到大海心情开阔,见到落花黯然神伤等等。且不说这里面大多数规则都是只可意会不可言传的,即便是那些明确的表面规则,若要写全,也需要逐字给每个字开列出一大串规则来。根本没人有能力和精力来做这样的工作。
其次,规则就是用来被打破的,真正的好诗才不会被规则所限制。比如“白发三千丈”可以无视自然规则。
能总结出来的规则都没什么用,有用的规则都是可意会而不可言传的。基于规则的算法就此也就走到头了。

二、从规则到规律
计算机和人的能力有个互补的地方,计算机可以一极快的速度列出所有符合规则的文字;人类这方面能力有限,即便是经过专业训练的,也只能一下子想出少部分这样的文字,其它的要借助查资料等辅助手段。而接下来,如何从众多符合规则的字词中选出一个合适的,则是人类远胜于计算机的地方。如果你不告诉计算机怎么挑,计算机只能随机选一些字,质量可想而知。
按直觉想,只要继续添加规则,指导计算机挑选出最合适的文字不就可以了嘛。但是这需要添加太多太多的细节规则,已经没人有能力列出所有这些规则来了。人虽然没能力了,让计算机帮忙归纳总结一下可不可以呢?

规则是人为制定的。这就意味着第一,它只会是少量的方向指导性的,并不能解决任何细节技术问题。第二,只要有前人制定了规则,就一会有后人来打破规则。规律是客观存在于已有作品中的,有些规律可能根本不是作者有意为之的,但它就是自发的形成了。既然规则指导不了细节,也许可以利用一下其它作品中的规律来做指导。这其实跟一个人学习写诗的过程也十分类似。开始学习了基本规则,然后还要大量阅读其他人的作品,体会他人的作品哪好哪不好,在用于自己的作品才能写出好诗来。

利用统计学的方法,还是可以比较容易的计算出古诗词中的一些规律来的。最直接的,高频词。比如,统计出来的《全宋词》高频词包括:东风,何处,人间,风流,归去,江南,西湖,斜阳,明月等。高频词就是大家看着最顺眼的那些词,如果计算机从这些刚频词中选词,而不是随机挑选,那么结果也会顺眼的多。比如将宋词中的高品词随机组合一下:“回首明月,悠悠心事空,西湖何事寂寞中,风吹斜阳匆匆”,不那么别扭了吧。这也是为什么总有人说计算机写出来的诗怎么感觉都眼熟的原因,因为大量使用了高频词啊。

只考虑词频显然还不够,就算都是频词搭配起来也可能还是非常别扭的,比如:“江南大漠凭栏鱼”。所以还要考虑搭配,比如“流水”经常搭配“浮萍,恋恋,落花,相随”等词。那么,如果诗中如果已经选了流水,就应该尽量从搭配流水的高频词里继续选。其它的还有很多规律可以总结,比如对账的规律“云对雨,雪对风,晚照对晴空”,等等。

有了这么多细节的规律帮助,写诗程序可能依然还是很烂。说实话,研究这些的大多是写诗外行,包括我在内,我自己都不会写诗,又能想到多少有用的规律呢?更多的规律是我想不到的,比如很可能在某个特定的地方放个特定的夸张就会有神奇的功效?或者放个谐音梗?或者就是我完全想象不出来的。

我想不出来的,能不能让计算机去想呢?让计算机不但帮我把规律的数据计算出来,还要帮我研究需要总结哪些规律,甚至是我都想象不到的规律?

三、基于机器学习的写诗算法

“机器学习”从名字也能看出来就是让计算机自己学习,简单来说,你给计算机提供一个训练集,比如以《唐诗三百首》为训练集。也不必告诉计算机怎么学,学什么,反正计算机自己去研究吧,然后计算机利用它学习到的知识输出一些东西出来。
机器学习不是什么新东西,也发展了几十年了。
在自然语言处理方面机器学习的应用也是十分广泛的。然语言处理常见的几种问题包括:分类(比如判断一封信是否是垃圾邮件);知识问答;总结中心思想;翻译;聊天对话等等。不过所有这些问题都是相通的,用一个文字生成算法就可以解决所有问题。文字生成是指给计算机一段上文,然后计算机生成一段下文。比如,上文是一封信,下文“是”或“否”,就可以用来判断垃圾邮件了;上文给一段中文,下文生成英文就是翻译;上文是上联,下文生成下联,就解决了对联问题;上文标题,下文生成诗句,就解决了写诗问题。
机器学习中有各种各样的算法,简单问题,比如垃圾邮件分类,使用简单的贝叶斯分类算法就可以得到比较完美的解决。不过像文字生成这样复杂的问题,就必须用到人工神经网络了。人工神经网络借鉴了动物大脑的结构,依靠神经元组成的网络来模拟和解决各种问题。人工神经网络可以学习,然后根据学习到的东西输出一些数据。对于复杂的人工神经网络,人类已经不能完整的分析出它们到底学了什么内容,只知道学习的效果不错。
当年在学校学习是做的人工神经网络作业,建立的网络只有二十几个节点(神经元)。后来解决一些稍微复杂点的问题也不过就用几百个节点,还比不上蚯蚓的“大脑”。生成文字问题可是复杂的多的问题,小的神经网络很难产生满意的效果。生成单词词组一般至少需要上千个节点;生成句子就要再多几个数量级:我训练的诗词生成模型的节点数已经过亿了。目前,在文字生成领域表现的比较好的一个模型叫GPT3,它有超过一千七百亿个节点。这个数量已经超过人类大脑皮层神经元的数量了,不过不用担心,人工神经网络的效率远远不及人脑,人工神经网络的节点数很可能每年都有翻倍,但即便如此,它们也不会在未来几年内具有真正匹敌人类的创作能力,尤其是像小说这样的大部头文字。
有了这么强大的机器学习功能,我这种门外汉也不需要太了解诗词创作的技巧了。把现有的诗词交给机器,让它学就是了呗。可是这样学出来的机器也只会输出它学过的那些诗词里有的东西啊。举个例子,假如《唐诗三百首》里只有关于牛的诗,没有任何提到羊的诗,那么机器很容易就学会了,牛吃草,牛挤奶。机器也只会写牛吃草,牛挤奶之类的东西,它永远也不会自主写出羊吃草,羊挤奶,虽然对于人类来说这是非常自然的可以借鉴过去的。

更多的知识可能并不在训练集中,要让模型掌握这些知识,仅仅使用训练集是不够的。需要首先使用预训练集对模型进行初步训练。比如使用维基百科上的所有内容对模型进行预训练,让模型先学会一些基本知识,比如牛和羊是非常相关、类似的动物。这样之后,在使用诗词对模型进行训练,模型会利用之前预训练时掌握的一些知识。在诗词训练集中学到了牛吃草,那么,模型也会根据之前掌握的知识,创作出羊吃草这样的文字。

我做的练习模型

一、模型选择
我在自己做练习之前,先查看了一下能够找到的别人写的一些诗词生成模型。使用简单的LSTM、seq2seq等模型是不太容易生成行文自然的诗句的。真正效果好的模型都是最近两年在超大模型比如BERT,GPT等基础上构架上训练的。我在github上看到一些例子,效果真的很好,我作为理科生,已经分辨不出某些诗句是AI还是真人写的了。

肯定是越大的模型效果越好,但是越大的模型训练越费时费钱。
另外一个考虑的因素是,有的预训练模型可以直接做文本生成,有的还需要在附加一些神经网络层才可以。我用了GPT2的小型模型做练习,这个模型写大文章可能差点,但应付诗词还可以;而且GPT2可以直接生成文字,用起来很方便。
GPT2在某些情况下,可能还是太费钱了一些。训练一个GPT2模型一般最多两三天吧,就算用GPU,只租两天也不算是太贵。可是要长期发布就麻烦了。这个GPT2模型如果只使用CPU做预测,当下主流电脑做一个预测需要十秒钟以上,有点太长了。使用GPU,速度可以快一百倍左右,但价格也差不多要高一百倍。目前租一个云服务器,只用CPU的话,第一年只需要¥70左右;租一个带GPU的,需要¥7000每年。

二、训练集选取
整个项目里,整理训练集是最费时间和精力的工作。对于多数机器学习项目,都是训练集越大效果越好,但是做诗词生成也学并不是的。古人创作过无数的诗词,能广为流传的是极其少数的一部分。也就是说,我们能够整理出来的诗词大多数其实写的并不好,如果让电脑跟它们学,恐怕也学不了多好。理想情况是只收集那些高水平的诗词。另外,太多的训练数据会激励算法使用一些不太常用的字词来作诗。这些词语如果应用不当,就非常影响作品可读性。反之,风月、云雨、山水等高频词,无论如何搭配,读起来都不至于太别扭。

当然小数据集也有缺点,它只会覆盖部分主题。我实验有了相对较小的数据集,主要包含了《千家诗》、《唐诗三百首》和其它一些我个人比较喜欢的诗词。训练好的模型对于处理风花雪月这类常见题材是可以产生出相对不错的结果,但是如果让它写个没训练过的内容,比如关于建设现代化等的题材,就没办法生成的很好了。

如果资源足够,采用大训练集,给不同质量的诗词配上不同的权重,应该可以获得更好的训练结果。

合辙押韵是诗词的一个非常重要的属性,但是我在练习中没有考虑这一问题。为程序添加人为指定的平仄,尾韵等规则并不是一个好的办法。因为那些最为脍炙人口的古诗中,很大一部分,以现在的读音朗读都不在押韵,或者至少不是精确的押韵了。一首诗读起来顺不顺耳,最主要的还不在于是否押韵,而是是否耳熟。我们从小就背着那些不再精确押韵的古诗,审美早已适应这种宽松的规则了。过度强调押韵反而会影响生成诗词的质量。

最好的办法还是让模型自己去学习韵律。我的训练集只包含文字,没有文字的发音信息,所以模型无法学到发音规律。只要把字音信息也加入数据集,把数据集中的每一个字都扩展为“字+拼音”的格式。比如,原本数据集中的文字是“风雨送悲声”,改为“风feng1雨yu3送song4悲bei1声sheng1”。这样一来,文字的平仄韵律信息就都有了。模型自然也就可以学习到如何押韵了。
同理,我在训练的时候也没有把文字是如何书写的这个信息传递给模型,所以模型绝对不会掌握比如对拆字联这样的功能。如果把文字的书写信息(使用图像,或者抽象成偏旁部首)加入训练集,那么模型就可以学习到与书写有关的功能了。

三、损失函数
我偷懒,使用了默认的损失函数,也就是说,训练时没有添加额外规则。但实际上这样会影响模型的效果。一些规则,模型是很难自己学习到的,尤其是数据集也不够大的情况。比如,押韵、用词要丰富多彩等一些规则,模型自己领悟不到。
通过挑选训练集,可以弥补一部分不足,比如比如尽量避免有重复词的诗句,以方便模型更快的掌握这个规律。

四、训练时间
看别人的经验,训练时间在使用GPU的情况下一般一两天。这与模型大小和数据集有关。我试验的模型和数据集都不算太大,用GPU一两个小时效果就已经不错了。这时候,模型还会时不时产生一些莫名其妙的结果。增加训练时间,可以让模型产生更规范的结果,但同时,也会削弱预训练数据的作用。个人感觉,虽然这时候模型的输出减少了明显错误,但也同时减少了惊艳的诗句。可能模型也跟人一样,要有创造力、要天马行空才能写出神句来。学习的太多,反而影响创造力。
我也试了一下只使用CPU做训练,比使用GPU要慢一两个数量级。

五、生成结果
我个人纯理工背景,文采很差,对于诗词的好赖,只能大致一评。

我觉得程序生成的诗词基本还行吧,虽然也有一些不足。首先是生成的诗完全没有考虑押韵。其次是生成的诗不是每一首都令人满意的。当然也可能我的水平太差,看不懂模型生成文字的深意。不过以我的水准来评判,程序有时还是会产生一些相当不错的诗句的。我在计算机生成的诗篇里挑拣了一些,个人认为还算可以的,发表到了一些诗友论坛。诗句文采怎样先不论,至少没有读者跳出来说你这根本不是人话,也算是成功了。

首先测试作对。吟诗常以做对为基础嘛。以“倦鸟无心归去”为上联,电脑对下联“野花有意偷开”。对的妙啊,能把“花”和“开”联系起来就算不错了,还能把“野”和“偷”关联上,已经超出我的预料了。我不相信电脑真的明白什么是野花,也许只是在训练集或预训练集中野花总和偷摸之类的词在一起吧。但无论怎样,这表明电脑水平还是可以的。
有时候模型写的对联思路极其开阔。比如看到这一副生成的对联时,我差点一口老血喷到屏幕上:“千家百姓,共建和谐社会;三宫六院,同沾雨露春风”。

诗人一生的经历都会体现在作品里,比如,读李煜的诗,就总是会联想到他亡国之君的悲伤;读苏轼的诗,会让人燃起在逆境中乐观的态度。计算机没有任何过往经历,它堆出来的词再美丽也也没有任何内涵。但反过来,(高情商说法)也可说电脑作诗最擅长那些不需要具体内容的空洞题材,比如说泛泛的写景,赞美称颂等等。而且,前面也讨论过,如今社会有使用需求的恰恰也是这一类的诗。比如写马屁诗,模型一分钟至少能写八百首。

模型可以日夜不停的写,我却没有精力去读,随便摘取了一些,张贴在这里:https://www.facebook.com/mlshici

孤僻、内向的进化优势

我是个性格比较孤僻、内向的人,不太喜欢社交活动。还没有严重到社交恐惧,就只是不喜欢社交活动。我不善言辞,而且业余爱好都是比较冷门的。像平时大家谈论的比较多的八卦、体育之类的内容我都毫无兴趣,所以经常觉得跟别人无话可说,自然也就不喜欢参与群体活动了。

在当下的社会中,我这样的性格是一种明显劣势。无论什么工作,想做的好,做出成就,都离不开与人交往。不善社交可能是阻拦很多人成功的主要障碍。有时候我就想,内向的性格既然处于明显劣势,怎么就没在进化中被淘汰呢。直到这次疫情出现,我才意识到,内向性格也有进化优势的一方面。讨厌社交的人,总是避免与别人接触,得传染病的几率会低很多。尤其是出现现代医学之前,传染病引起的大瘟疫动则就消灭几分之一的人口,结果筛选出了大量讨厌社交的基因。

我们的世界是虚拟的吗?

在计算机产业星期之前,恐怕很少有人会考虑我们的世界是不是虚拟的。随着计算机运算能力越来越强,玩家在游戏软件中的体验越来越趋近于现实世界,于是有人开始思考:我们的世界或许也只是一个模拟软件。有一些著名的电影比较好的体现的这一想法,比如《第十三层楼》(The Thirteenth Floor),《黑客帝国》(The Metrix)等。

有一些人坚信我们的世界是虚拟的。有两个主要的原因支撑了他们的信念。

第一条是从概率上来推算的,有点类似费米悖论。既然人类都可以做出比较逼真的虚拟世界了,可以想象,比人类更高级的文明是一定也会创建虚拟世界来模拟和研究他们遇到的问题的。而且一旦掌握了虚拟技术,一个高级文明可以创建成千上万的虚拟世界。所以,虚拟的宇宙的数量应该是大大高于真实的宇宙的数量的。这样看来,我们生活在虚拟宇宙中的概率要大于生活在真实宇宙中。

第二条与科学的发展相关,尤其是量子力学的发展。以前人们自然的想法是物质和能量都应该是连续可分的。比如我用力推桌子,这个力可以是任意大小,可以是无限小。但是在近代,科学家们开始认识到,这个力不是连续的,它是有最小单位的,一份能量的最小值就是普朗克常数。并且,物质和能量只是同一种东西的不同表现方式。能量不可无限细分,物质同样不能,物质由不可再分的基本粒子构成。
一个很神奇的现象是基本粒子具有全同特性,这是由玻色最先提出来的。我们日常基础的宏观物体中,没有两个物体是完全相同的。比如两把椅子,看起来可能差不多,但观察细节总能找到不同。但是电子和另一个电子之间却具有完全相同的属性。他们的状态可能不同,但是所有属性都是一模一样的,比如所有的电子在相同状态下都具有完全相同的质量,完全相同的电量等等。对于基本粒子的全同特性有两种主要解释(都是假说)。
第一种是,每种基本粒子在宇宙中可能都是只有一个个体。比如整个宇宙只有一个电子,我们看到的每一个电子都是同一个电子,所以他们的属性当然都一样。虽然在人类的感知中,时间只能朝一个方向运动,但基本粒子也许不受此限制。一个电子也许已经在时间纬度上来回运动无数次了,以至于我们以为在当前的时刻可以观察到无数个电子。
另一种解释是,基本粒子并不是一个具体的物质,它们只是一些抽象的逻辑单元。想象一下,我们在计算机里使用软件虚拟的一个世界。这个虚拟世界极其复杂,以至于生活在虚拟世界中的智慧生物已经开始研究这个世界的构成了。这些智慧生物发现他们的世界的最基本构成就是一种二进制逻辑单元,这就是他们世界的“基本粒子”。他们同样会发现,他们世界的基本粒子是全同的:每个粒子都是一个只有两种状态(0,1)的单元。所以,这是不是也说明我们世界同样是被虚拟出来的,我们已经发现了这个虚拟软件的基本逻辑单元?

真的能够通过科学的手段证明我们这个世界是真实的或是虚拟的吗?我觉得是不行的,这已经超过“科学”的范畴了,只能用哲学方法来讨论了。因为,一切可以证明世界是真是的证据可能都是被虚拟出来了;反之,一切可以证明世界是虚拟的证据,也都有可能就是真实世界的本来样貌。

在我看来,世界是虚拟的这种思想就是神创论的一个变种或者说发展。有些人的心里是需要一个万能的规则制定者的。我不是坚定的无神论者,我觉得神的存在或不存在都有可能。但是我觉得就算是有神存在,神也不会在意人类的一切。不论我们的世界,对于神来说是不是“虚拟的”,它对于我们来说,都是真实的。

中美医疗制度的一些不同之处

最近上海封城,搞得民怨沸腾。我在跟家人朋友讨论的时候,想了很多事情。大多数想法都没时间记录下来。因为正好给个朋友介绍了一下我在美国看病的经历,就写了一点关于中美医疗制度的比较。

(我只能比较一下波士顿和上海这两个我最熟悉的城市。这两个城市在各自国家都是拥有最好医疗资源,其它地区的情况可能并不相同。)

其中一个区别是:在中国,医院从病人那里收费,病人再从保险公司拿钱;而在美国,医院直接从保险公司收钱。

在中国,医院为了多赚钱,会倾向于过度医疗。病人只要还负担的起,一般不会为了省钱而承受耽误病情的风险。我在上海工作的时候,只要觉得有哪疼了,哪不舒服了,就立刻去医院。医院通常有用的,没用的,CT,B超之类的都查一遍。我小时候更是,只要一感冒就去医院,一去医院就是打一个星期的青霉素。长大之后才知道,青霉素对于感冒一点用都没有,小时的疼全白受了。

在美国,如果保险公司认为有过度医疗的嫌疑是会拒绝向医院付款的,所以医院为了避免收不到钱,会倾向于谨慎治疗。只要符合规定,能不治疗的就不治疗,能不检查就不检查。我大约20年前第一次体验了在美国看病。我当时发高烧,感觉自己快要不行了。强打精神来到医院,结果医生根本不当回事,让我自己去药店买两片非处方的退烧药吃。美国医院和药房也是彻底分开的,不能靠卖药赚钱,所以对于开处方药也是非常谨慎。后来在美国常住,多数时候去医院,连专科医生都见不到。打发我的都是“全科医生”或者叫家庭医生,相当与中国医院里挂号台的。当然,这个比喻有点夸张。在中国挂号台,护士一般也就问一句话:“哪不舒服”,然后就告诉你去什么科。全科医生问的非常全面,还要听听看看,几十分钟才会做结论。我多数时候得到的结论都是:你没病,回家去吧。试过几次后,我也就不像在上海时候那样,总往医院跑了。

一个典型的例子是我和我爸看耳石症的经历。我俩都得过这个病,症状都是眩晕、呕吐。我爸在中国看的,CT、核磁一通操作下来,并没有什么真正的发现,然后开了一大堆不相关的药回家。我在美国看的病,医生跟我聊过之后,让我做了一些特定的动作供她诊断。最后她告诉我这是耳石症,过几天自己会好,就让我回家了。

美国这边,如果医生觉得你的病不需要治疗,或者是已经没得治了,是有可能会不顾病人感受而放弃治疗的。有些人可能还一下子接受不了这样的处理方式。新冠刚出现那会,大家心里都是比较害怕的。我们这里有位女士阳性了,跑去医院看病。她可能以为这么严重的病情应该进重症室监护室了,结果医生却让她回家。医生大概是觉得根本不用治疗,就别来浪费资源。但那位女士却无法接受,于是隐瞒病情飞回国,结果没进重症室,却进了拘留所。

中美这两种制度都有各自优缺点的。

中国的方式容易造成资源浪费,中国的医疗资源本就不及美国。我每次去医院,医院都挤满了人,排队要排几个小时。可能那里面相当一部分人都跟我一样,纯粹是去找安慰的。尤其是遇到新冠疫情这样的情况,大家都很恐慌的时候。医院会被轻症患者挤满,从而耽误了救治那些更需要被救治的重症患者。

美国这边医院里的患者就少的多了。即便在新冠疫情最严重的时期,我也没有在医院里见到拥挤不动的病人。因为余量够大,在疫情出现的时候才可能应对的更从容。美国的医生,一般每天只接待十来个病人,压力相对(中国医生)小多了,收入又高,所以他们对待病人的态度非常好。对待儿童更是格外的好,小画书、玩具、冰激凌都是现成的。我在美国遇到的每一个医生态度的特别好,绝对不亚于商店里向我推销的售货员。在中国,一个门诊医生一天处理上百个病人都是常态,别说好态度,能耐心听完病人讲话的就算不错了。我还遇到过张口就骂病人的医生。

美国医疗最大的问题就是贵。医生准入门槛高,又不能像中国那样薄利多销,于是就高收费。跟医生聊几句,至少也要付个几百美金的。一个国内收费一千人民币的门诊小手术,在美国可能要上万美金。还是说我看耳石症的经历吧,我爸在中国做了一大堆检查,拿了一大堆药;我在美国只是和医生聊了二十分钟。虽然过程差别巨大,但最后效果差不多,我俩付给医生和医院的钱也是差不多的。虽然直接付款的是保险公司,但羊毛出在羊身上,最终还是导致大家都要缴纳高昂的收入税和医保费。

优化旧照片

最近测试了一些翻新旧照片的算法,主要是试着把模糊的小照片翻新成清晰的大照片。据说淘宝上有很多人做这个生意,主要是靠手工来修复的。网络少也有不少PS修复旧照片的教程。不过我不会使用PS,所以只能依靠算法了。修复旧照的人工智能的算法也是有一些的,但目前来说,人工智能算法的修复效果在总体上还是比不了人工修复的,但某些情况下表现也还不错。

第1步 选照片

首先找一张老照片:

要想效果好,照片一定要找上图这样的:大头照,而且只有少量折痕和磨损。我也测试了一些质量更差的照片(人脸所占像素太小,有大片磨损等),人工智能对于它们目前还还无能为力。

第2步 去网纹

很多老照片都不是光滑的,像纸上有排列规则的凸点,扫描之后的图片上就会有网纹。上图的旧照片上可以明显看到这些网纹。我找的几个人工智能程序都是解决特定问题的,无法很好的处理这些网纹,所以先要人工去除这些网纹。网上搜了一下,没有专门去除网纹的程序,只有PS相关的教程。PS是收费软件,我也没有。好在有一些免费的图像处理软件可以替代PS。我用了GIMP(GNU Image Manipulation Program)加上FFT插件。FFT是快速傅里叶变换的缩写,用于把时域信号转换为频域信号。

上图经过傅立叶变换,在频域上的图像如下(灰度图部分):

由于原照片里的网纹是规则的(有固定频率),频域图会在一些特定的频段里出现高能量区域,也就是上图用红圈圈出来的部分。我只画了三个圈,但其它那些亮点(除了中心低频区域外),都是网纹造成的,只要把这些亮点全部涂黑,再做反傅立叶变换,新生成的图片就是被去除了网纹的照片了。

第3步 上色

GitHub上最受欢迎的黑白照片上色程序是这个:https://github.com/jantic/DeOldify

我试了一下,效果只能说马马虎虎,上了色的照片看上去还是像旧照片,只是把原来的纯黑白色调中加入一点肉色,一看就不是真的彩色照片。

如果会PS的,肯定还是PS效果好。

第4步 清晰化

我在GitHub上找到了两个照片变清晰的软件,一个是微软的 https://github.com/microsoft/Bringing-Old-Photos-Back-to-Life

一个是腾讯的 https://github.com/TencentARC/GFPGAN

两个软件各有千秋,比如微软的可以修复裂痕,但腾讯的可以放大照片。具体那个效果更好,要针对不同情况测试一下。

这两个软件都是侧重于对人头像进行优化,对其它区域的优化会差一些,所以经过它们优化的照片常常是人脸非常清晰,但衣服还是模糊的。再有它们对于眼镜的处理都不太好。常把眼镜当成皱纹;或者把眼镜上的反光当作眼睛来处理。有时候生成的照片非常诡异吓人。

另外,它们也没法处理太小的人像,或是有缺损的人像。

处理后的效果

LabVIEW 的 unicode 问题

以前从来没有意识到过这个问题,直到最近,我试图去查看自己很久之前写的一些VI的时候才开始注意到它的。

先简略的介绍一下unicode的背景:

计算机是在美国被发明的,所以很自然的最开始只考虑了处理英文。早期最著名的关于如何在计算机中表达字符的标准是ASCII标准(American Standard Code for Information Interchange,美国信息互换标准代码)。它定义了128个字符,包括英文字母大小写,数字,常用的标点和一些特殊符号。当时世界上所有的计算机都用同样的 ASCII 方案来保存英文文字。这个方案最大的问题是只包含了英文字母,于是其它国家,组织和公司纷纷开始扩展这个标准用以支持其它字符,比如制表符、数学运算符号、中文字、日文字等等。比如在中国最常用的标准 GB2312,GBK,GB18030 等都是对 ASCII 的中文扩展。之后扩展出的这些标准有个很大的问题,就是同一个数值在不同的标准中被定义了不同的含义。某一数值在中文的标准下可能是一个中文字符,放到韩国的系统的某标准下,可能就是一个完全不相关的制表符。这就造成了,在中文环境下开发的软件,运行到韩国系统下显示的就完全是乱码。如果有人需要在一个系统下同时运行一个中国软件和一个韩国软件,恐怕就不行了。

为了解决这个问题,1990年开始,计算机业界开始研发一种编码标准,它可以覆盖全世界所有的字符,这样任何一个字符都有自己独占的编码,这样就不会出现换个系统就乱码的问题了。这就是unicode,也叫万国码、单一码。unicode规定了字符集,但是这套字符集也还有多重不同的编码格式。Windows采用了UTF-16LE格式的unicode编码(UTF全称为 Unicode Transformation Format),使用16位的(双字节)数据表示一个字符。但是当前最流行的unicode编码格式却是 UTF-8,这是一种变长的编码方式,根据字符的常用程度,可能由1到6个字节来表示。目前大多数的unicode文档采用的都是 UTF-8 编码的。

目前,大多数的软件也都是基于unicode的了。但是LabVIEW始终没有支持unicode。虽然Windows很早就开始支持unicode了,但是为了兼容那些还没有支持unicode的软件,Windows系统保留了一个默认字符集的设置,对于非unicode的软件,Windows会使用默认的字符集来解释那些字符编码。国内使用的电脑几乎都设置了默认的字符集为中文字符集,所以,一个软件是不是支持unicode,对于绝大多数中国用户来说根本感觉不到什么差别。所以,我之前也从来没觉得LabVIEW不支持unicode有什么问题。

直到最近我才发现了问题。我家里有两台电脑,一台操作系统安装了英文的Windows,并且没有修改过默认的非unicode字符集;另一台操作系统是中文Deepin Linux。我在两台电脑上都安装了社区版的LabVIEW 2021。我想在两台电脑上查看一下自己十年前写的一些VI,这时才发现,如果VI的路径中包含中文,是无法在Windows上被LabVIEW打开的。不过,我主要使用的是Linux,在Linux上还可以打开大部分的VI。但很快我就又发现,如果那些VI中如果有我以前设置的中文的常量或注释,在Linux下是无法正确显示的。但是呢,我还可以再Linux下添加中文常量或注释。之后,又遇到了更严重的问题:以前那些项目中,如果有中文名的子VI,或是库中、类中有中文名的VI,就通通都打不开了。我开始还以为是不是10年过去了,LabVIEW系统有什么变化,但是没听说过啊。于是我开始怀疑是不是文字编码的问题,我把Windows的默认字符集换乘了中文,果然在Windows下一切正常了。而且我还发下,在Linux下,给VI添加的中文注释,拿到Windows下,看到的全是乱码。

总结我看到的现象,我深刻怀疑,目前LabVIEW在Windows下是不是用unicode的,字符编码由Windows系统决定,对于大部分中国用户来说,采用的是GB18030中文编码;但在Linux下却使用了UTF-8编码。

在Linux由于系统和LabVIEW采用的都是unicode,所以一个VI在不同Linux版本下应该行为都是一致的。但Windows系统用的是unicode,LabVIEW用的却不是。我们现在假设一个项目中有两个VI,本别是“界面.vi”和“任务1.vi”,其中“任务1.vi”是“界面.vi”的子VI。在操作系统层面,有两个文件:“界面.vi”和“任务1.vi”,它们的名字都是使用unicode保存的。但是在“任务1.vi”内部,它记录了自己要调用“任务1.vi”,却用的是非unicode编码保存的子VI的名字。所以LabVIEW中用于记录这个子VI的一段二进制数据与操作系统中记录这个子VI文件名使用的二进制数据是不同的。每次LabVIEW需要操作系统找到相应的VI文件时,还需要做一次编码转换,把文字转为系统认识的unicode编码。如果保存VI,和读取VI都是在中文Windows中,这没有问题,VI名字总能被正确转换。但是把之前在中文Windows系统下保存的项目拿到非中文Windows(默认语言编码不是中文)下打开,文件名编码转换这一步就会出错,LabVIEW就无法找到正确的VI了。同理,中文Windows下保存的VI,如果有中文名,拿到Linux下打开会出错;反过来也是一样会出错。

总之,由于LabVIEW在Windows下没有使用unicode,造成了中文显示在不同系统下的不一致行为,切换系统就会出现乱码,甚至VI无法被加载。目前没有什么好办法可以解决这个问题。我只好将来在LabVIEW中只使用英文,不是用中文了。

重新制作了书籍网页

最近学习完了React,又使用Docusaurus重新为书籍制作了网页:https://lv.qizhen.xyz/

实际上,Docusaurus已经帮我作为绝大部分工作,我学习的React技能基本没有用上,毕竟书籍的格式还是比较简单固定的,不需要非常炫酷的界面。

之前是用Docsify制作的页面。我对于Docsify的界面和功能已经非常满意了,最主要的问题是它制作的网页是客户端渲染的,搜索引擎都不会爬取。墙外的搜索市场已经被Google垄断,恐怕Google是不会有动力去支持搜索客户端渲染页面了。国内的搜索市场竞争还更激烈一点,可以它们居然做的都比Google还差很多,也没有指望。只能自己修改网页了。

Docusaurus使用起来比Docsify麻烦不少,所需技术门槛也稍高。这一是因为Docsify只支持制作文档网页,而Docusaurus还可以制作个人主页和博客,功能复杂了不少,虽然都是我都用不上的。二是因为Docusaurus是制作服务端渲染页面的,多了一个Build步骤。再有Docusaurus比较新,资源相对来说少一些。

但不管怎么样,Docusaurus也是非常令人满意的。现在Google可以爬取我的页面了,国内的搜索引擎都还不行,但这不是网页的问题,最可能是我的域名没有工信部认证,国内引擎不愿意访问。

Docusaurus开发团队非常活跃,应用前景应当超过其它类似工具。非常推荐用于创建静态的个人主页、博客、文档等。