Twisted Meadows

The path twists, and the future is uncertain.

为什么神经网络对我有用

我对机器学习的兴趣完全是出于好奇。但神经网络在这方面不太吸引我,也许是因为高中生物已经学过很多。
但机器学习里的神经网络——尤其深度神经网络——对我来说是「有用」的。这里我将介绍它在哲学层面给我的一点启发。(其中一些想法未必是对的,仅作记录)

世界是基于通感的

是的,结论就是,世界是基于通感的。我先把结论放在这里。
这种说法会很confusing。因为从唯物主义的角度来说,外部世界是「客观存在」。但当我们说「世界是基于通感的」,指的是:你所认知的世界。
也就是说,在你的心里,在你的概念里,在你的思想里的这个世界。是基于通感而存在的。

让我们从头开始。
首先确保我们知道什么是感知
生物学上的每一个神经元,在机器学习里都被用一种类似的运算节点来模拟。这种模拟神经元的运算节点,被称为感知机(perceptron)。无数个感知机交织在一起,搭建出了神经网络——这是我们对生物的模拟。
生物所能获得的感知是什么?——基于各种器官与外部交互时获得的信号:眼睛获取光学信号,耳膜获取声音,皮肤上的感受器获取触觉和温度,位听神经获取平衡感……
这是我们对外界的感知。:器官收集客观的物理化学信息,将其处理成能在生物体内传输的信号。——就目前所知,这种信号类似于一种电信号,即使它们在突触之间也许是利用化学方式传播。

那意识又是什么
《失控》这本书,有一种很打动我的讨论:意识依赖于感知。
很多人以为把大脑单独拿出来,它里面仍然保存了这个人的意识(缸中之脑)。但KK认为,是你的躯体接收到的这些「感知」赋予了你意识。
1954年,一群加拿大心理学家做了个很有趣的实验。他们搭建了一间避光隔音的实验室,志愿者们呆在这个狭小房间里,头上戴着半透明的防护眼镜,手臂裹着纸板,手上戴着棉手套,耳朵里塞着耳机(里面持续播放低沉的噪音),在床上静躺2-3天。
他们起初还能听到持续的嗡嗡声,不久就融入一片死寂。只看得到暗淡的灰色,与生俱来的五色百感渐渐蒸发殆尽。各种意识挣脱身体的羁绊开始旋转。
半数的受测者都产生了幻觉,声称进入一种“醒时梦”的状态。由于志愿者们的描述太不可靠,有一位研究员以受测者的身份进行了观察:“现实感没了,体像变了,说话困难,尘封的往事历历在目,满脑子性欲,思维迟钝,梦境复杂,以及由忧虑和惊恐引起的目眩神迷”。
在这个与世隔绝的寂静棺材里呆了两天后,几乎所有被试都没有了正常的思维。注意力土崩瓦解,取而代之的是虚幻丛生的白日梦。

这个实验如何解释?我不是在写一本教科书,所以无法给出详细的论证。
个人看法:人的神经网络是高度复杂交错构建的。其中不可避免的存在大量的循环。如果没有外部的强刺激来触发某些响应机制,这个网络就会自我运转至一种疯狂状态——所有存在正反馈的环都会无限循环地放大其中信号。你脑海深处产生的一点点小想法,都有可能被强化成一场巨大的无法停止的风暴。

身体是意识乃至生命停泊的港湾,是阻止意识被自酿的风暴吞噬的机器。神经线路天生就有玩火自焚的倾向。如果放任不管,不让它直接连接“外部世界”,聪明的网络就会把自己的构想当作现实。
……
而身体,或者说任何由感觉和催化剂汇集起来的实体,通过加载需要立即处理的紧急事务,打断了神智的胡思乱想!

这个观点对我的启发非常强。Everything you feel is shaping you。你以为是「大脑」在思考,但其实你的所有器官也都在参与「思考」。它们采集的信息不只是被「采集」而已,这些信息是意识运转的基础,它们进入感受器时就已经组成了你的一部分意识。
——你之所以知道自己能控制自己的手指,是因为你驱动它时能感受到相应的触觉、你的眼睛能捕捉到它运动。——若没有这些信息反馈,这一套行动就无法形成闭环——你仅靠想象来决定自己的动作,你的想象就会最终演变为风暴而丧失其应有的意义,因为你的逻辑系统是孤立地运转的。

深度神经网络继续推进我对这个话题的认知,它让我几乎可以从哲学层面来看待人类的「运行」。
『殊途同归』。当初Geoffrey Hinton坚持认为神经网络是才是机器学习的正确方向。他坚持要把自己那篇论文发在Nature上,但在那个年代,所有人都觉得神经网络这个主意已经没救了。他想到其中一个审稿人是Stuart Sutherland,一位很有名的心理学家,就跑去给他解释机器学习里的神经网络以及反向传播算法。这位心理学家 was shocked,因为他发现这群计算机科学家用不同的方法验证了与他们对脑神经科学的研究相似的结论(关于概念如何传播和组织、它们在大脑内的图形结构…)。

这个故事想指出的是,计算机科学家往往从工程的角度来思考如何实现对一些东西的计算,但它们的努力有时同样反映出生物进化的方向。当他们的深度神经网络模型越来越完善的时候,这种模拟,很有可能就是在越来越逼近生物学上的「现实」。

好了,该来具体谈谈机器学习里的神经网络了。如果学习它,你会了解到些什么?

我不列举所有知识。只举对我来说重要的。

至少目前,卷积神经网络在图像识别领域已经获得超出人类的正确率。这证明图像识别应用中的一些技术是「先进」的。
来看:计算机科学家已经在探究深度神经网络工作的「原理」,在图像识别上,输入的图像传给网络第一层时,第一层的所有感知机都只处理简单特征:某些区域的直线、斜线、纯色…
第二层开始,感知机接受前一层传来的处理结果并将它们抽象和组合,这时候它们开始能理解类似:圆形、倒角、长直线……
第三层也许能将信息组织得更抽象,你能看到一些感知机似乎在找鼻子,另一些在找眼睛……(如果你是在训练一个人脸识别的网络)
再往后,后面的层级就会越来越抽象,处理的信息达到人类无法理解的维度。

神经网络的浅层如我们预期一样在处理输入信号,将其翻译为一些「有意义」的特征;但随着深度的增加,越靠后的网络层越倾向于把信息往更高维度、更综合的方向抽象,最终形成一个「结果」向外部输出。

它当然跟人脑不像,因为人脑不是这样线性的「输入-输出」模型。但我愿意相信的是:在人脑最重要的核心区域,神经元全部基于高度抽象的“概念”进行运算,你收到的信息,无差别地传入网络(它们也许被预处理过,如视神经已经把光学信号进行了转译),被一套通用的模型处理,输出一些相对稳定的结果(变成你的想法和行动)。

大脑的不同区域看起来是分工的。但它们的功能如此「通用」,以至于你给它输入别的信号它也能慢慢学会处理。为了验证所有大脑神经都是用的同一个「算法」,有人把小鼠的听觉输入切断,然后给负责听觉的大脑皮层接入视觉感官,让动物的auditory cortex学会了看东西……

然后他们给盲人做了个头戴式摄像头,采集低清图像转化成灰度图像,将每个像素点的灰度值转换成不同电压传到盲人舌头上。盲人就靠舌头获得了视觉……

这意味着,讯号是可以跨界产生影响的。就像「辣」其实是一种痛觉,但你吃东西的时候会把它判断为这道菜的「味道」。人体是如此复杂的存在,它既是由大脑统控的集中式管理方案,又是器官各自运转的分布式处理系统。你所看到的东西在被视神经收集到的瞬间,视神经已经开始对它做处理。信息层层传递,传到大脑皮层的已经是一些足够抽象的概念——它们甚至已经不必与「视觉」有关。
一束信号如果在传递中串了线,就有可能从「视觉频道」跳到「听觉频道」,当这个概念错误地被听觉大脑皮层处理时,严重时就会让你产生幻听,不严重时…也许你脑内就莫名地产生了一丝忧伤、或者一丝欢愉。

讯号是可以跨界产生影响的,即所谓的“通感”。如果我们认为大脑内有一个核心区域是所有意识交汇的「总成」。就能解释文艺作品中,以下技术有助于推动情绪的原因:环境描写、画面色调、背景音乐……
它们在某种程度上给你传递了“通用”的感受,因为这些不同形式的信号在输入网络的时候都“触动”了一些共同的关键节点。于是你可以从某些颜色、某些旋律、某些文字中感受到近似的悲伤。

刚才提到Geoffrey Hinton和他的反向传播算法,「反向传播算法」指的是,神经网络在进行一次运算之后,会根据输出的结果正确与否接收外界的「惩罚」,这个惩罚会被反向传播回整个路径,使得每个感知机自我修正(下次我就不会再这样了!)。

神经网络是在不断“学习”中变化的。当你被某段感情伤害时,状况就类似于机器学习模型算出了错误的答案,身体的某种机制会去“惩罚”这个错误答案。从而,让整个网络里的每个神经元自我调整。它们产生一种“抑制”,会去回溯在这段感情里你的所有行为和情绪,控制情绪的、控制运动的每个神经元都会根据权重抑制自身。这个“学习”过程让神经网络产生变化:即使跟同一个人发生一段全新的恋情,你的感受和行为也会不一样了,因为你身体的每一个神经元都在学着活得更“正确”。

世界是基于通感的

回到这个结论上来,我认为,从出生起(甚至出生前起),人的感知就在塑造自己(的意识)。气候冷暖在塑造你,你看到的事物在塑造你,你受到的夸奖和委屈也在塑造你。
当你在谈论审美时,你真正指的是什么?
(如果你认同「Everything you feel is shaping you」)也许「审美」只是你从小到大所有感知的加权总和——所有这些感知中让你感到「舒服」的部分,构成了你的审美。

人的大脑是在高度抽象的层面被「训练」的。它不仅仅是学会了一件「具体」的事,而且会跨界产生影响。
就像你学习知识时会用「类比」这种方法。如果所有抽象概念都是在核心区域被处理,那么它学到的经验就可以在毫不相干(却又隐含相同抽象规律)的领域间迁移。即使那些抽象规律并不真正适用于所有领域,若假设人脑的运算能力总是不足的,It’s underfitting,它会用并不普适的经验去处理未知事件就一点也不奇怪了。——就像你生命里的每一个「第一次」尝试。(而且你会在错误尝试后得到「惩罚」,这促使了一次「反向传播」,从而「训练」了你的神经网络,从这个方面来说,你就「学习」了。)

也许你从小生活在局促的空间里,家教很严、做事处世处处受制,长大后想问题的格局也很容易「小器」。

也许你谈恋爱从未失败过,只要努力,想追的女孩就总是能追到手,后来你成了一支基金的经理,潜意识里觉得这种「投资->回报」模型跟追女孩很像,你的投资风格就会异常奔放自信,自以为每次操作都能带来预期中的高收益。


神经网络对我是有用的。我借助它来认识自己。

如果你把人体比做一套高度复杂的神经网络系统,我的建议是:试着了解自己的“网络特性”。——这似乎是与基因有关、取决于成长环境和历程的巨大模型。
然后基于特性去做能够改善网络自身的选择和决定。

以我自己为例:
我认为我的大脑是学习率较高、对错误的punishment超强的类型,同时我会倾向于过早泛化。 这意味着我对未知情况的试探会比较快速和大胆,但一旦得到坏的结果,就会获得很强的负反馈,使之前的所有相关行为都被严重“否定”,这使我很不愿意去做完全没接触过的事情。同时我有很强的倾向去给事物做“抽象”,寻求它们的内在一致性,以尽量总结出可以覆盖更多实例的简单经验。

那我会如何修正自己的选择和决定呢?
——我仍然会鼓励自己去尝试未知,而不是靠既有经验去推断。
——在一些重要的体验发生时,我不会去强化它(让刺激的更刺激),我会去做一些差异化,让正在发生的这件事带上一些不同的特征。这样我在下次遇到类似情况时,就能够因为「差异」而跳出之前的经验,不至于落入一种错误的响应中而丢失宝贵体验。
——不过如果我想活得感性一点的话,在某次重要事件发生时,我会究极强化我的体验,超速飙车、做爱、吸毒一条龙,全方位分泌所有激素去促进神经网络「深刻记住」这一刻。

对 ML 中 Normal Equation 的理解

机器学习中的回归问题,类似于以往各种工程上的优化问题。定义一个 Cost Function:

(1)   \begin{equation*}  J(\theta) = \frac{1}{2m}\sum_{i=1}^m(h_\theta(x^i)-y^i)^2 \end{equation*}

对这个函数求解最优化问题。

由于J是一个关于\theta的函数,所以目标是求一组最优的\theta,来使J取得最小值。

一个符合直觉的做法是Gradient Descent。也就是迭代地用 J\theta 求导,顺着梯度最大的方向移动,直到收敛于最低点。

另一个方法则是Normal Equation,对我来说这是一个非常强的公式,可以把Gradient Descent花了那么多次迭代才解决的事用一次运算就搞定:

(2)   \begin{equation*}  best \theta = (\mathbf{X}^\top\mathbf{X})^{-1}\mathbf{X}^\top \overrightarrow{y} \end{equation*}

这里面的\mathbf{X}是一个矩阵,\overrightarrow{y}是列向量,\theta其实也是列向量。
\mathbf{X}的每一行是一个样本,第i行其实就是 x_i^1, x_i^2, x_i^3, \dots 这样的一组特征(feature),其中的每个值都可以视作特征空间里的一个维度。
对于第i个样本,它对应的(标准)结果就是 y_i,总共m个样本结果组成了向量\overrightarrow{y}
(回顾一下Cost Function:损失函数其实是定义了我们的Hypothesis h_\theta(\mathbf{X})与真实结果\overrightarrow{y}之间的距离。以此衡量训练结果。)
再补充Hypothesis:

(3)   \begin{equation*}  h_\theta(x) = \theta^\top \overrightarrow{x} = \theta_1 x_1 + \theta_2 x_2 + \dots + \theta_n x_n \end{equation*}

Normal Equation 的思路类似于中学数学里求极值:对某个方程求导,然后让式子等于 0,等于 0 的点即为极值点。
但为什么上述思路能被(2)这样一个式子实现呢。


这个问题是我去年初遇到的。当时就想把阳哥哥给我的解答记录下来。拖了这么久,终于给Wordpress装了LaTeX插件,所以来记录一下:

(2)的推导方法有两种,第一种是 cost function J (1)对 \theta 这个向量的每一个分量求偏导

    \[ \begin{cases} \frac{\partial J}{\partial \theta_1} = \frac{1}{m} (h_\theta(x^1)-y^1) x_1\\ \frac{\partial J}{\partial \theta_2} = \frac{1}{m} (h_\theta(x^2)-y^2) x_2\\ \dots \\ \frac{\partial J}{\partial \theta_n} = \frac{1}{m} (h_\theta(x^n)-y^n) x_n \end{cases} \]

假设 \theta 是个n维向量,就得到了n个式子,令这n个式子都等于0,我们就得到了一个线性方程组:

    \[ \begin{cases} \frac{1}{m} (h_\theta(x^1)-y^1) x_1 = 0\\ \frac{1}{m} (h_\theta(x^2)-y^2) x_2 = 0\\ \dots \\ \frac{1}{m} (h_\theta(x^n)-y^n) x_n = 0 \end{cases} \]

这个线性方程组可以写成矩阵相乘的形式,即

(4)   \begin{equation*}  \mathbf{X}^\top\mathbf{X}\theta = \mathbf{X}^\top \overrightarrow{y} \end{equation*}

(4)这个式子就是所谓的Normal Equation。它跟式(2)是等价的。


第二种方法要稍微高级一点儿,写出来会简洁得多。就是直接把cost function写成矩阵的形式,即

    \[ J = (\overrightarrow{y} - \mathbf{X}\theta)^\top (\overrightarrow{y} - \mathbf{X}\theta) \]

然后直接以这个式子对向量 \theta 求导,也会直接得出normal equation。
至于如何直接对向量求导,你可以参考wikipedia的matrix calculus。其实本质上对向量求导就是一种记号,和对向量的每个分量求导没的本质区别。


再回头看一次这个好用的Normal Equation:

    \[ best \theta = (\mathbf{X}^\top\mathbf{X})^{-1}\mathbf{X}^\top \overrightarrow{y} \]

对于有 n 个特征维度的 \mathbf{X} 来说,用 Normal Equation 求 \theta 的复杂度是 O(n^3),而 Gradient Descent 的复杂度为 O(kn^2)
所以,当特征维度不多时,都可以用 Normal Equation 快速求解。当 features n > 10000 时,才开始考虑使用 Gradient Descent 。

但是,这个公式显然也不是能够无条件使用的。你可以看到其中有 (\mathbf{X}^\top\mathbf{X})^{-1} ,意味着括号内的矩阵必须是可逆的。
真的是这样吗?——老师说,此处的矩阵并不必须是可逆的。在代码中,对这个矩阵的求逆直接用pinv()函数来做(也就是求伪逆、广义逆)。阳哥哥说,使用广义逆来算 \theta(\mathbf{X}^\top\mathbf{X}) 不可逆时的一种传统的处理方式,可以用的原因是广义逆得出的 \theta 符合一些良好的统计性质。(具体参考:高惠璇的《统计计算》)
不过, (\mathbf{X}^\top\mathbf{X}) 不可逆往往意味着数据模型有问题(Features数量比样本数量还大,或是非常强的collinearity),遇到不可逆的情况还是返回去检查模型比较好。

Python 可视化图形界面简单实践

最近工作上常用的一个操作需要进行二进制数bit翻转。由于不知道windows系统计算器就有这个功能,自己用python写了一个。简单记录下实践过程。

Python进行GUI编程,可用的库并不多。功能也不算强大,这篇文章简单列举了几个库的特点。
我呢,没有被它们任何一个吸引。所以直截了当地选择了Python标准库自带的Tkinter
( Python Tkinter 官方文档:https://docs.python.org/2/library/tkinter.html

完成品大致是这样的:

基本框架是:

from Tkinter import *
top = Tk()
top.title("Listen's Option Checker")
# Your code here
top.mainloop()

在这个基础的窗口里,用Frame控件分割空间(类似于html和CSS那种方式)实现布局。
然后在各个分区的frame里,根据与用户的交互方式,选择对应的控件:Button、Entry、Label……

控件由类似这样的句子生成:

Confirm_Button = Button(frm2Sub1,text="确认",command=hex2bin)
Confirm_Button.pack(side=LEFT)

第一句创建了一个Button实例,这个按钮的父容器是frm2Sub1,按钮上文字为「确认」,当用户按下这个按钮时,就会立即触发一个名为hex2bin的函数,来对用户的操作进行响应。
现在命名空间内的Confirm_Button指向了这个Button实例。用.pack()方法来完成对它的部署。参数side=LEFT是要让它在所属Frame容器内靠左放置,若不指定位置,每个新的控件都会被部署在前一个控件下方。

当然,包括Frame在内的所有控件都需要通过.pack()来呈现。如果漏了这一句,程序界面内就看不到对应控件。

去查一下Tkinter支持控件列表,再配合以上基本信息,已经足以制作一些功能极其简单的可视化程序界面。其实网络上大多数介绍文章也没有讲的更多。
但还有一些值得注意的地方。

Command传参

Button控件的command参数,是不允许传参的
我理解此处的command参数就类似于一个函数指针,指向目标函数。
网上比较常见的传参方法是用lambda函数

Button(frm, textvariable=strvar31, command = lambda : FlipBit(31,strvar31))

它其实是定义了一个匿名函数,在Button被按下时执行的是lambda,再由lambda将参数交给你的执行函数。类似这样的用法,在只有一两个同类控件的时候是可行的。也很方便。

但对我这个程序来说,需要32个按钮充当32位二进制bit,不可能手动复制32个button的创建过程。
这里的Lambda函数,如果用在for循环创建的button内,就会导致它传进去的参数固定为for循环的控制变量i(的最大值,也就是最后一次循环的值)。
原因也很简单,lambda函数在未被执行时并不会被解释(编译成固定的函数)。它只有被真正执行时才生效。而真正执行时是Button按下时,不是For循环当初loop经过它时。所以i始终为定值(终值)。

最终的办法是用一个Event Handler。定义如下两个function:

def handler(bit):
num[bit] = num[bit] ^ 1
strvarlist[bit].set(str(num[bit]))
binary = "".join(map(str,num))
decimal = int(binary,2)
var16.set(hex(decimal))

def handlerAdaptor(fun,v):
return lambda event, fun=fun, v=v: fun(v)

由handlerAdaptor作中介,将参数传进去。方法是将handler与鼠标点击event绑定。
绑定鼠标事件的Button写法如下:

ButtonList.append(Button(SubSubFrameList[i], textvariable=strvarlist[i]))
ButtonList[i].bind('', handlerAdaptor(handler, i))
ButtonList[i].pack()

这里跟前面创建控件的方式不同,有一个小变化:
为了使我们这32个bit的控件(包括button和其上的textvariable)可以被方便地遍历,我不再是给它们赋予一个固定的命名,而是将每个实例直接.append()到它们各自的list里
这样我在另外的函数里处理它们时,可以直接用下标来控制和选择。——包括这里创建完成后的.bind()和.pack(),也都是用list加下标来做选择。


一些参考资料:
tkinter官方文档学习笔记
Tkinter 控件简单计算器示例
Python Tkinter参考资料之(通用控件属性)
tkinter模块常用参数(python3)
Python Tkinter GUI(三)显示图片
Python GUI进阶(ttk)—让界面变得更美


打包exe程序


上表来自《Python程序打包成exe可执行文件》,文章很有用。我此前用过的py2exe不好用,这次选择pyinstaller,简单打包方法:

pyinstaller -F -w main.py

生成文件在dist目录。build仅为中间文件。

-F 是打包成一个单独的exe文件,不加则会生成一整个目录的文件。(加了该参数会导致程序调用外部image失败,在spec配置内添加DATA也没用)

-w 不需要查看命令行时,用这个参数隐藏cmd画面。

亲测-F之后程序启动速度慢了几十倍不止。所以我不建议-F

更复杂的配置,需要使用脚本相同目录下,由pyinstaller生成的一个.spec文件。我没有深入研究,需要时可以在网上查。(参考:pyinstaller打包工具的使用说明将自己的python程序打包成.exe/.app)

毕竟青山遮不住

你好。

很少写东西给人看了。因为这事让我自觉像只受伤的猴子。
我的诚实也很有限度。所以你不妨把这些主动交代当作我对世界的馈赠。

互联网上的我的博客这么一个地方,我把它理解为一座山间小屋。大多数时候主人不在,云深不知处去了。也就很少有人来。
这样的一篇文章还能被你看到,不得不郑重地说一声你好。

熟悉我的人应当察觉到我的性情有些变化。——但是真的有人熟悉我吗?这事也很值得怀疑。
前段时间一些事,使得看上去一直很坦诚的我显然也藏着不少的小秘密。
不过无妨啦。用台湾话说叫「这样也没差啊」。(我现在挺喜欢学台湾朋友说话。以至于他们觉得「四川人说国语比较好听诶」……)

性情变化的诱因是我大学毕业了。我大概从某种逃避里走了出来。重见了好几个之前不想面对的问题。
结论就是这世界前行时背负的沉痛远超出你想象
这句忠告给年轻的朋友们。可能我以前不太懂这一点。我现在懂了。挺懂的。我是这方面的大师。
或者我一直就很有这方面的天赋。我有出家和苦修的基因,不需要遭遇什么灾难和困厄就愿意身负枷锁循环推石头上山。可惜那些宗教都太烂了,我宁愿老死也不想与他们为伍。

用一种流行的说法来讲,我现在就是一名「佛系人类」。
世界应该感谢我。我放下一切。
虽然我也曾讲述孤独。——这在现在的我看来是一种撒娇,是在告诉世界「你们都该来爱我」。

但是总的来说,我称得上是「基本无害」。绝不会有那种因压抑而爆发以至于发疯了跳起来咬人一口的冲动行为。
我现在很乖巧。——这句话的意思是说,我甚至已经了解自己的毒性所在。我在体内化解自己的毒。——消解自己,也消解存在的意义。

当然,这会带来一些问题。所以我现在是个新的虚无主义废物。我把所有事情都当个卵。
我在公司里有个很好的师傅,他可能觉得我脑子不太好使,每次接收命令都要花个一分半钟反应。但我也不只是傻,其实是我有点懒。我觉得所做的这一切毫无意义。那些迟钝感都是我在心里劝说自己。
这样说可能会引起一些朋友的担心。在此澄清一下。即使这样,我也是能在年终绩效上给自己打二等而值得老板把我改为一等的存在。
……
全靠同行衬托的好。

我也不是个自甘堕落的人。我还是挺想把自己塑造为一个积极向上的形象。不想到老了来装逼,人家可以感慨「半生飘零」。我却全都是在「原地待命」。
可是我现在在哲学上非常…非常的虚无。以前还想当台下为英雄鼓掌的观众,而现在我觉得英雄他在另一个维度下可能也很孤独无助。

你可以看到,上文里用了很多个「我」字,而且已经是删减过很多的结果。「以自我为中心」,这是目前我狭隘的哲学走入死胡同的一个大的趋势。

等等,好像装逼装得有点过了。本文主旨是要向各位朋友们汇报工作的,😄。

大多数原地待命的人,等了大半辈子,命也不会来。
我按部就班,在某些路口顺从滑向势能最低点,贪心取简单的局部最优。因此也只受较少的挫折。

毕业之后有了更具体的任务和压力,就少了很多自我怀疑,因为我清楚看到自己的价值——您做的每一件事可都是在替公司挣钱呀

年中,在我刚毕业的时候——那时我的性情还一如既往——写过一些没有发出来的东西:

愿诸位都去谋求自己最大的「势」。去破解生命中最复杂的温柔。
积攒心中勇气,而不是被愤怒、不满或者仇恨驱动着做选择。
去承受最深重的伤痛。
乘着年轻,埋头冲进黑暗困厄中去。

且不管当时发生了什么。这样的文字在什么时候拿出来都不丢人。因为我得意于自己和各位好朋友的才华。
即使今日的我不再相信朋友这个概念。我也依然承认这批人的才华。我认为世界不止于此,人类,也不止于此。因为我们不止于此。

另一项需要澄清的状况是,虽然我很懒地滑向了局部最优点,但不等于我被困进某种局面。我随时也可以跳出这个小圈去做厉害的事,照样龙战二三十载,少建功业,说一句际会如期。
而目前的境遇所让我满意的是:周围有些人很厉害。只要这个公司里还有比我聪明的人,我就没有理由说自己努力,也没有理由看不起别人。

我写东西的时候老喜欢自我塑造为一种寡淡形象。现实中我比较简单开心,下班沉迷游戏,偶尔跟同事打球,输赢赌一瓶雪碧。
以前关注男欢女爱,因为它们似乎与存在有关。可惜存在先于一切,爱情不在了我也还在。这就显得爱情很不负责任。
我的困厄在于器量太小。最近工作上常用一个词是trade-off。聪明人都懂得trade-off,而我在有些命题上拒绝承认trade-off。

时光像显影剂一样洗刷着那些远去的人事,浮现出真假掺半又让人忍不住原谅的小小心思。
可我还是对那种留不住的难以名状无法割舍。我不算个愚蠢的人,很多道理不用脚指头都能想清。但问题也正在这里。我的脚指头告诉我那些东西是可以留住的。然后我的大脑知道这太难了,就拒绝听从脚指头的指挥。
因此我仍在原地待命,无力地看世界在我面前来去,时而感觉脚趾发麻,内心抽搐。
只能假装清醒,在枯朽间写下一笔难受。

Mac操作系统上的ss客户端:ShadowsocksX-NG

一直以来我使用的ss客户端都是ShadowsocksX,设置比较简单,功能也很稳定。没有想过要换。
但是GFW砌墙技术日新月异,ss的开发者们想必也没有闲着,ShadowsocksX却很久很久都没有更新过了,这让我很没有安全感。

后来在GitHub上一找,才发现原作者@clowwindy停止更新ShadowsocksX之后,其他开发者用Swift重写了一版ShadowsocksX-NG,之后都在NG版本进行更新。
但是我下载X-NG之后,经过简单的配置,却无法像X那样开始使用。于是开始(瞎jb)排查问题。


以下这些操作我在单独执行每一步的时候都没有作用。直到我把它们全都做过一遍之后,我的X-NG才开始正常工作。
所以把它们记录下来,希望有人能用得上。


首先需要知道,这版X-NG会把自己的log文件:ss-local.log
保存在路径:~/Library/Logs
所以这个log文件的地址为:~/Library/Logs/ss-local.log

通过log文件的error记录,可以看到一些错误提示

例如上图中,我的error是:address已被占用。

查看了一下端口监听状况,问题在于,我在偏好设置中,把本地的「Socks5监听端口」和「HTTP代理监听端口」设置成了同一端口。导致http代理先启动而占用了端口,Socks代理就启动失败了。

⬆️随便把它们改成不同的值即可解决问题。

 

另外一种常见问题是,ss-local服务启动失败
(log里的记录为:ShadowsocksX-NG Start ss-local failed.
这可能是ss-local没有执行权限导致的。

GitHub上有一种说法是删除软件重新安装可以解决。但是……???重装这种操作真的是程序员解决问题的思路吗…………………………

我参考了另外的做法,
cd /Applications/ShadowsocksX-NG.app/Contents/Resources

ss-local赋予执行权限:
chmod +x ss-local
然后,重启电脑。
(我还发现另外有个地方也有ss-local文件:/Users/godlike/Library/Application Support/ShadowsocksX-NG/ss-local-3.0.5   不知道影不影响)

 

我还看到有人的做法是把这个文件的读写权限赋给所有用户⬇️,我也照做了。虽然我感觉这项并不影响它的运行。

如果你曾经安装过另外的ss客户端,前往文件夹:
~/Library/Application Support/
删除其他的SS文件夹 ,只留一个
因为可能是ss-local冲突(虽然我觉得这种说法很没有道理)

 

最后,可以用这个命令来检查一下端口监听状况:
lsof -iTCP -sTCP:LISTEN -n -P
我这里,正常运行的ShadowsocksX-NG会有图示的4个进程:

其中,privoxy监听了我设置的HTTP代理端口,ss-local监听了我设置的Socks5代理端口。而两个Shadowsoc如果没跑起来,你就翻不了墙。

下面的命令可以用来验证Socks代理是否成功转发你的流量
curl --socks5 127.0.0.1:1086 http://cip.cc


我的服务器在日本,所以可以看到这个网站返回的我的IP是东京的地址。
当然,其实你用浏览器开个google就知道代理是否运行成功了。


PS. 如果你像我一样,之前对别的软件做过代理设置(例如Dropbox的「网络」-「代理服务器」,我之前设置了本机的Socks5代理)
你需要根据新的Socks5监听端口对它们进行修改。
因为老版本的ShadowsocksX默认监听在1080端口,而ShadowsocksX-NG则默认监听1086端口。


附赠:《科学上网漫游指南》
我觉得上面的信息挺有用的

深入理解GFW:内部结构


[2020.5.11 Update] 目前 Shadowsocks 已经不稳定,不建议继续使用 ss 做科学上网工具了。我个人已经切换到 V2Ray 。继续向我询问 ss 相关问题我也不见得能解决。

用python控制鼠标键盘帮我做测试

写了一个脚本实现自动化测试过程。

最近公司在做一些测试,属于简单重复劳动,一套做下来费神费力,又学不到什么东西,几乎没有收获。
因此写了一个python脚本自动去控制整个流程。


测试的流程是在一个Terminal软件里执行一系列命令(有一定规律性),然后对输出的结果做文字处理(提取关键信息,做数据统计、分析)。
文字处理用python好做,但是数据处理过程中其实是用一个前辈写的脚本软件实现的,所以有一丢丢麻烦的地方。

最后我写了三个程序来组成这个自动化脚本。

主程序会使用PyAutoGUI库来实现对鼠标和键盘的控制。
A子程序是一个命令生成器,在它里面定义一些规律性的东西,然后生成出一个cmd_list传回主程序。调试的时候直接运行子程序就能看到是否生成了预期的系列指令。
B子程序是中间信息处理。将主程序写在input文件里的内容提取出目标数据,存入另一个文档,用os库调用前辈的.exe档来执行中间处理。

具体来说,我的PyAutoGUI在程序开始运行时会要求用户进行两次移动鼠标的操作(坐标采集)。
采集到的坐标分别对应了Terminal软件和txt文档的编辑器。
正式开始运行之后,程序调用A的生成器获取命令列表,移动鼠标到Terminal去,选中这个Terminal,然后用键盘对它输入指令(整个命令列表)。
这里存在一个判断,如果当前输入的这条指令是一个很耗时的操作,那就time.sleep(5)这样等待一段时间。否则Terminal那边执行到一半,键盘就会开始下指令。相应地,输出的log信息也就会被打乱。

完成一套测试指令之后,PyAutoGUI会执行CTRL+C,去txt编辑器那边CTRL+V,然后保存这个log文件,交给B去做数据处理。

B完成处理的分析结果我是直接print到了console里。这样我对每轮的输出可以进行检查,然后手动写入excel文档里。
不过应该用csv库直接导出到csv更好。给代码增加一点健壮性,遇到log异常的时候直接重新进行测试就好了。


用到的第三方库:

  • os
  • time
  • pyautogui

经验性的总结:

PyAutoGUI很好用,它还有官方中文说明文档:Doc PyAutoGUI

应该将程序模块化实现。对每个子程序都可以用

if __name__ == "__main__":

单独调试,很赞。

如果程序的功能相对固定,不需要经常修改的话,可以封装成exe,这样也方便同事使用(比如前辈给我的那个exe档)。例如可以参考:如何将python程序封装成exe可执行文件

后续如果要加csv输出的功能,参考:
13.1. csv — CSV File Reading and Writing
总结python对csv文件的操作

目前还存在另一个问题就是PyAutoGUI库还没支持多屏。双屏时它对相同坐标位于哪个屏幕可能会有些困扰。所以我跑脚本时都得把外接显示器拔掉。
这个问题只能等新版本的PyAutoGUI解决了。

导出Ello上的个人数据

Ello是我常用的一个社交自言自语平台。
我不想打扰朋友圈的时候就发在ello里。它的优势是文字内容不限长度,支持基础的排版(加粗、链接、tag、划去),允许发布后再次编辑,还支持图文混排。
以前它还有个优势是发图是不会被压缩,但现在也要压图了,而且没有针对长图优化(超长图片大概是起源于微博的中国特色?),所以有点烦。

ello另一个「卖点」是尊重个人信息的所有权。有比较丰富的隐私设置,号称绝不把用户数据出卖给广告商,还能方便地「带走」或者删除自己在这个平台上的数据。

我已经在ello上面产生了太多的内容(612 Posts),开始对这些信息的管理有所担忧。
所以今天试了下「带走」数据。


点击右上角头像,进入settings。往下翻,翻到Your Data标签,点开之后有Export Data选项,点申请。
然后ello就会把你的数据打包,下载链接发送到你的邮箱,24小时有效。

我跑到邮箱里一看,蒙蔽了,是个.json文件。
打开之后的体验如下:

好在我会python,对吧。

然后我就写了一个小的辅助程序。
它把json文件读入python,然后略去不重要的信息,只把post的内容和发布时间提取出来:

这对我来说就已经够用了。提取结果以文本形式输出到一个txt文件里方便以后查看。

图片以网址的方式给出。如果需要存图的话,随便写个小爬虫就能存下来了。

保存的post是按照最后编辑时间逆序(因为ello给的json里就是按这个顺序)。编辑顺序往往跟发布顺序不同。
发布时间已经从json里提取出来了,如有需要可以按发布时间再做一次排序。对我来说影响不大,我就没有加。

代码已放到GitHub:https://github.com/MamaShip/ElloExporter

telegram robot开发(python)[施工中,暂时搁置]

Telegram 为robot提供的 API

官方页面在这里:Telegram Bot API
而我使用的是一个叫 python-telegram-bot 的第三方库:https://github.com/python-telegram-bot/python-telegram-bot

开始用之前,你需要在telegram上找到@BotFather,根据它的指示新建一个机器人。完成之后你会得到一条这样的消息:

被我码掉的部分才是你需要的东西,也就是这个机器人的token。

这个第三方库通过 Updater 来基于token监听机器人的变化(接收到的信息)

from telegram.ext import Updater
updater = Updater(token='TOKEN')

然后用 Dispatcher 对它做出响应

dispatcher = updater.dispatcher

具体的做法是,建立一个handler,对特定的命令执行相应的函数:

from telegram.ext import CommandHandler
start_handler = CommandHandler('start', start)
dispatcher.add_handler(start_handler)

上面的例子里,事先已经写好了一个start函数,(telegram里面大多数机器人都有 /start 命令)
当用户向机器人发送 /start 的时候,dispatcher 对’start’句柄调用start函数,传入 def start(bot, update) 的两个参数分别是 telegram.bot.Bot类telegram.update.Update类
我在函数内部执行以下代码:

print bot
print type(bot)
print update
print type(update)

输出是:

关于它们的属性和相关方法,可以参考官方文档。


python 操作 mysql

主要用到库 MySQLdb
大致是这么个操作方式:

# 打开数据库连接
db = MySQLdb.connect("localhost","testuser","test123","TESTDB" )

# 使用cursor()方法获取操作游标
cursor = db.cursor()

# 使用execute方法执行SQL语句
cursor.execute("SELECT VERSION()")

# 使用 fetchone() 方法获取一条数据库。
data = cursor.fetchone()

# 关闭数据库连接
db.close()

具体可以参考这个:python操作mysql数据库

有一点需要注意:执行INSERT这类修改数据库的操作之后,需要用 db.commit() 这句来提交事务,否则 mysql 那边不会真正的插入数据。(关于 autocommit 的探讨参见:Python 的 MySQLdb 模块插入数据失败与 autocommit(自动提交)的关系


为robot创建一个mysql数据库

mysql很简单,具体过程不表。
创建之后,由于我们robot主要还是要说中文的,所以把数据库的字符集改成utf8:

show variables like 'character%';
可以看到目前使用的是哪些字符集。

退出 mysql,去

vim /etc/mysql/my.cnf

在各对应字段加入默认字符集设置:

[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
character-set-server=utf8

重启服务之后,再进入 mysql,你会看到还是有一点小问题:

mysql> show variables like ‘character%’;
+————————–+—————————-+
| Variable_name | Value |
+————————–+—————————-+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | latin1 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+————————–+—————————-+
8 rows in set (0.01 sec)

我试了试执行:

ALTER DATABASE `Robot` DEFAULT CHARACTER SET utf8;

可行。


python的中文支持问题

python有很多方面都需要修改,才能正常处理中文字符串。在文档前加

# -*- coding: utf-8 -*-
这种常识就不说了,除此之外:

import sys
reload(sys)
sys.setdefaultencoding('utf-8')

可以参考这篇文章:python2.7 查询mysql中文乱码问题


使python在后台运行

这篇文章提供了多种方案:python脚本后台运行
我为了调试方便,使用的是tmux:

1、启动tmux
在终端输入tmux即可启动
2、在tmux中启动程序
直接执行如下命令即可(脚本参考上面的): python test123.py
3、直接关闭ssh终端(比如putty上的关闭按钮)
4、重新ssh上去之后,执行如下命令:
tmux attach
现在可以看到python程序还在正常执行。


来测试我的机器人

Telegram上搜索 @Twisted_bot 来与他对话。
相关代码开源在:https://github.com/MamaShip/TwistedBot

Ubuntu上部署WordPress的流程

简单记一下部署流程,好久没弄了,有点搞忘。

Hello world!

又冒出来一个博客。

这次是个技术博客。大概会活得久一点。

(会把以前的东西搬一点过来。另外界面这些都有待调整。

Page 3 of 4

Powered by WordPress & Theme by Anders Norén