中文房间之争-浅论到底什么是智能

中文房间问题

1980 年,美国哲学家 John Searle 提出了一个思维实验:中文房间(Chinese Room Argument),它是这样的:

假想在一个密闭的房间中,有一个完全不懂任何中文的美国人。他手上有这两样东西:1)所有的中文字符集(数据);2)如何处理这些中文字符的规则书(程序)。现在,门外有人在纸条上用中文写上一个问题,递进房间当中(输入)。房间里这个完全不懂中文的美国人,按照手头的规则书,把问题的正确回答拼凑出来后递出去(输出)。按照图灵测试的标准,房间里的人被认为具有理解中文的智能,然而,实际上他对中文一无所知。

要注意的是,这本规则书上仅仅只是基于语法(Syntax)的,而不涉及任何语义(Semantics)。也就是说,你可以理解成,这本规则书上,罗列了一切可能的中文问题,并给出了相应的中文回答,房间中的人,只需要按照对应的回答,拼凑出相应的中文字符递出去,但这个过程中,他对问题和答案是什么意思,一无所知。

这个思维实验提出后,长达三十余年的时间里,各方都提供了各种回复与反驳。Searle 最初只是希望借助于这个思维实验,来指出图灵测试在验证智能方面并不是完备的,即我们该如何辨别智能。不过,随着论战的升级,它实际上指向着一个历史更悠久的路线之争:智能是可计算的吗?

其实,所谓路线之争,本来就没有绝对的对错,关键在于你持有什么立场和信念。我们就来看看,中文房间面临的各种诘难和相应的回复。

系统论回复(System Reply)

最常见的回复,也是 Searle 在其论文中首先驳斥的,就是系统论。在这种观点下,房间中的那个人的确不理解中文,然而,如果你把房间当作一个整体系统,则可以说这个系统是理解中文的。这一派观点的人做出了一个类比:想像一下人类大脑中的神经元,单独拎出来一个神经元,它显然不理解中文,然而,人作为一个整体,是可以理解语言的。

Searle 对此的回复是:让我们更极端一点,现在房间中的这个美国人,背下来了所有的中文字符和整本规则书。现在,他离开房间出门和真正的中国人交谈。对面走来了一个人问他「你给王菊投票了吗?」,他根据大脑里内化的规则书回答「没有」。尽管他对「王菊」和「投票」是什么,一无所知。Searle 在 1984 年进一步指出,系统论一方的错误在于,误把这个问题当作整体论和还原论之争的一个分支,然而实际上,他驳斥的是单凭语法本身并不足以构建语义。

系统论的一个变体是「虚拟心智论(Virtual Mind Reply)」。和系统论一样,它同样不认为房间中的人具有智能,但也不认为这个系统整体具有智能。事实上,它完全不关心智能是通过谁或什么(Agent)展现了出来,而只在意在整个过程当中,是否有智能被创造了出来。许多人在这一观点上进行了进一步的演化,继而讨论到:中文房间之中,真正的智能体现在那本规则书上。正反双方对这本规则书是否是可穷尽的,以及意识和本体论的行为主义之间的关系展开了辩驳,不过讨论的内容过于学究,这里就不展开了。

英国物理学家和数学家 Roger Penrose 在 2002 年回应过这一系列相关的观点,把「中文房间」问题扩大化到「中文体育馆(Chinese Gym)」变体。现在,让全中国所有的人,每个人模拟一个大脑中的神经元来处理信息。难道中国这个国家,或者在这个过程当中,存在着任何形式的智能吗?好吧,虽然这个变体的思维实验,核心目的是驳斥智能不需要依附主体的这一看法,但如果你想到了《三体》一书中,三体星人全民模拟一台计算机的行径,很多人的确可能会觉得这个过程当中存在着智能。

机器人回复(Robot Reply)

机器人回复赞同 Searl 的观点,即中文房间中无论是那个人,还是房间整体,都不存在智能可言。但原因在于,仅仅语言是不足以构成智能的,按照这一方的观点,如果我们造出了一个机器人,它不仅仅可以根据语法处理中文,还可以识别图像、声音、运动,并给出相应的肢体、表情等反馈,这个机器人就具备了智能。按这样的标准,西部世界中的接待者(Hosts)显然是具备智能的。

Searl 和一众人并不认为这样的机器人真正具备了智能,反驳的主要切入点,是从意向性(Intentionality)和因果关系(Causal Connection)两点入手的。通俗一点来说,这样的智能只是一系列反应的组合,硅谷最新一季当中的 Fiona 就是典型代表,她的语言、面部表情、肢体动作只不过是按照程序例行公事,却没有自发的因果性。

v2-2b8d6ad7f7bcb3317a41f8ff13d2f730_r

模拟大脑回复(Brain Simulator Reply)

模拟大脑回复跳开了中文房间这个问题,而是直接向 Searl 质问:如果我们用非生物的方式,模拟出了一个和大脑神经元一模一样工作的装置,它拥有智能吗?想像一下西部世界中接待员脑中的白色装置,就可以认为是这个,显然西部世界里的机器人,比上面硅谷中的那个 Fiona 更像拥有智能的人类。

Searl 的回复依然是不能。模拟大脑本身并不意味着智能,他在这里给出了一个很类似于上文「中文体育馆(Chinese Gym)」的思维实验变体,目前来看,Searl 就是既不认同智能直接等同于大脑的电化学信号。不过,在这个方向上讨论下去,Searl 隐晦地表达出了他对于什么是智能的深层看法。

模拟大脑回复一方进一步质问:我们现在不模拟大脑了,而是逐步替换大脑。假设有一个人,我们把他大脑中的突触,一个接一个,替换成特制的晶体管,这些晶体管能十分逼真地模拟大脑突触。那么,当全部替换完成后,这个人还拥有智能吗?

Searl 一方的回答是:在我不知道他被替换前,我可能会认为他拥有智能。但一旦当我知道它是一台图灵机后,我便不再认为他具备智能。这个看似已经近乎于狡辩的回复,其实暗藏着 Searl 一方对智能更深层的看法:凡是可以被计算的,都不再是智能。其实,直到今天,也没有人可以给智能下一个令所有人满意的定义,而中文房间更深层次的分歧正在于:智能是一个可计算的问题吗?

他人心智回复(Other Minds Reply)和直觉回复(Intuition Reply)

还有许多人绕开了关于智能的定义,而尝试从如何辨别智能的角度出发。在他们看来,我们之所以认为中文房间中的人,或机器人不具备智能,纯粹是出于我们固有的偏见。按照同样的标准,我们一样也无法判定身边的人是否真的具备智能,或者仅仅只是表现出智能。这一系列的回复,都强调图灵测试的重要性,正在于摒弃我们固有的偏见,而单纯从结果出发来辨别智能是否存在。

美国哲学家 Daniel Dennett 提出了哲学僵尸(Philosophical Zombie),也可以作为一个补充回应。用一个非常简化但并不准确的版本来说,这里的僵尸不是美剧 Walking Dead 中的僵尸,而是指那些只表现出智能,却并不真的具备智能的人类,假设他们存在的话。Daniel Dennett 认为,首先智能显然是一个进化出来的产物。其次,中文房间提出的观点如果成立,即表现出智能的主体并不一定真正具备智能。那么,两个同样在行为上表现出智能的主体,前者不具备真正的智能(只是装得像),而后者具备真正的智能,通过他的一系列的论证(这里就简化不展开,不考虑他论证的正确性),最后得到的结论是:前者比后者更有生存优势,故而地球上大多数人,其实都是只表现出智能,却不真正具备智能的僵尸。

能看到这,说明你肯定不是一具僵尸了。

0

从零搭建一个基于Istio的服务网格

上篇文章从微服务1.0时代的三大痛点(技术门槛高,多语言支持不足和代码侵入性强)说起,由此引出服务网格的起源和演化历史。但古语有云纸上得来终觉浅,绝知此事要躬行,不亲自撸一遍命令,怎敢跟人提服务网格?本篇我将教大家如何在本地从零搭建一个基于Istio的服务网格,从而对服务网格有一个更直观的认识。

1 通关密码:上上下下左左右右ABAB

  • 原料:Mac一台,VPN账号一枚
  • 做法:依序安装和运行KubernetesMinikube,Istio

mario

2 穿墙大法:Shadowsocks

无论是Kubernetes、Minikube还是Istio,官方提供的安装文档都非常详尽,只要英文过关,依葫芦画瓢基本上都能跑通。但如果你在国内,还得加一个必要条件,学会如何突破网络审查,俗称fan墙。

Mac下的穿墙软件我首推Shadowsocks,同时支持Socks5代理和HTTP代理,最新版本可以从GitHub下载。

3 小Boss: kubectl!

3.1 安装

Kubernetes是Istio首推的运行平台,因此作为第一步,我们首先来安装kubectl,Kubernetes的命令行工具,用来控制Kubernetes集群。根据官方文档,Mac下安装kubectl只需要一行命令,brew install kubectl,这简单、极致的用户体验让你感动的想哭。But wait...

3.2 穿墙1: Brew

你敲完命令,踌躇满志的按下回车之后,可能会发现,屏幕迟迟没有输出,10秒,30秒,1分钟,3分钟,10分钟。。。恭喜你,你被墙了!

Brew默认的镜像源是GitHub,而GitHub时不时会被墙,即使不被墙访问速度有时也慢的令人发指,导致Brew命令也常常超时甚至失败。解决办法要么换源,要么给GitHub配上Socks5代理。对码农而言,我更推荐后一种,方法如下:

1) 打开~/.gitconfig文件,如果不存在则新建

2) 在文件末尾添加如下配置并保存:

[http "https://github.com"]
  proxy = socks5://127.0.0.1:1086
[https "https://github.com"]
  proxy = socks5://127.0.0.1:1086

注:socks5://127.0.0.1:1086是Shadowsocks默认开启的Socks5代理地址。

配上Socks5代理之后,一般就可以妥妥的运行Brew命令了。

3.3 验证

安装好kubectl之后,直接运行kubectl version查看版本号。完整的kubectl命令列表在这里可以找到。如果想进一步学习常见的kubectl命令,可以访问Kubernetes Playground完成在线练习。

4 中Boss: Minikube!

4.1 安装

安装完kubectl,接下来就是在本地搭建Kubernetes集群,Minikube是最简单的一种搭建方式,它通过VM模拟了一个单节点的Kubernetes集群。官方文档给出了Mac下的安装命令。

curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-amd64 && \
chmod +x minikube && \
sudo mv minikube /usr/local/bin/

Minikube默认使用的VM Driver是VirutalBox,因此启动Minikube之前,还要安装VirtualBox。

4.2 启动

安装好Minikube和VirutalBox之后,可运行如下命令第一次启动Minikube:

minikube start --docker-env HTTP_PROXY=http://<本机IP>:1087 --docker-env HTTPS_PROXY=http://<本机IP>:1087

注:官方文档给出的启动命令带有--vm-driver=xhyve,而事实上最新版本的Minikube已经废弃了xhyve driver,应去除。

4.3 穿墙2: Docker

你可能已经注意到,上面的启动命令中带了两个--docker-env参数,都指向了Shadowsocks开启的HTTP代理,为啥呢?还是因为墙。Minikube默认使用Docker作为容器运行时,并在VM中内置了一个Docker守护进程,使用的镜像源是DockerHub。如果你经常使用Docker,那你一定知道在国内使用Docker一般都要修改镜像源(比如阿里云的容器镜像服务)或者使用代理,否则拉取速度也是慢的令人发指。由于Minikube使用的是内置的Docker守护进程,使用代理更为方便,但要注意,开启Shadowsocks HTTP代理时,需要修改代理的侦听地址为本机IP,而不是默认的127.0.0.1,否则在VM中的Docker守护进程是无法访问到这个代理的。

注:--docker-env参数只有在第一次启动Minikube时需要,之后启动直接运行minikube start即可。如果需要修改代理地址,可编辑~/.minikube/machines/minikube/config.json文件。

4.4 验证

安装完Minikube之后,就可以试着创建第一个Kubernetes服务了,具体步骤参考官方文档

5 大Boss: Istio!

5.1 安装

拿到了kubectl和Minikube两大神器,搭建Istio可以说是水到渠成了。基本步骤如下,

1) 启动Minikube

minikube start \
  --extra-config=controller-manager.ClusterSigningCertFile="/var/lib/localkube/certs/ca.crt" \
  --extra-config=controller-manager.ClusterSigningKeyFile="/var/lib/localkube/certs/ca.key" \
  --extra-config=apiserver.Admission.PluginNames=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota \
  --kubernetes-version=v1.9.0

2) 下载并解压Istio安装包

curl -L https://git.io/getLatestIstio | sh -

3) 进入安装目录(假设为istio-0.7),将bin/目录添加到PATH环境变量

cd istio-0.7
export PATH=$PWD/bin:$PATH

4) 部署Istio的核心组件(包括外部流量网关Ingress, 管理Envoy实例生命周期的Pilot以及执行访问控制和使用策略的Mixer)到Kubernetes

kubectl apply -f install/kubernetes/istio.yaml

注:如果你需要启用Istio的Mutual TLS Authentication(服务身份验证)功能,可以改为运行kubectl apply -f install/kubernetes/istio-auth.yaml

至此,一个基于Istio的服务网格就算安装完成了。One more thing,还记得上篇文章提到的服务网格所独有的边车模式吗?为了将一个具体的服务接入Istio,需要为每一个服务实例创建一个配套的边车进程。根据官方文档,Istio提供手动和自动两种方式来创建边车进程,前者发生于部署阶段,而后者发生于Pod创建阶段,推荐使用后者,具体步骤参考官方文档,限于篇幅,这里就不再赘述。

5.2 验证

安装完Istio之后,可运行kubectl get pods -n istio-system查看所有Istio相关的Pods,确保这些Pods都处于Running状态。然后,你就可以开始Istio的探索之旅了,建议从官方提供的Bookinginfo示例应用起步,这里就不再展开。

NAME                                      READY     STATUS    RESTARTS   AGE
istio-ca-59f6dcb7d9-5mll5                 1/1       Running   18         42d
istio-ingress-779649ff5b-x2qmn            1/1       Running   26         42d
istio-mixer-7f4fd7dff-6l5g5               3/3       Running   54         42d
istio-pilot-5f5f76ddc8-6867m              2/2       Running   36         42d
istio-sidecar-injector-7947777478-gzcfz   1/1       Running   9          41d

6 参考

0

容器管理利器:Web Terminal 简介

一. 前言

在微服务大行其道的今天,容器恰巧又是微服务的主要载体,所以我们操作的对象也由最开始的「物理机」到「虚拟机」再到今天的「容器」。由于这些载体的变更,我们的使用方式也需要随之发生一些改变。比如一个最常用的登入操作,「虚拟机」下我们可能通过 ssh 的方式 ,但如果是容器呢?ssh 的方式就需要在每个容器中都运行一个 sshd 进程,这种做法可行但略显繁琐,也不太符合一个容器只运行一个进程的思想。
那么有没有一个即方便快捷又安全的登入方式呢?

有,通过 Web Terminal 的方式,通过 Web 的方式即可以避免对客户端的依赖又能够实现用户权限控制。目前,有很多开源的 Web Terminal 的项目,基本上都是通过 ssh 代理的方式调用并返回一个 shell 的虚拟终端(pty)。

ssh_proxy.png

二. 实现容器的 Web Terminal

2.1 架构图

docker ws.png

2.2 前端 Web Termianl 页面

Linux 终端返回的内容会带很多特殊的字符,比如我输入一个 ls 指令,终端返回的结果如下:

'l'
's'
'\r\n'
'\x1b[0;0mRUNNING_PID\x1b[0m  \x1b[1;34mbin\x1b[0m          \x1b[1;34mconf\x1b[0m         \x1b[1;34mlib\x1b[0m\r\nbash-4.3# '

这样我们就需要自己做穷举处理了,这里推荐使用一款的模拟 Terminal 的 JavaScript 库 xterm.js。这个库已经帮我们做了这些复杂操作。

<script>
  var term = new Terminal();
  term.open(document.getElementById('terminal'));
  term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ')
</script>

可以看到它已经将 \x1B[1;3;31mxterm.js\x1B[0m 这些特殊字符变成了红色:

xterm-example.png

2.3 调用 Docker Daemon API 返回 Shell 虚拟终端

在平常的命令行操作下,我们经常会使用 docker exec -i -t <container_id> /bin/sh 来模拟一个 Shell 的伪终端。在 Web Terminal 实现里,我们需要通过 API 调用的方式来实现同样的操作。当然,我们首先要确保 Docker Daemon 的远程调用是开启的。

  1. 先调用 execCreate 来创建一个 Exec。在调用时,需要指定TtyAttachStdinAttachStdoutAttachStderr 参数均为 true,Cmd 参数为 bash,这样才能获得 bash 进程的标准输入输出和错误;

    request

    POST /v1.24/containers/e90e34656806/exec HTTP/1.1
    Content-Type: application/json
    
    {
      "AttachStdin": true,
      "AttachStdout": true,
      "AttachStderr": true,
      "Cmd": ["sh"],
      "DetachKeys": "ctrl-p,ctrl-q",
      "Tty": true,
      ...
    }
    
  2. 如果调用 execCreate 成功,调用请求会返回该 Exec 的 ID,根据这个 ID 继续调用execStart 接口。在调用时,需要指定 Detach 为 False,Tty 为 True,这样才会返回一个 HTTP 的 stream:

    request

    POST /v1.24/exec/e90e34656806/start HTTP/1.1
    Content-Type: application/json
    
    {
     "Detach": false,
     "Tty": true
    }
    

    response:

    HTTP/1.1 200 OK
    Content-Type: application/vnd.docker.raw-stream
    
    
    {{ STREAM }}
    

2.4 d-terminal

d-terminal 是这个系统的核心,它分成两个部分:

  1. 一部分用于处理用户端的输入和输出,以及存储和展示后端 Docker Dameon 主机的 IP 和 container_id。因为像 top 这样的监控命令需要服务端定时推送数据给客户端,所以使用了 WebSocket 协议以支持服务端推送。
  2. 另一部分用于调用 Docker Daemon 返回虚拟终端。对于终端来说,通常是你输入一个字符就会立马返回,直到你输入一个 "归位键" 终端才会把你输入的字符拼接成一个字符串并发送给 shell 解释器,并将 shell 解释器的结果返回。为了提升使用流畅性,新启用了一个线程去调用 Docker Daemon API,当然也可以使用像 epoll 这样的多路复用技术来实现。

main.png

d-terminal 是使用 Python 实现的 Web 应用,核心代码如下:

@sockets.route('/echo')
def echo_socket(ws):
    ...
    # 调用 Docker API 返回一个虚拟终端
    docker_daemon_sock = get_tty()._sock
    # 启动一个与 Docker Daemon 通讯的子线
    docker_daemon_sock_thd = DockerDaemonSock(ws, docker_daemon_sock)
    docker_daemon_sock_thd.start()

    while not ws.closed:
        message = ws.receive() # 接收 terminal 的输入
        # 将用户的输入发送那个 docker daemon
        docker_daemon_sock.send(bytes(message, encoding='utf-8'))


# 子线程 DockerDaemonSock 类
class DockerDaemonSock(threading.Thread):
    def __init__(self, ws, docker_daemon_sock):
        super(DockerDaemonSock, self).__init__()
        self.ws = ws
        self.docker_daemon_sock = docker_daemon_sock

    def run(self):
        while not self.ws.closed:
            try:
                # 接收 docker daemon 的返回
                resp = self.docker_daemon_sock.recv(2048)
                if resp:
                    # 将 docker daemon 的返回发送给前端 terminal
                    self.ws.send(str(resp, encoding='utf-8'))
                else:
                    print("docker socket closed.")
                    self.ws.close()
            except Exception as e:
                print("docker termial socket error: {}".format(e))
                self.ws.close()

三. 总结

上述仅仅是描述了一个最基本的实现,完全是为了抛砖引玉,后续可以通过在中间层添加一些扩展,比如,用户权限的分配,与自己环境中的容器编排引擎集成等,最终作为 Pass 平台的一个基础的组成部分。

最后,上述的 demo 可去 github 具体查看。效果如下:

image

四. 参考

0