NLP中的平滑方法

背景

MLE

我们来看一个概率模型,也就是 p(e)在 event space E 下的概率分布,模型很可能会用最大似然估计(MLE),如下
$$P_{MLE}={c(x) \over \sum_ec(e)}$$

然而,由于观测数据不充足,很多事件 x 并没有在训练数据中出现,也就是 c(x)=0,PMLE=0,这是有问题的,没有在训练数据中出现的数据,并不代表不会在测试数据中出现,如果没有考虑到数据稀疏性,你的模型就太简单了!

没有观测样本,怎样来预测呢?

为什么要平滑?

因为根据链式法则,联合概率是分概率的乘积。如果单个概率为0,会导致整体概率=0。所以必须要平滑。

NLP中哪些任务要平滑

语言模型: ngram, nnlm
机器翻译:

##

Data sparsity 是 smoothing 的最大原因。Chen & Goodman 在1998 年提到过,几乎所有数据稀疏的场景下,smoothing 都可以帮助提高 performance,而数据稀疏性几乎是所有统计模型(statistical modeling)都会遇到的问题。而如果你有足够多的训练数据,所有的 parameters 都可以在没有 smoothing 的情况下被准确的估计,那么你总是可以扩展模型,如原来是 bigram,没有数据稀疏,完全可以扩展到 trigram 来提高 performance,如果还没有出现稀疏,就再往高层推,当 parameters 越来越多的时候,数据稀疏再次成为了问题,这时候,用合适的平滑方法可以得到更准确的模型。实际上,无论有多少的数据,平滑几乎总是可以以很小的代价来提高 performance。

平滑方法

  • Additive smoothing
    • Add-one smoothing
      -
  • Good-Turing estimate
  • Jelinek-Mercer smoothing (interpolation)
  • Katz smoothing (backoff)
  • Witten-Bell smoothing
  • Absolute discounting
  • Kneser-Ney smoothing

Additive smoothing - 贝叶斯估计

MLE estimate: $$P_{MLE}(w_i|w_{i-1})={c(w_{i-1}w_i) \over c(w_{i-1})}$$

Additive smoothing:

$$P_{Add-1}(w_i|w_{i-1})={c(w_{i-1}w_i)+\delta \over c(w_{i-1})+\delta V}$$

通常取值 $0 \lt \delta \le 1$。

$\delta=0$时就是极大似然估计。
通常取$\delta=1$,这时成为拉普拉斯平滑 (Laplace smoothing),即假设每个词多出现了一次。

极大似然估计可能会出现索要估计的概率值为0的情况。这时会影响到后验概率的计算结果,使分类产生偏差。解决这一问题的方法是采用贝叶斯估计

这样等价于拉普拉斯先验的贝叶斯估计。还是dir(delta)先验?

参考

  • 《统计学习方法 | 李航》 4.2.3 贝叶斯估计

- Katz smoothing (backoff)

为什么叫backoff

Katz backoff是一个经典的语言平滑模型,我们知道一般来说低阶模型出现的频率更高(低阶的元组包含在高阶元组中,阶数越高越稀疏),从而更加可靠,于是乎,未出现的高阶元组可以利用高阶元组中包含的低阶元组对其进行平滑。总之,高阶模型可靠时候便使用高阶模型,否则使用低阶模型。

差值(Interpolation) vs. 回退(backoff)

扩展阅读

python 命名空间、权限

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Test:
def __init__(self):
self._a = 1
self.__b = 2 # 内部会被重命名为_Test__b
self.__c__ = 3

def print(self):
print(self._a)
print(self.__b)
print(self.__c__)
print('\n')

t = Test()
t.print()

t._a = 5
t._Test__b = 6
t.__c__ = 7
t.print()

ss

public private

需求

  • 私有变量
  • 私有方法
  • 私有类

实现 - python

Python中的成员函数和成员变量都是公开的(public),在python中没有类似public,private等关键词来修饰成员函数和成员变量。

在python中定义私有变量只需要在变量名或函数名前加上 ”__“两个下划线,那么这个函数或变量就是私有的了。
在内部,python使用一种 name mangling 技术,将 __membername替换成 _classname__membername,也就是说,类的内部定义中,

  • 私有变量: 实例._类名__变量名
  • 私有方法: 实例._类名__方法名()
  • 私有类:

ssdfsd

  • xx: 公有变量
  • _xx: 表示保护成员(属性或者方法),只有类对象和子类对象自己能访问到这些变量。
    • 以单下划线开头的变量和函数被默认当作内部函数
    • 使用from module improt *时不会被获取,但是使用import module可以获取
    • 什么鬼设计
  • xx_:
    • 在解析时并没有特别的含义,但通常用于和 Python 关键词区分开来,比如如果我们需要一个变量叫做 class,但 class 是 Python 的关键词,就可以以单下划线结尾写作 class_
  • __xxx: 表示私有成员
    • 表示名字改编 (Name Mangling),即如果有一 Test 类里有一成员 __x,那么 dir(Test) 时会看到 _Test__x 而非 __x。这是为了避免该成员的名称与子类中的名称冲突。但要注意这要求该名称末尾没有下划线。
  • __xxx__: 表示这是一个特殊成员
    • 是一些 Python 的“魔术”对象,如类成员的 __init____del____add____getitem____call__ 等,以及全局的 __file____name__ 等。 Python 官方推荐永远不要将这样的命名方式应用于自己的变量或函数,而是按照文档说明来使用。
  • _: 作被丢弃的名称

实现 - Java

实现 - C/C++

扩展阅读

tensorflow中的动态维度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import tensorflow as tf
sess = tf.Session()
q = tf.placeholder(tf.float32, shape=(None, 1024))

# 采用未知维度(None),初始化tensor
dim_none = tf.shape(a)[0]
p = tf.zeros([dim_none], tf.float32)

# 采用未知维度(None),初始化variable
v = tf.Variable(tf.ones([dim_none,5])) # 这里会报错

# run
rand_array = np.random.rand(10, 1024)
p_value = sess.run(p, feed_dict={q: rand_array})
p_value.shape # (10, 1024)