NLP学习笔记:词性标注

任务目标:通过已有的训练数据,将每个单词的词性标记出来。

知识储备:

  1.计算语言模型

    思路:假设每句话经过分词表示为  (w1,w2,w3,... wi  ) 对应的每个单词的词性记为(z1,z2,z3,... zi)

          求 则语言模型 z =  P(w1,w2,w3,... wi | z1,z2,z3,... zi)* P (z1,z2,z3,... zi)

               =   [ P(wi |zi)(i从0到n的连乘)] * P(z1)* [ P(zt | z t-1)(t从2到n的连乘)]

               =  取各个数的 log 值方便计算。

       假定  :训练样本的单词数为 M  ,词性种类为 N

       记 pi = log (P(z1))  表示词性 tag 出现在句子开头的概率  长度为N的数组

            A =  [ P(wi |zi)(i从0到n的连乘)]     表示单词wi为词性zi的概率  表示 N*M的矩阵

         B =  [ P(zt | z t-1)(t从2到n的连乘)]  表示词性  zt-1 的下一个词的词性为 zt 的概率  N*N的矩阵

  2.维特比算法

    第一步已经计算出了各个单词与词性的关系,下一步就需要根绝维特比算法计算出最优解。    (动态规划的思想)

    找规律,假定  s(n)= P(z1) +P (w1|z1)

              +P(z2|z1)+P(w2|z2)

              +P(z3|z2)+P(w3|z3)

              + ......

              +P(zn|zn-1)+P(wn|zn)

    根据上述关系式,可以定义数组 dp[i][j]  来表示 第 i  个单词的词性为 第j个tag的概率

              定义数组 dp[i][j] = dp[i-1][k] + A[][] + B[][]

    思路大概是这样的接下来实现即可

已有的训练数据的样本格式为:

 

目前假定 遇到  .  即判定为一句话的结束,这样方便计算 pi 值

tag2id,id2tag = {},{}   #两个map ,用来记录位置和词性的关系  ,可以通过位置找到词性,也可通过词性定位置
word2id,id2word = {},{} #同上,记录单词与位置的关系。
for line in open("data/traindata.txt"):
    items = line.split("/")
    word , tag = items[0],items[1].rstrip()

    #如果单词未在map中,则加入。
    if word not in word2id:
        word2id[word] = len(id2word)
        id2word[len(id2word)] = word
    #tag不在,则加入
    if tag not in tag2id:
        tag2id[tag] = len(id2tag)
        id2tag[len(id2tag)] = tag
M = len(id2word)  #词典长度
N = len(id2tag)   #词性种类个数
print(M,N)

#构建需要的矩阵 ,pi ,A , B  A 是N*M 的矩阵
#计算使用的数学公式 Z = A + pi + B
#  A为N*M的矩阵,表示 A[i][j] 表示 tag i 出现单词 j 的概率。    pi 表示 每种词性出现在句子开头的概率   B 表示 N*N  B[i][j] 之前状态是 i 转换成 j  的概率
import numpy as np
pi = np.zeros(N)   #每种词性,出现在句子开头的概率
A = np.zeros((N,M))
B = np.zeros((N,N))

prev_tag = ""
for line in open("data/traindata.txt"):
    items = line.split("/")
    wordId ,tagId = word2id[items[0]] , tag2id[items[1].rstrip()]
    if prev_tag == "": #表示句子开始
        pi[tagId] +=1
        A[tagId][wordId] +=1
    else: #不是句子开始
        A[tagId][wordId] += 1
        B[tag2id[prev_tag]][tagId] +=1

    if items[0] == ".":
        prev_tag = ""
    else:
        prev_tag = items[1].rstrip()

pi = pi/sum(pi)
for i in range(N):
    A[i] /= sum(A[i])
    B[i] /= sum(B[i])

def log(v):
    if v ==0:
        return np.log(0.000001)
    else:
        return np.log(v)


def vitebi(x,pi,A,B):
    """
    :param x:
    :param pi:
    :param A: 单词概率
    :param B: tag状态转移概率
    :return:
    """
    X = [word2id[word] for word in x.split(" ")]
    T = len(X)
    dp = np.zeros((T,N))  #dp[i][j] 表示 wi 词性是第j 个tag 的概率
    ptr = np.array([[0 for x in range(N)] for y in range(T)])
    for j in range(N):
        dp[0][j] = log(pi[j]) + log(A[j][X[0]])

    for i in range(1,T):
        for j in range(N):
            dp[i][j] = -9999
            for k in range(N):
                score =dp[i-1][k] + log(A[j][X[i]]) + log(B[k][j])
                if score>dp[i][j]:
                    dp[i][j] = score
                    ptr[i][j] = k
    #输出结果
    best_seq = [0]*T
    #找最后一个单词的词性
    best_seq[T-1] = np.argmax(dp[T-1])
    #从后到前的循环,找每个单词的词性
    for i in range(T-2,-1,-1):
        best_seq[i] = ptr[i + 1][best_seq[i + 1]]
    for i in range(len(best_seq)):
        print(id2tag[best_seq[i]])

mystr = "Social Security number , passport number and details about the services provided for the payment"
print(vitebi(mystr,pi,A,B))

 

posted @ 2020-08-27 11:40  星际毁灭  阅读(406)  评论(0编辑  收藏  举报