的地得
引子
小孩上补习班回来,很自豪地说,终于搞清楚了“的地得”的区别。 于是举了一个例子,考一考他。
我已经讲 de 非常 de 清楚了。
这个句子中,两个de分别是啥。 我们首先讨论了第二个de。 其实刚开始,我自己还没有意识到第一个de的存在。
小孩在实习班学到的规律,主要依赖词性。 于是认为“非常de清楚”中,“清楚”是一个副词,那么应该用“得”。 但在这个例子中,却是错了。
一路上,两个人争论,最后结论是, 有些规律是表现性的,是有特例的, 而有些规律是根本性的,是没有特例的。 牛顿的伟大,就在于他发现的三大定律,没有特例。 一个 F=ma 解决了所有问题。
三步法
关于“的地得”,第一步要分清楚关联的两词,谁修饰谁,谁是主,谁是次。 如果分不清楚,可以尝试删掉其中一个。 如果删掉了其中一个,句子仍然保留了愿意,则留下来的是主,被删掉的是次。反之,如果删掉了其中一个,句子变得不符逻辑,则被删掉的是主,留下来的是次。
前述的例子:
我已经讲 de 非常 de 清楚了。
如果要确定“非常”和“清楚”的主次关系,那么可以尝试删掉一个,例如“清楚”,得到的句子为:
我已经讲 de 非常 de 了。
即使是小学生,也可以判断这个句子是不通顺的,所以被删掉的“清楚”是主,留下来的“非常”是次。
第二步看主的位置,如果主的位置在de之间,那么de应该用“得”。 如果主在de之后,则进入到三步。
前述的例子,“清楚”为主,“非常”为次,主在de之后,需要进入第三步。
第三步看主的词性。这一步只适用于主位于de之后的情况。 如果主的词性是名词,那么de应该用“的”。 如果不是名词,那么可能是形容词、副词或动词,都应该用“地”。
前述的例子,“清楚”不是一个名词,所以应该用“地”。
综上,三步分别是:
- 看主次
- 看位置
- 看词性
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 "地"
真值表
也可以用表格来描述,定义两个属性或者叫特征,分别为
- PS: predecessor is significant, 位于 de 前面的词是不是主成分
- SN: successor is noun, 位于 de 后面的词是名词
这样定义的两个属性,都是布尔型,取值为真或假。
PS | SN | de |
---|---|---|
True | True | 得 |
True | False | 得 |
False | True | 的 |
False | False | 地 |
不带中间层的神经网络
是否有可能构造一个神经网络,实现上述表格的映射。
最简单的网络应该是,输入层两个结点,分别对应 PS 和 SN,输出层三个结点,分别对应“的”、“地”、“得”,分别命名为
- D1:的
- D2:地
- 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 三列中最大值,所以得到下面八个不等式:
- w5+w6 > w1+w2
- w5+w6 > w3+w4
- w5-w6 > w1-w2
- w5-w6 > w3-w4
- w2-w1 > w4-w3
- w2-w1 > w6-w5
- -w3-w4 > -w1-w2
- -w3-w4 > -w5-w6
其中 6 式与 3 式等价,8 式与 1 式等价。对 5 式和 7 式变形后,得到以下式子:
- w5+w6 > w1+w2
- w5+w6 > w3+w4
- w5-w6 > w1-w2
- w5-w6 > w3-w4
- w1-w2 < w3-w4
- w3+w4 < w1+w2
分别合并 1、2、7 以及 3、4、5 得到
- w3+w4 < w1+w2 < w5+w6
- 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]