引子

小孩上补习班回来,很自豪地说,终于搞清楚了“的地得”的区别。 于是举了一个例子,考一考他。

我已经讲 de 非常 de 清楚了。

这个句子中,两个de分别是啥。 我们首先讨论了第二个de。 其实刚开始,我自己还没有意识到第一个de的存在。

小孩在实习班学到的规律,主要依赖词性。 于是认为“非常de清楚”中,“清楚”是一个副词,那么应该用“得”。 但在这个例子中,却是错了。

一路上,两个人争论,最后结论是, 有些规律是表现性的,是有特例的, 而有些规律是根本性的,是没有特例的。 牛顿的伟大,就在于他发现的三大定律,没有特例。 一个 F=ma 解决了所有问题。

三步法

关于“的地得”,第一步要分清楚关联的两词,谁修饰谁,谁是主,谁是次。 如果分不清楚,可以尝试删掉其中一个。 如果删掉了其中一个,句子仍然保留了愿意,则留下来的是主,被删掉的是次。反之,如果删掉了其中一个,句子变得不符逻辑,则被删掉的是主,留下来的是次。

前述的例子:

我已经讲 de 非常 de 清楚了。

如果要确定“非常”和“清楚”的主次关系,那么可以尝试删掉一个,例如“清楚”,得到的句子为:

我已经讲 de 非常 de 了。

即使是小学生,也可以判断这个句子是不通顺的,所以被删掉的“清楚”是主,留下来的“非常”是次。

第二步看主的位置,如果主的位置在de之间,那么de应该用“得”。 如果主在de之后,则进入到三步。

前述的例子,“清楚”为主,“非常”为次,主在de之后,需要进入第三步。

第三步看主的词性。这一步只适用于主位于de之后的情况。 如果主的词性是名词,那么de应该用“的”。 如果不是名词,那么可能是形容词、副词或动词,都应该用“地”。

前述的例子,“清楚”不是一个名词,所以应该用“地”。

综上,三步分别是:

  1. 看主次
  2. 看位置
  3. 看词性

python

上述的过程,可以用 python 实现为一个函数 guess_de()。 函数的参数是前后两个词,分别命名为 predecessor 和 successor。 所以函数的原型是:

def guess_de(predecessor, successor)

我们需要两个辅助函数,一个用于判断主次 is_significant(), 一个用于判断词性 part_of_speech(), 这两个函数的实现应该都非常难,涉及到自然语言处理。 现在假设这两个辅助函数是现成的,那么 guess_de() 的实现如下:

def guess_de(predecessor, successor):
    if is_significant(predecessor):
        return "得"
    else:
        if part_of_speech(successor) == "noun":
            return "的"
        else:
            return "地"

真值表

也可以用表格来描述,定义两个属性或者叫特征,分别为

  1. PS: predecessor is significant, 位于 de 前面的词是不是主成分
  2. SN: successor is noun, 位于 de 后面的词是名词

这样定义的两个属性,都是布尔型,取值为真或假。

PS SN de
True True
True False
False True
False False

不带中间层的神经网络

是否有可能构造一个神经网络,实现上述表格的映射。

最简单的网络应该是,输入层两个结点,分别对应 PS 和 SN,输出层三个结点,分别对应“的”、“地”、“得”,分别命名为

  1. D1:的
  2. D2:地
  3. D3: 得

假设没有中间层,也没有激活函数,则共有 6 个 weight,假设从上到下依次为 w1, w2, w3, w4, w5, w5,则每一个 weight 连接的输入和输出如下表:

input weight output
PS w1 D1
SN w2 D1
PS w3 D1
SN w4 D2
PS w5 D1
SN w6 D2

虽然真假在程序中一般用1和0表示,但在神经网络中,0的输入会导致weight失去意义,所以假设在输入层,用1表示真,用-1表示假,则神经网络对应的映射表格为:

PS SN D1 D2 D3 de
1 1 w1+w2 w3+w4 w5+w6 D3
1 -1 w1-w2 w3-w4 w5-w6 D3
-1 1 w2-w1 w4-w3 w6-w5 D1
-1 -1 -w1-w2 -w3-w4 -w5-w6 D2

如果期望通过SoftMax得到最终的结果,那么上述的表格,每一行 de 列的值,应该正好对应 D1 D2 D3 三列中最大值,所以得到下面八个不等式:

  1. w5+w6 > w1+w2
  2. w5+w6 > w3+w4
  3. w5-w6 > w1-w2
  4. w5-w6 > w3-w4
  5. w2-w1 > w4-w3
  6. w2-w1 > w6-w5
  7. -w3-w4 > -w1-w2
  8. -w3-w4 > -w5-w6

其中 6 式与 3 式等价,8 式与 1 式等价。对 5 式和 7 式变形后,得到以下式子:

  1. w5+w6 > w1+w2
  2. w5+w6 > w3+w4
  3. w5-w6 > w1-w2
  4. w5-w6 > w3-w4
  5. w1-w2 < w3-w4
  6. w3+w4 < w1+w2

分别合并 1、2、7 以及 3、4、5 得到

  1. w3+w4 < w1+w2 < w5+w6
  2. w1-w2 < w3-w4 < w5-w6

weight的值只要满足上述不等式即可,可以设定 1 式成立,例如三组和依次取 10、20、30,即下表:

pair sum
w3+w4 10
w1+w2 20
w5+w6 30

再在这个限制下,分别确定 w1~w6 的值,使 2 式成立,即三组值之间,差值依次变大。由于和是偶数,所以差也应是偶数,所以可以令三组差依次取 0、2、4,即下表:

pair difference
w1-w2 0
w3-w4 2
w5-w6 4

一一对应,分别联立三个方程组可解得:

weight value
w1 10
w2 10
w3 6
w4 4
w5 17
w6 13

代入原始表格检验

PS SN D1 D2 D3 de
1 1 w1+w2=20 w3+w4=10 w5+w6=30 D3
1 -1 w1-w2=0 w3-w4=2 w5-w6=4 D3
-1 1 w2-w1=0 w4-w3=-2 w6-w5=-4 D1
-1 -1 -w1-w2=-20 -w3-w4=-10 -w5-w6=-30 D2

确认 de 对应的值为对应行 D1、D2、D3 最大值。

映射表

另一种办法,是直接用一个映射表。

输入为两个布尔型,可以直接转换为类型整数的 input_index:

input_index = ((PS<<1) | SN)

值为 0~3,如下表:

PS SN input_index
True True 3
True False 2
False True 1
False False 0

输出实际为枚举型,可以直接用一个数组存放:

output = ["的", "地", "得"]

真值表扩展为

PS SN input_index de output_index
True True 3 2
True False 2 2
False True 1 0
False False 0 1

为了实现 input_index 到 output_index 的映射,定义 index_map:

index_map = [1, 0, 2, 2]

有了上述定义后,并以 PS, SN 为参数的函数为:

def guess_de(PS, SN):
    input_index = ((PS<<1)|SN)
    output_index = index_map[input_index]
    return output[output_index]