人工神经网络究竟是什么鬼?

难解释的问题,就举个简单的例子说明。PS:稣才入门,也不懂不简单的例子……

题目

有一个未知的函数 f(x1, x2),其中 x1、x2 取值和结果符合下表:

x1 x2 f(x1, x2)
0 0 0
0 1 1
1 0 1
1 1 0

求 f(x1, x2) 的表达式。

求解

人脑抢答

知道异或运算的人可以马上抢答:f(x1, x2) = x1 ^ x2,其中 ^ 是 C 语言里表示 XOR 的运算符。

很明显,这答案是准确无误的,人脑的速度还可以……

放开那个函数,让 AI 来!

人工神经网络(Artificial Neural Network,简称 ANN)解决问题的思路相对而言不太精确,大概就是——通过几个函数算出一个近似值,接近 0 就说是 0,接近 1 就说是 1。

首先,引入一个激活函数:

1
sigmoid(x) = 1.0 / (1 + exp(-x))

举个例子:sigmoid(1.777) = 1.0 / (1 + exp(-1.777)) ≈ 0.855326

类似的激活函数还有 tanh,但其实用 ReLU 更好,既简单又接近生物上的神经元。参考:在神经网络中,激活函数sigmoid和tanh除了阈值取值外有什么不同吗?请问人工神经网络中的activation function的作用具体是什么?为什么ReLu要好过于tanh和sigmoid function?。但是 sigmoid 比较古老,很多教材拿它举例,稣也沿用它。

我们要求的函数是这样的:

1
f(x1, x2) = sigmoid(w1 * g(x1, x2) + w2 * h(x1, x2) + w3)

其中:

1
2
g(x1, x2) = sigmoid(wg1 * x1 + wg2 * x2 + wg3)
h(x1, x2) = sigmoid(wh1 * x1 + wh2 * x2 + wh3)

最终要求的是这三对系数:

1
2
3
wg1 wg2 wg3
wh1 wh2 wh3
w1 w2 w3

通俗说法叫求 w,其中序号为 3 的系数,又叫 bias 或者 b。

函数 f、g、h 其实就是一个神经元(neuron),结构如下:

神经元结构图

神经元结构图 DOT 源文件

训练出来的一个解是:

1
2
3
-5.734 -6.029 1.777
-3.261 -3.172 4.460
-6.581 5.826 -2.444

下面我们来验证一下,举例 x1 = x2 = 0 比较容易算:

1
2
3
4
g(0, 0) = sigmoid(1.777) ≈ 0.855326
h(0, 0) = sigmoid(4.460) ≈ 0.988570
f(0.855326, 0.988569) = sigmoid(-6.581 * 0.855326 + 5.826 * 0.988570 + -2.444)
= sigmoid(-2.313491586) ≈ 0.090012 ≈ 0

结论

ANN 就是数学的运用,训练就是在随机的 w 组合通过参考已知解逐渐纠正误差,逼出正解 w 组合。

打个比方,练习投篮的过程:肉眼观测,无数次调高低角度、出手力度、左右偏差,最终找到一套合适的参数,这个叫培养了球感……

机器学习也差不多是这样的过程,只是它比人快很多。

学习 MongoDB 选举机制

为了快速了解 MongoDB 选举机制,在网上找了一些文章来学习,后来发现里面提到的一些机制都过时了,尝试看代码了解,发现协议有 PV0 和 PV1 两种。

代码:https://github.com/mongodb/mongo/blob/r3.6.5/src/mongo/db/repl/topology_coordinator.cpp

一篇比较新的参考文章:https://blog.csdn.net/wentyoon/article/details/78986174

如果新选举出的主节点立马挂掉,至少需要 30s 重新选主,这个是由 leaseTime 常量决定的:

const Seconds TopologyCoordinator::VoteLease::leaseTime = Seconds(30);

PV0 时,一个反对会将最终票数减 10000,即在绝大多数情况下,只要有节点反对,请求的节点就不能成为主节点,由 prepareElectResponse 函数实现,里面有不少 vote = -10000;,PV1 版本取消了否决票。

留一法交叉验证

题目

假设有如下一组输入并输出一个实数的数据,则线性回归(Y = bX + c)的留一法交叉验证均方差为?

X Y
0 2
2 2
3 1

A. 10/27
B. 20/27
C. 50/27
D. 49/27

概念

1. 交叉验证(Cross Validation)

也称作循环估计(Rotation Estimation),是一种统计学上将数据样本切割成较小子集的实用方法。

在模式识别(Pattern Recognition)和机器学习(Machine Learning)的相关研究中,经常会将整个数据集合分成两个部分,分别是训练集合和测试集合。在一个 n 个元素的集合,选择 r 个元素做训练集(非空集,r > 0),剩下的 n - r 个做测试集,这可以用“组合”计算有多少种可能。把每种组合都做过一遍就是交叉验证。

2. 组合(Combination)

nCr 表示由 n 个不同元素中,每次取出 r 个不重复之元素的组合,用符号 C n(下标)r(上标)表示。

3. 留一法交叉验证(Leave-one-out Cross Validation)

只留一个元素做测试集,即:r = n - 1。

4. 均方差

标准差(Standard Deviation),别名:标准偏差、实验标准差、均方差,是离均差平方的算术平均数的平方根,用 σ 表示。标准差是方差的算术平方根。标准差能反映一个数据集的离散程度。平均数相同的两组数据,标准差未必相同。

解题

三个元素的集合留一,一共有 3C1 = 3 种组合,画 3 个点:

  • A = (0, 2)
  • B = (2, 2)
  • C = (3, 1)
  1. 连接 A 和 B,得到直线 Y = 2,C 点的偏差 = 2 - 1 = 1
  2. 连接 A 和 C,得到直线 Y = (6 - X) / 3,B 点的偏差 = 4/3 - 2 = -2/3
  3. 连接 B 和 C,得到直线 Y = 4 - X,A 点的偏差 = 4 - 2 = 2

所以方差为:(1^2 + (2/3)^2 + 2^2) / 3 = (9 + 4 + 4 * 9) / 27 = 49/27

题目说的是“均方差”,根据百度百科标准差词条的说法,“均方差”==标准差,要开平方……所以题目中的答案没有一个是对的。出题者想让我们选 D,稣偏要选 F,你懂的 ck……

特稣垃

“你留在我身体里的东西,我会用内力逼出来!”

“别装逼,稣戴套了……”

话虽如此,稣还是既吃惊又不解,刚才没有高空坠落啊!这到底是肿么肥事?稣瞄了一眼自己的 iPhone 7,红色的套依然崭新地散发金属般的光泽,显示的时间是凌晨 4 点多,但日期是 2019 年……这个女人,稣好像不认识,为什么会睡在特稣垃里?必须好好追忆一番!

稣买了一辆特稣垃摸抖歪,改造成一个可以写代码和睡觉的移动小房,每周都有一两天,吃完晚饭,上健身房锻炼,洗澡,然后把车停到 JFC 充电车位,开空调,写代码,睡觉。这一系列动作灰常自然,不太可能出八哥,但是这个女人……实在是个异常。稣从来不去酒吧,健身房也没认识这号人物。难道这是特稣垃的车灵?

以其乱猜,不如直接问她,“你是人是车?怎么会在这里?”

“车?你怎么不问是不是鬼?我是你过去妻啊!”

“小凰?你怎么变成这样的……样子都和上次不一样!”

“你忘记了?我们刚刚从 2024 年穿越回来的,这是我 2024 年的样子。”

“呃,这么一说,仔细看你,还有点像小老婆!”

“哈,是的,她因为不好好学习,已经被我取代,都消失好几年了。”

“握叉!?这事情稣会同意?”

“别装逼,你就喜欢知书达理的美女,这不就是我?”

“稣是这种人吗?咳,嗯!稣就是这种人……但为什么我们要穿越到过去?”

“因为在 2024 年,电动房车很流行,很多程序员下班,就找充电桩车位过夜,关系好的一些基友,还会相约停在一起,好交流。然后大量上班族都不买房,不生孩子了,房价大跌。你穿越的目的就是要告诉现在的自己,记得把房子卖了,而且不要买特斯拉,上班族专用的国产电动房车很快就要流行了。”

“这个理由不错,但穿越本身是什么鬼?太不科学了!”

“没错,我就是鬼!我带你来的。”

吓醒。

小本科对非欧几里得几何学脱敏的故事

高中时代

从小喜欢天文和数学,但高中时,有两个事件,促使后来读了挨踢专业。

  1. 穷。所有人都说读天文学很烧钱,穷人家是负担不起的,没学到家就出来又不好就业。

高一,有电脑课,但觉得电脑没人脑聪明,没什么兴趣。后来意外看到 Bill Gates 的事迹,明白了挨踢行业是很赚钱的,而且这个行业不怎么需要讲人情世故,也是自己可能擅长的领域。

  1. 被非欧几何打击了。和数学课代表交好,经常讨论数学,都喜欢自学超前的知识。其中讨论过的一个最大的未解之谜就是:

三角形内角和可以大于或小于 180 度。

当时只学到一些皮毛概念,仅知道“黎曼几何学是大于,罗巴切夫斯基几何学是小于”,但已经大受打击……

大学时代

大部分时间都用于实践编程技术,原来比较擅长的数学和英语都被牺牲,不怎么认真去学。

但有追究过非欧几何学到底怎么来的:公理体系中采用了不同的平行定理。

  • 在平面内,从直线外一点,至少可以做两条直线和这条直线平行;

  • 在平面内,从直线外一点,有且只有一条直线和这条直线平行;

  • 在平面内,从直线外一点,不能做直线和已知直线平行。

当时没有去深入理解,看了一个例子说球体表面的两条直线都会相交,结果就对非欧几何过敏了……脑子里不断产生抵抗,球面不是平的,球面的直线特么是弯的,这让直男怎么接受?

然后就把非欧几何学当成是外星的哲学了,觉得不是个有用的理论,完全忽视了自以为能理解的广义相对论是和黎曼几何学有关的!

突然脱敏

最近补了点数学基础,顺便想把这个问题解决掉。纠正过程如下:

  • 球面上的两点之间,直线最短,嗯,在球体上看,最短的直线是穿过球体内部的,那个才是直的。

不对,我们讨论的是二维的面,你怎么扯到三维的球体,还内部?

  • 球面上的直线是指什么?用地球来比方,赤道线和纬度线是不是都算?

赤道线是,但其他的纬度线不是,其它纬度线上的两点之间最短的线,并不在纬度线上,纬度线绕的更远,最短的还是这两点加上球心切面上两点之间那段圆弧(劣弧)。

  • 直线只是定义一样,但在两种不同体系的面上样子是不一样的?

是的,“两点之间,直线段最短”球面上的直线,在三维世界看确实是弯的,但在二维世界,它是直的……是直的……是直的。在四维空间看我们的世界,也许也是弯的,但反正我们在三维空间看,是直的!虽然我们能找到其实是弯的证据。

脱敏是很重要的能力

这其实不难理解,但长期自我抑制,不去解决它,再好的理解能力也没用武之地。

有些故事,要先相信,才有续集。

你是直的,还是弯的?

批量导出 QQ 空间说说

创作故事

2015-03-16 为了导出自己的说说,写了个半自动的程序,手动分析几个参数填到代码理,很快就刷刷地下载了 7 年的说说。第二天,就在知乎回答了两贴。

如何一次性导出QQ空间说说?

如何批量导出特定 QQ 号的所有说说?

2015-09-07 发现导出程序失效了,参数有点变化,但很快又跟进。

2015-11-27 又失效了,除了 json 字段有变化,还增加了对 Cookie 的验证,于是又加上了 Cookie 的模拟。

2015-12-11 又又失效了,这次增加了对 UserAgent 的验证……继续跟进。

由于知乎的热度,越来越多的人找 UMU 导出,但这个程序是半自动的,会占用宝贵的时间,所以要收点人工费。需要的可以联系微信 UMUTech,QQ:154401181(验证消息:qzone,QQ 比较少用,尽量先加微信)。

收费规则

1. 8.88 元起,每 1000 条 8.88 元人民币;

2. 未满 1000 条部分,采用进一法,即 10001 条,是收费 8.88 * 2 = 17.76 元;

3. 封顶 88.88 元,所以,如果您的说说很多也不用害怕会很贵。

4. 支持 QQ 红包、微信、支付宝。

FAQ

1. 要交出 QQ 密码吗?

答:不必须。如果您的说说都是公开的,则完全没有必要交出密码。如果您发过只有自己或者少数好友可见的说说,则需要用您的账号密码登录才能抓全。

2. 我的 QQ 空间被封了,别人都无法访问,可以导出吗?

答:只要您自己能访问就可以,但要您协助登录。比如让您刷一个二维码,(二维码在本人的机器上产生,截图给您,所以您刷完是在本人机器上登录)。极少数时候因为登录保护,刷二维码可能失败,实在不行,要提供您的账号和密码。您可以事先改一个临时密码,事后再改掉。

3. 导出的格式是什么样的?

答:主体是一个 json 文件,里面有您全部说说,包括说说本身、别人的回复、图片链接(没有图片本身)。另外生成一份 txt 文件,只有发帖时间和说说本身,其它都没有。

备注

如果有时间会改进,比如说搞成图文并茂的格式,也导出博客。

外星人

早上,稣像往常一样吓醒,不同的是,稣在加拿大某大学宿舍,身体是别人的……稣熟悉一下环境,思考一下人生,还记得自己家住道州德国,如果用这个加拿大人的身份生活下去,德国的父母不是很难见面?而且稣原本的身体会不会被别人占用了?如果是对换身体,那对方也可能想换回来。于是稣决定赶回德国!

打开柜子,收拾了一些衣物,发现这个加拿大人还是蛮有品味的。然后记起,他有一台红色外星人笔记本,翻了一下床,果然在被子里面,很高端。开始犹豫要不要带上,这又不是稣的,留着,如果他回来,起码还有东西,不至于都被稣洗劫了……但是仔细想想,又有点可笑,身体都被占了,他还活着吗?还能回来吗?都不知道呢!

最终,稣还是没带,因为——太重了!这时候他的舍友来了,但是稣并不记得他的名字,所以有点尴尬,他见到稣,开玩笑地说:“假期还窝在宿舍?不出去找女朋友玩?”嗯?他女朋友是个挪威美女,年轻貌美!哈!这个可以带……问题是稣不知道她在哪里,算了,还是赶快溜回德国。

出宿舍楼大门的时候,保安看到稣带着一些行李,就说要刷脸备案。好吧,刷!反正这脸不是稣的……果然逼逼几声就通过了,保安说:“谢谢配合,Aerial 先生!”咦?怎么像个字体名,Arial?

街上没啥人,出来之后才知道是因为地球已经被外星人入侵!有两种外星人来到地球,其中一种像章鱼,会发射神奇脑电波控制地球人,变成他们的奴隶。另一种像螳螂,自带刀剑,会到处砍人。

这里还插播一个新闻:据说,有个专门对付外星人的公司,打算通过提供保护赚钱,结果被团灭……老板打算带着老婆逃亡,却被员工砍死。专家分析是因为员工被外星章鱼控制。

稣十分后悔,刚刚吓醒,又被吓尿……赶快就近躲进一座医院。稣走进手术室,看到医生们都在做手术,但越看越不对劲,几个人被切成一块块的,这是什么医术?好叼,还能救活?最后那些人被推出来,稣才明白,那些医生是在解剖尸体,研究外星人如何控制人类……这个地方其实也不是医院,是非地球人研究中心!

研究员们看到稣,很惊讶地问:“你是怎么活着到这里的?”稣回答:“稣是从某大学出来的,那边还有很多人,很容易就走到这里。”研究员哈哈狞笑:“你说的那个大学,在你出来之后就团灭了。现在整个地球可能只有各个非地球人研究中心还有活人。如果你不介意,我们要研究一下你是如何能躲开外星人的。”

研究?稣想起电影理的情节,被研究的意思应该就是被当畜牲宰割吧!果断拒绝!但如果直接拒绝,可能他们会来硬的,所以稣先忽悠他们:“可以啊,但是稣饿了,你们这里有吃的吧?”

咳!?陪他们吃了一顿白灼章鱼……然后稣开始有种不祥的预感,这是真的地球章鱼吗?胆战心惊问出口,果然得到吃精的回答:“这些都是外星人,战争多年,早就没东西吃了,只能捡打死的外星人吃……”呃,稣只想说:“还真特么好吃啊!!而且一只可以吃很久,实在是太实惠了!”

吃完,稣就来了一个灵感,能不能生吃外星章鱼,这样可以获得他们的能力。研究员们听稣这样讲,惊喜万分,觉得很可行。于是从冰柜里取出一只速冻的小章鱼。这是被打死的外星章鱼肚子里怀的,还有生命力的时候被速冻保存。研究员不忘科普:“外星章鱼是胎生的,这就是和地球章鱼的区别!”

解冻完,大家毫不客气地讨论怎么分食,小章鱼一脸无辜。最后,院长亲自示范一口闷,果然吃完之后,院长就有了控制别人的能力。稣建议,出去控制那些螳螂把自己砍死,然后再吃个螳螂,这样院长就自带刀剑了。院长喜出望外,立马就去砍死几只螳螂,带回来给大家吃,哦不,是研究。

稣看到了希望,就表示了自己回德国的心愿。院长当即表示,会护送稣回去。临走前,稣问最后一个问题:“院长,为何相信稣的方案?生吃可能有细菌、病毒,太冒险了!”

“我是广东人。”

再次吓醒。

C/C++ 数字后缀用大写还是小写?

问题

对于 uint64_t 的常量,以前只写 Windows 平台的代码,所以习惯加上 Ui64 的后缀,前几天发现 g++ 不支持,于是改为 ULL,然后又测试了 ull,也是可以的,开始纠结以后是用大写还是小写……

分析

输入时,是小写比大写方便,但小写的字母 l,容易被认成数字 1,比如 1234567890ll,看起来没 1234567890LL 容易辨认。虽然可以通过字体来提高辨识度,但不是每个人都用这样的字体。

看了 golang,并不支持数字加后缀,好样的!

同样的纠结,还有十六进制的 0x 和 0X,abcdef 和 ABCDEF……

golang 一样有这个纠结,看来要彻底的单一化还是不容易的。

ATL 的 HexEncode 函数,输出的十六进制是大写的,UMU 也一直喜欢大写格式,但 std::hex 默认是小写,要用 std::setiosflags (std::ios::uppercase) 改大写。所以,如果自己实现一个 HexEncode 函数,默认还是应该返回小写格式的,这个道理很简单,您看一下键盘,上面标注的都是大写字母,但按下去,默认是小写。要大写?请按住 SHIFT。

总结

写代码时,数字后缀用大写,毕竟也很少需要加后缀;输出时,数字并不需要带后缀。

而十六进制可以更宽松点:写代码时,用小写,节省按 SHIFT 键;输出时,看情况。

MongoDB Shard ID hash 算法 std::hash 的跨平台性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <functional>
#include <iomanip>
#include <iostream>
#include <string>


int main()
{
std::string str = "Meet the new boss...";
std::size_t str_hash = std::hash<std::string>{}(str);
std::cout << "hash(" << std::quoted(str) << ") = " << str_hash << std::endl;

str = "Meet the new boss..;";
str_hash = std::hash<std::string>{}(str);
std::cout << "hash(" << std::quoted(str) << ") = " << str_hash << std::endl;

str = "Meet the new boss../";
str_hash = std::hash<std::string>{}(str);
std::cout << "hash(" << std::quoted(str) << ") = " << str_hash << std::endl;

str = "Meet the new boss..,";
str_hash = std::hash<std::string>{}(str);
std::cout << "hash(" << std::quoted(str) << ") = " << str_hash << std::endl;

return 0;
}

Windows, VS 2017 的结果:

hash(“Meet the new boss…”) = 5935324269489717502

hash(“Meet the new boss…;”) = 5935347359233909933

hash(“Meet the new boss…/”) = 5935325369001345713

hash(“Meet the new boss…,”) = 5935322070466461080

Ubuntu 16.04, g++ 5.4.0 20160609 的结果:

hash(“Meet the new boss…”) = 10656026664466977650

hash(“Meet the new boss…;”) = 12509209616339026574

hash(“Meet the new boss…/”) = 6552276210272946664

hash(“Meet the new boss…,”) = 15639609178671340058

还好我们不会在生产环境,使用 Windows 部署 MongoDB……

1
2
3
std::size_t ShardId::Hasher::operator()(const ShardId& shardId) const {
return std::hash<std::string>()(shardId._shardId);
}

详见:https://github.com/mongodb/mongo/blob/master/src/mongo/s/shard_id.cpp

这个 std::hash 在 x86 和 x64 下都不一样,所以,让我们看看 MongoDB 如何解决这个问题:

MongoDB 3.4 no longer supports 32-bit x86 platforms.

好样的!