NLP(Natural Language Processing)
NLP主要是关注计算机和人类(自然)语言之间的相互作用的领域。如果要想实现人机间自然语言通信意味着要使计算机既能理解自然语言文本的意义,也能以自然语言文本来表达给定的意图、思想等。前者称为自然语言理解,后者称为自然语言生成,这也是NLP的两大任务。
但处理它的困难之处在于自然语言是一种符号,出现的理由是为了作为一种方便人类进行高效交流的工具,但,想要高效免不了会进行省略,即说话人之间交流可能只说一小部分话,然后剩下的部分是由听话人按自己的世界观进行补全。另外语言和句法中本身存在模糊不清,同一句话可能有多个意义。语义可能与顺序和指代词有关,而且某些语言就人类角度还带有深意等等等…
语言模型(Language Model)
想要量化语言,首先需要建模。语言模型就是用来衡量一句话多大程度是人话的模型(是否通顺,符合语法句法),理论上可以直接把整个句子放进模型然后预测这个句子S是否是人话,但这样训练量大,且很难学习到句子的内部特征。那么可以使用Chain Rule,把句子中每个词的联合概率表示,也可以表示成多个条件概率的乘积。LM分为Unigram,Bigram(first order markov assumption),N-gram。
U
n
i
g
r
a
m
,
P
(
S
)
=
P
(
w
1
,
w
2
,
.
.
.
,
w
n
)
=
P
(
w
1
)
P
(
w
2
)
P
(
w
3
)
.
.
.
P
(
w
n
)
Unigram,P(S)=P(w_1,w_2,...,w_n)=P(w_1)P(w_2)P(w_3)...P(w_n)
Unigram,P(S)=P(w1,w2,...,wn)=P(w1)P(w2)P(w3)...P(wn)
B
i
g
r
a
m
,
P
(
S
)
=
P
(
w
1
,
w
2
,
.
.
.
,
w
n
)
=
P
(
w
1
)
P
(
w
2
∣
w
1
)
P
(
w
3
∣
w
2
)
.
.
.
P
(
w
n
∣
w
n
−
1
)
Bigram,P(S)=P(w_1,w_2,...,w_n)=P(w_1)P(w_2|w_1)P(w_3|w_2)...P(w_n|w_{n-1})
Bigram,P(S)=P(w1,w2,...,wn)=P(w1)P(w2∣w1)P(w3∣w2)...P(wn∣wn−1)
N
−
g
r
a
m
,
P
(
S
)
=
P
(
w
1
,
w
2
,
.
.
.
,
w
n
)
=
P
(
w
1
)
P
(
w
2
∣
w
1
)
P
(
w
3
∣
w
1
,
w
2
)
.
.
.
P
(
w
n
∣
w
1
,
w
2
,
.
.
.
,
w
n
−
1
)
N-gram,P(S)=P(w_1,w_2,...,w_n)=P(w_1)P(w_2|w_1)P(w_3|w_1,w_2)...P(w_n|w_1,w_2,...,w_{n-1})
N−gram,P(S)=P(w1,w2,...,wn)=P(w1)P(w2∣w1)P(w3∣w1,w2)...P(wn∣w1,w2,...,wn−1)
L
=
∑
w
∈
C
l
o
g
P
(
w
∣
c
o
n
t
e
x
t
(
w
)
)
,
C
为
w
的
映
射
L=\sum_{w\in C} logP(w|context(w)),C为w的映射
L=w∈C∑logP(w∣context(w)),C为w的映射
其中Estimating Probability的P(Wn) = 词汇Wn出现次数 / 语料库所有词汇数量V。
ungram model是最简单的,不考虑语言逻辑,如“今天 下雪 了”和“了 下雪 今天”的概率显然不应该是一样的。Bigram model在随机的基础上,会大概率选取关联概率最大的单词,因此优于unigram。N-gram,在有大量的语料库的基础上,可以训练。
如何评估语言模型?
计算P的概率便可以判断了,所以准确率,recall也是可以使用的。但是在LM一般使用句子的混乱度来判断:
P
e
r
p
l
e
x
i
t
y
=
2
(
−
x
)
Perplexity = 2^{(-x)}
Perplexity=2(−x)
x
=
l
o
g
P
(
w
1
)
+
l
o
g
P
(
w
2
∣
w
1
)
.
.
.
N
x=\frac{logP(w_1)+logP(w_2|w_1)...}{N}
x=NlogP(w1)+logP(w2∣w1)...x : average log likelihood。一般来说它越小越好,N-gram考虑了很多的情况时,Perplexity是最小的。
如何处理语料库没有的情况?
如果出现了语料库没有词汇,将会导致概率连乘为0的情况。Smoothing的方法有:
- Add-1 Smoothing。和贝叶斯一样的Laplace Smoothing ,即 P M L E ( w i ∣ w i − 1 ) = c ( ( w i , w i − 1 ) ) c ( w i ) P_{MLE}(w_i | w_{i-1}) = \frac{c((w_i , w_{i-1}))}{ c(w_i )} PMLE(wi∣wi−1)=c(wi)c((wi,wi−1)) P A d d − 1 ( w i ∣ w i − 1 ) = c ( ( w i , w i − 1 ) ) + 1 c ( w i ) + V P_{Add-1}(w_i | w_{i-1}) = \frac{c((w_i , w_{i-1})) + 1}{ c(w_i ) + V} PAdd−1(wi∣wi−1)=c(wi)+Vc((wi,wi−1))+1V是词典的大小(无重复词),在分母中加V而不是其他的数,是为了保持所有P()的总和为1。
- Add-k Smoothing。 P A d d − k ( w i ∣ w i − 1 ) = c ( ( w i , w i − 1 ) ) + k c ( w i ) + k V P_{Add-k}(w_i | w_{i-1}) = \frac{c((w_i , w_{i-1})) + k}{ c(w_i ) +k V} PAdd−k(wi∣wi−1)=c(wi)+kVc((wi,wi−1))+k可以自由设置k,但同时也可以优化 f(k) ,即把k包含到损失函数中。此时Perplexity = f(k),故 M i n p e r p l e x i t y = M i n f ( k ) = > k = a r g m i n k f ( k ) Min_{perplexity}= Min_{ f(k)} => k= argmin_k f(k) Minperplexity=Minf(k)=>k=argminkf(k)
- Interpolation。现在不出现的概率,不代表未来数据更多还不出现。某些词的重要性会不一样,即还需要考虑到词频信息。 P ( w n ∣ w n − 1 , w n − 2 ) = λ 1 P ( w n ∣ w n − 1 , w n − 2 ) + λ 2 P ( w n ∣ w n − 1 ) + λ 3 P ( w n ) , 其 中 λ 1 + λ 2 + λ 3 = 1 P(w_n|w_{n-1},w_{n-2}) = λ_1P(w_n|w_{n-1},w_{n-2})+ λ_2P(w_n|w_{n-1})+ λ_3P(w_n),其中 λ_1+λ_2+λ_3 =1 P(wn∣wn−1,wn−2)=λ1P(wn∣wn−1,wn−2)+λ2P(wn∣wn−1)+λ3P(wn),其中λ1+λ2+λ3=1
- Good-Turning Smoothing。考虑到了新的情况(未出现),相当于把一些概率已经给到了新事件 P G T = ( c + 1 ) N c + 1 N c P_{GT}=\frac{(c+1)N_{c+1}}{ N_c} PGT=Nc(c+1)Nc+1N_c是出现c次的单词的个数。
N-gram 语言模型缺点
简而言之,N-gram 模型就是用
P
(
w
t
∣
w
t
−
n
+
1
,
…
,
w
t
−
1
)
P(w_t | w_{t-n+1}, …, w_{t-1})
P(wt∣wt−n+1,…,wt−1)来模拟上述公式的方法,式子中的N比那时N-gram的来历。当N=1的时候,公式会退化变成:
p
(
w
t
∣
C
o
n
t
e
x
t
)
=
p
(
w
t
)
=
N
w
t
N
p(w_t|Context)=p(w_t)=\frac{N_{w_t}}{N}
p(wt∣Context)=p(wt)=NNwt
它也被称为一元语言模型,或者上下文无关语言模型,即不考虑该词所对应的上下文环境,仅考虑当前词本身的概率,那么通过计算词频就可以达成目标了。
但N-gram 语言模型也存在一些问题:
- N=1时无法建模出词之间的相似度(只是单纯算词频,没有考虑到上下文)。以及无法建模更远的关系。
- 但是当使用N=高阶的时候,往往这样的单词组合出现概率很低,整体的统计概率的可信度就大打折扣。
- 而且在处理数据时存在1 序列的长度未知。2 通过词的组合方式得到的句子非常庞大。3 耗费人力且相当主观。 4如果计算相似度非常不准确。
神经网络语言模型(Neural Network Language Model,NNLM)
其实处理N-gram的问题,不可以用one-hot处理词后再进行相似度计算?词数太多太稀疏?矩阵分解可以解决。词的权重可以利用TF * IDF来计算,其中TF表示词频,即一个词在这篇文本中出现的频率;IDF表示逆文档频率,即一个词在所有文本中出现的频率倒数。
但是,在数据集如何足够的情况下…请上深度学习,意在求解某个抽象过的特征向量来代表某个词(这也是主要区别,一个词就是词,另一个把词变成了向量,语义更丰富,而且每个句子、短语和逻辑表述都是向量,然后再神经网络负责合并),特别在是单词在句子语境中,应该通过单词的同伴来知道它的意思,在文本中理解它的意思。不过如何求解其中的参数,使其能够根据context上下文来进行理解句子的可能尝试呢?NNLM模型便如下图:
尝试用神经网络方法求解的一般思路是:
- 训练集:数据巨大且固定的词汇的集合 V = { w 1 . . . w t } V={w_1...w_t} V={w1...wt}
- 训练目标:得到一个好的函数能将每个词映射为向量– f ( w t , w t − 1 , … , w t − n + 2 , w t − n + 1 ) = p ( w t ∣ w 1 t − 1 ) f(w_t,w_{t-1},\dots,w_{t-n+2},w_{t-n+1})=p(w_t|w_1^{t-1}) f(wt,wt−1,…,wt−n+2,wt−n+1)=p(wt∣w1t−1),其中约束为 f ( w t , w t − 1 , … , w t − n + 2 , w t − n + 1 ) > 0 f(w_t,w_{t-1},\dots,w_{t-n+2},w_{t-n+1}) > 0 f(wt,wt−1,…,wt−n+2,wt−n+1)>0并且 Σ i = 1 ∣ V ∣ f ( i , w t − 1 , … , w t − n + 2 , w t − n + 1 ) = 1 \Sigma_{i=1}^{|V|} f(i,w_{t-1},\dots,w_{t-n+2},w_{t-n+1}) = 1 Σi=1∣V∣f(i,wt−1,…,wt−n+2,wt−n+1)=1。
- 损失函数:最大似然+正则项:
m a x 1 T ∑ t l o g f ( w t , w t − 1 , … , w t − n + 2 , w t − n + 1 ; θ ) + L ( θ ) max \frac{1}{T}\sum_tlogf(w_t,w_{t-1},\dots,w_{t-n+2},w_{t-n+1};\theta) + L(\theta) maxT1t∑logf(wt,wt−1,…,wt−n+2,wt−n+1;θ)+L(θ) - f 函数的深度拟合。
y
=
b
+
W
x
+
U
t
a
n
h
(
d
+
H
x
)
y=b+Wx+Utanh(d+Hx)
y=b+Wx+Utanh(d+Hx),其中
b
,
W
,
U
,
d
,
H
b,W,U,d,H
b,W,U,d,H都是参数。最后输出层采用sofamax得到概率p:
p ( w t ∣ w t − 1 , … , w t − n + 2 , w t − n + 1 ) = e y w t ∑ i e y i p(w_t|w_{t-1},\dots,w_{t-n+2},w_{t-n+1})=\frac{e^{y_{w_t}}}{\sum_ie^{y_i}} p(wt∣wt−1,…,wt−n+2,wt−n+1)=∑ieyieywt - 随机梯度下降:
θ + ϵ ∂ l o g p ( w t ∣ w t − 1 , … , w t − n + 2 , w t − n + 1 ) ∂ θ → θ \theta + \epsilon \frac{\partial log p(w_t | w_{t-1},\dots,w_{t-n+2},w_{t-n+1})}{\partial \theta} \to \theta θ+ϵ∂θ∂logp(wt∣wt−1,…,wt−n+2,wt−n+1)→θ
然后结合具体NNLM的模型图,它的处理细节是先假设每个单词的输入共享一个词向量(其实这是副产物,它的出现还是为了预测句子的),于是可以得到每个词向量,然后先nn一层,再把这个输入和原词向量拼接后在nn,以预测下一个词。网络训练完成后,也就得到这样的词向量,完成了word embedding。
- 为什么输入层直连输出层? 因为这是一种recurrent的作法,其实可以做,也可以不做。做了拼接,相当于扩展了基本特征(也可使高层的梯度直接到下层,便于梯度训练); 但不做的Mikolov方法就只用了一层,效果也很好。
- 为什么要将每个词首尾相接? 目的是把当前词的前n-1个词表示成向量形式的输入,送到神经网络中,从而训练这n-1个词的词向量。同样,可以这么做,也可以不这么做。这么做了,实际上就是运用了语言模型的简单粗暴又直接;不这么做,同样也可以选择Matrix vector,或者卷积神经网络,或者RNN对每个词进行处理一下,一样也能达到目的。
- 为什么没有激活函数? 因为它不需要预测的精准,加入激活函数没什么实际的意义,反而会降低性能,下面的w2v同样也是这样。
word2vec
Word2Vec是NNLM的升级版,基本的网络结构很像,只是训练方法不太一样。主要有两种形式,通过这两种形式,最后得到每个词的词向量(副产物):
- 通过词汇去预测上下文–Skip-gram。
- 通过上下文来预测词汇–CBOW(Continuous Bag-of-Words)
###关于word2vec的理论细节推导强烈推荐《word2vec中的数学原理详解》,下面只整理一些个人的想法。
(w(t)代表当前词语位于句子的位置t,窗口大小为5,共同构成上下文)
首先这两者与NNLM模型的不同在于,一是没有NNLM的隐层(据说是因为隐层会使矩阵运算变很多),二是输入层单词经过映射矩阵得到的D维词向量累加而不是拼接构成映射层,三是使用了哈夫曼非线性。
CBOW,可以看成由当前词汇到预测其他相关词汇的多分类问题,但是对于最后的输出进行多分类softmax有千万级的词汇映射,显然不可能。所以对于CBOW的优化过程主要是Hierarchical Softmax,通过多个二叉树–二分类来曲线救国。具体的过程是先累加向量(向量的相加将变成路径),然后做n个逻辑回归来预测哈夫曼树(根据词频建立的,其实先DNN但是参数多了才哈夫曼的),这就是hierarchical softmax优化,以二叉树的思想来得到概率,最后所建立二叉树的每个非叶子节点相当于神经元。
Skig-Gram,逆转因果关系,其实是给定单词输出跟单词联系可能紧密的词。但是同样会面对权重矩阵太大的问题,所以引出了另一个优化为:
- 1常见单词或短语是一个词
- 2对词频繁度以一定采样率进行计算,使in the这种不会有太多信息量的词采样下降,同时低频词的采样率变大一些。
- 3不管SG还是CBOW本质都是分类问题,那么通过选择出更有代表性的负例来加速计算。即使用“负抽样”技术(Negative Sampling)来修改优化目标,使得每个训练样本只更新模型权重的一小部分。(比如如果训练一个生僻词需要走树的路径很久)(对于正例,即是训练出的那种,然后小数据集给5-25个负例,大数据集给2-5个,然后每次只更新有关词的权重矩阵的参数,采样还是以频率),负采样的训练是一个在小窗口里面的二元组对,这种方法不使用哈夫曼,只算逻辑回归来更新权重参数。
下图是具体流程:从左到右先是one-hot向量,乘center word的 W 找到词向量,乘另一个context word的矩阵 W’ 得到对每个词语的“相似度”(内积能代表相关性)以做到预测上下文其他词汇的效果,然后多个结果进行softmax得到概率,再与label对比计算损失。(显然这个矩阵会很大,所以具体实现需要结合上述的优化以达到近似的效果)
J
t
(
θ
)
=
log
σ
(
u
o
T
v
c
)
+
∑
i
=
1
k
E
j
∼
P
(
w
)
[
log
σ
(
−
u
j
T
v
c
)
]
J_t(\theta) = \log \sigma\left(u_o^Tv_c\right)+\sum_{i=1}^k\mathbb{E}_{j \sim P(w)}\left [\log\sigma \left(-u^T_jv_c\right) \right]
Jt(θ)=logσ(uoTvc)+i=1∑kEj∼P(w)[logσ(−ujTvc)]
t是窗口,k是采样个数。
词向量代表什么?
通过以上过程我们可以看到,最终的目标是学习到了一个矩阵,通过这个矩阵进行变换可以得到相应的向量。所以Embedding的目的其实是为了获得在更低维,更稠密,更连续的空间中也携带了语义信息的词表示,即便他降维了也能保留这种潜在关系。而词的维度,代表了向量中各个维度语义的多少。
为什么词嵌入能保留潜在关系?
实际上Embedding是在给定了大量的关联信息(是否上下文,是否共现等等)下,再最大似然得到参数,所以最后这些向量不但可以表示本身的信息,还能得到隐含的个体间的关系信息。
CBOW和Skip-gram如何选用?
根据mikolov的论文,skip-gram适合处理少量的数据,能很好的表示低频词,而CBOW学习速度快对高频词的表示要更好。实际应用中skip-gram会更常见一些。
哈夫曼树的作用
哈夫曼树的作用是来代替隐藏层和输出层的神经元,它的叶子节点起到输出层的作用,叶子的数量即为词汇表的大小,而内部节点是隐层神经元的作用。好处在于1计算量由V到logV 2哈夫曼树高频词更靠近树根,那么找到的时间会更少,符合贪心的需求。但是使用负采样之后不但可以提高训练速度,还可以改善词向量的质量,所以在实际中它使用的频率会更高。但是负采样的思想和哈夫曼还是很像的,都是把一个多分类的问题分解成二分类/正负对,而负采样使用二元逻辑回归可以让训练样本只更新补个部分的权重,这样大大减少了梯度下降过程中的计算量。
如何评价Embedding的质量?
- Relatedness。词和词之间相似度,即空间距离是否和人的直觉一样。
- Analogy。即词汇类比任务,看是否A-B=C-D,如king-queen=man-woman
- Categorization。看词在每个类别的概率。
- 聚类。看看聚类分布的效果。
#先分词,比如结巴分词
import jieba
f1 =open("test.txt")
f2 =open("retrun.txt", 'a')
lines =f1.readlines()
for line in lines:
line.replace('\t', '').replace('\n', '').replace(' ','')
seg_list = jieba.cut(line, cut_all=False)
f2.write(" ".join(seg_list))
f1.close()
#gensim,从文档中自动提取语义主题的nlp必备神库
from gensim.modelsimport word2vec
sentences =word2vec.Text8Corpus("retrun.txt")
#skip-gram,默认window=5
model =word2vec.Word2Vec(sentences, size=200)
print(model)
#1计算相似度
y1 = model.similarity(wordA, wordB)
#2计算某个词的相关词列表
y2 = model.most_similar(wordA, topn=20)
#3不合群的词
y4 =model.doesnt_match(words.split())
model.save()
负采样NEG过程
- 建立词典D,统计各个词的词频。采样原则是:词频高,采样作为负样本的概率就高。
- 设D的每个词对应一个长度 l ( w ) = c o u n t e r ( w ) / ∣ C ∣ l(w)=counter(w)/|C| l(w)=counter(w)/∣C∣ 的线段.
- 收尾相连线段,就变成了长度为1的线
- 负采样的时候就直接在线上打点
GloVe
- 基于计数的方法能有效地利用统计信息,但受限于捕捉词语相似度,也无法处理数据量很大的场景。
- 而NNLM, HLBL, RNN, Skip-gram/CBOW必须遍历所有的上下文窗口训练,无法有效利用单词的全局统计信息,缺乏了整体的词和词的关系,使用负样本采用sample的方式会缺失词的关系信息(且会出现高频率词采样权重高)
于是Glove开始结合两者,使用全局的信息(共线矩阵),也就是多个窗口进行更新。
J
(
θ
)
=
1
2
∑
i
,
j
=
1
W
f
(
P
i
j
)
(
u
i
T
v
j
−
log
P
i
j
)
2
J(\theta)=\frac{1}{2}\sum_{i,j=1}^W f(P_{ij})\left(u_i^Tv_j-\log P_{ij}\right)^2
J(θ)=21i,j=1∑Wf(Pij)(uiTvj−logPij)2
Pij是两个词共现的频次,f是一个max函数,小于某个值时如0.5:y=2x,此后恒为1。由于u 和v都能够捕捉到共现,所以X=U+V
Representation Learning
表示学习是深度学习领域的一个概念,是学习数据表示的技术的集合,用于将现实世界中的数据转化成能够被计算机高效处理的形式。与之相对应,在机器学习领域,数据表示主要通过特征工程(feature engineering)手动实现,依赖特征的选择,而在深度学习则是自动获取的,依赖大量的数据。而将词表示为一个向量,也属于表示学习。从概念上,它是指把一个维数为所有“词”数量的高维空间嵌入到一个维数低的连续向量空间中,现有的方法主要有:
- Local方法(symbolic representations),直接编码,即one-hot,这种方法每次都是对每一种分配独立的一个空间,得到的特征高度稀疏且互斥,不能表示属性间的关联。
- Distributed方法(distributed representations),将词放入句子进行理解,即word embedding,这种方法需要投影到一个统一的维度,如运用矩阵分解或者深度学习,得到的特征低维且稠密,单个向量没有没有意义,必须需要特征的组合,所以特征间存在关联性,参数更少。
Distributed方法自然有很多,主要基于Distributional hypothesis,即满足具有相同上下文的词语,应该具有相似的语义。
基于此的常规主要有以下几种方法:
- LSA(潜在语义分析)是一种Syntagmatic models(强调组合,相似的词会出现在同一个语境中),采用词-文档共现矩阵建模词语之间的组合关系,然后SVD(SVD分解为文档主题矩阵、主题重要性矩阵、主题词矩阵),根据主题的重要性截断矩阵,得到想要的词向量。“潜在”意欲“隐藏”,LSA实际上是尝试利用单词周围的上下文从文档中捕获隐藏的主题。
- GloVe是一种Paradigmatic models(强调替换,相似的词汇拥有相似的上下文而可以不同时出现),采用词-词共现矩阵建模词语之间的替代关系,优化损失函数。
- 主题模型(topic model)是以非监督学习的方式对文集的隐含语义结构(latent semantic structure)进行聚类的统计模型,主题模型被用于对文本的表征进行降维、按主题对文本进行聚类、以及根据用户偏好形成文本推荐系统等等。这些主题不过是相关单词的集群,每个文档可以有多个主题,以及它们在每个文档中的分布和各种单词的频率
- LDA,每个主题由多个词汇组成,而每个文档由多个主题组成,tf-idf 计算词频,词汇和主题的分布由Gibbs采样来获得,其降维的思想在于变成这种主题分布后,数量大大减少。
目前的词嵌入用深度学习方法会更加的普遍,如上图中的一些模型。
- Globe vs local。Globe如MF一次性将所有的数据输入模型,然后得到向量的表示结果。优点1考虑全局缺点1计算量大2不支持online,即如果有新数据,需要重新训练。Local如skipgram优点就在很灵活,缺点是无法顾到全局。而Glove是一种结合两者的方法,上面也总结过了。
- LM vs 非LM。LM基于马尔科夫进行训练,非LM就是不以词间的连乘概率来进行计算。
- 具体的其他嵌入方法总结在这里Pretraning in NLP。
softma的常数不变性
s
o
f
t
m
a
x
(
x
)
=
s
o
f
t
m
a
x
(
x
+
c
)
softmax(x) = softmax(x + c)
softmax(x)=softmax(x+c)
即softmax不受加入常数的影响:
(
s
o
f
t
m
a
x
(
x
+
c
)
)
i
=
e
x
i
+
c
∑
j
e
x
j
+
c
=
e
x
i
×
e
c
e
c
×
∑
j
e
x
j
=
e
x
i
×
e
c
e
c
×
∑
j
e
x
j
=
(
s
o
f
t
m
a
x
(
x
)
)
i
(softmax(x + c))_{i} = \frac{e^{x_{i} + c}}{\sum_{j} e^{x_{j} + c}} = \frac{e^{x_{i}} \times e^{c}}{e^{c} \times \sum_{j} e^{x_{j}}} \\ = \frac{e^{x_{i}} \times \cancel{e^{c}}}{\cancel{e^{c}} \times \sum_{j} e^{x_{j}}} = (softmax(x))_{i}
(softmax(x+c))i=∑jexj+cexi+c=ec×∑jexjexi×ec=ec
×∑jexjexi×ec
=(softmax(x))i
这种特性能在,即使两个数都很大比如 1000与 1001,但是其结果与 1和2的结果相同,那么在计算时只需要关注数字之间的差,而不是差占的比例。