python自然语言处理实战 | 中文分词技术学习笔记

这是对涂铭等老师撰写的《Python自然语言处理实战:核心技术与算法》的学习笔记。

逆向最大匹配分词

#逆向最大匹配
class IMM(object):
    def __init__(self, dic_path):
        self.dictionary = set()
        self.maximum = 0
        #读取词典
        with open(dic_path, 'r', encoding='utf8') as f:
            for line in f:
                line = line.strip()
                if not line:
                    continue
                self.dictionary.add(line)  # 从dic_path里面导入字典
#                 print(self.dictionary)
                if len(line) > self.maximum:
                    self.maximum = len(line)
    def cut(self, text):
        result = []
        index = len(text)
        while index > 0:
            word = None
            for size in range(self.maximum, 0, -1):
                if index - size < 0:
                    continue
                piece = text[(index - size):index]  # 逆序截取字 中国知网 -- 国知网 -- 知网 - 中国
#                 print(piece)
                if piece in self.dictionary:
                    word = piece
                    result.append(word)
                    index -= size
                    break
            if word is None:
                index -= 1
        return result[::-1]

def main():
    text = "中国知网"
    
    tokenizer = IMM('./data/imm_dic.utf8')
    print(tokenizer.cut(text))

main()

利用HMM模型分词

class HMM(object):
    def __init__(self):
        import os

        # 主要是用于存取算法中间结果,不用每次都训练模型
        self.model_file = './data/hmm_model.pkl'

        # 状态值集合
        self.state_list = ['B', 'M', 'E', 'S']
        # 参数加载,用于判断是否需要重新加载model_file
        self.load_para = False

    # 用于加载已计算的中间结果,当需要重新训练时,需初始化清空结果
    def try_load_model(self, trained):
        if trained:
            import pickle
            with open(self.model_file, 'rb') as f:
                self.A_dic = pickle.load(f)
                self.B_dic = pickle.load(f)
                self.Pi_dic = pickle.load(f)
                self.load_para = True

        else:
            # 状态转移概率(状态->状态的条件概率)
            self.A_dic = {}
            # 发射概率(状态->词语的条件概率)
            self.B_dic = {}
            # 状态的初始概率
            self.Pi_dic = {}
            self.load_para = False

    # 计算转移概率、发射概率以及初始概率
    def train(self, path):

        # 重置几个概率矩阵
        self.try_load_model(False)

        # 统计状态出现次数,求p(o)
        Count_dic = {}

        # 初始化参数
        def init_parameters():
            for state in self.state_list:
                self.A_dic[state] = {s: 0.0 for s in self.state_list}
                self.Pi_dic[state] = 0.0
                self.B_dic[state] = {}

                Count_dic[state] = 0

        def makeLabel(text):
            out_text = []
            if len(text) == 1:
                out_text.append('S')
            else:
                out_text += ['B'] + ['M'] * (len(text) - 2) + ['E']

            return out_text

        init_parameters()
        line_num = -1
        # 观察者集合,主要是字以及标点等
        words = set()
        with open(path, encoding='utf8') as f:
            for line in f:
                line_num += 1

                line = line.strip()
                if not line:
                    continue

                word_list = [i for i in line if i != ' ']
                words |= set(word_list)  # 更新字的集合

                linelist = line.split()

                line_state = []
                for w in linelist:
                    line_state.extend(makeLabel(w))
                
                assert len(word_list) == len(line_state)

                for k, v in enumerate(line_state):
                    Count_dic[v] += 1
                    if k == 0:
                        self.Pi_dic[v] += 1  # 每个句子的第一个字的状态,用于计算初始状态概率
                    else:
                        self.A_dic[line_state[k - 1]][v] += 1  # 计算转移概率
                        self.B_dic[line_state[k]][word_list[k]] = \
                            self.B_dic[line_state[k]].get(word_list[k], 0) + 1.0  # 计算发射概率
        
        self.Pi_dic = {k: v * 1.0 / line_num for k, v in self.Pi_dic.items()}
        self.A_dic = {k: {k1: v1 / Count_dic[k] for k1, v1 in v.items()}
                      for k, v in self.A_dic.items()}
        #加1平滑
        self.B_dic = {k: {k1: (v1 + 1) / Count_dic[k] for k1, v1 in v.items()}
                      for k, v in self.B_dic.items()}
        #序列化
        import pickle
        with open(self.model_file, 'wb') as f:
            pickle.dump(self.A_dic, f)
            pickle.dump(self.B_dic, f)
            pickle.dump(self.Pi_dic, f)

        return self

    def viterbi(self, text, states, start_p, trans_p, emit_p):
        V = [{}]
        path = {}
        for y in states:
            V[0][y] = start_p[y] * emit_p[y].get(text[0], 0)
            path[y] = [y]
        for t in range(1, len(text)):
            V.append({})
            newpath = {}
            
            #检验训练的发射概率矩阵中是否有该字
            neverSeen = text[t] not in emit_p['S'].keys() and \
                text[t] not in emit_p['M'].keys() and \
                text[t] not in emit_p['E'].keys() and \
                text[t] not in emit_p['B'].keys()
            for y in states:
                emitP = emit_p[y].get(text[t], 0) if not neverSeen else 1.0 #设置未知字单独成词
                (prob, state) = max(
                    [(V[t - 1][y0] * trans_p[y0].get(y, 0) *
                      emitP, y0)
                     for y0 in states if V[t - 1][y0] > 0])
                V[t][y] = prob
                newpath[y] = path[state] + [y]
            path = newpath
            
        if emit_p['M'].get(text[-1], 0)> emit_p['S'].get(text[-1], 0):
            (prob, state) = max([(V[len(text) - 1][y], y) for y in ('E','M')])
        else:
            (prob, state) = max([(V[len(text) - 1][y], y) for y in states])
        
        return (prob, path[state])

    def cut(self, text):
        import os
        if not self.load_para:
            self.try_load_model(os.path.exists(self.model_file))
        prob, pos_list = self.viterbi(text, self.state_list, self.Pi_dic, self.A_dic, self.B_dic)      
        begin, next = 0, 0    
        for i, char in enumerate(text):
            pos = pos_list[i]
            if pos == 'B':
                begin = i
            elif pos == 'E':
                yield text[begin: i+1]
                next = i+1
            elif pos == 'S':
                yield char
                next = i+1
        if next < len(text):
            yield text[next:]


hmm = HMM()
hmm.train('./data/trainCorpus.txt_utf8')

text = '希望我的运气很好'  # '这是一个非常棒的方案!'
res = hmm.cut(text)
print(text)
print(str(list(res)))

利用jieba分词、词性标注、提取关键词

import jieba

sent = '中文分词是文本处理不可或缺的一步!'

seg_list = jieba.cut(sent, cut_all=True)

print('全模式:', '/ ' .join(seg_list)) 

seg_list = jieba.cut(sent, cut_all=False)
print('精确模式:', '/ '.join(seg_list)) 

seg_list = jieba.cut(sent)    # 通常使用默认精确模式
print('默认精确模式:', '/ '.join(seg_list))

seg_list = jieba.cut_for_search(sent)  
print('搜索引擎模式', '/ '.join(seg_list))
import jieba
sent = '好丑的证件照片'
print('/ '.join(jieba.cut(sent, HMM=False)))

jieba.suggest_freq(('证件照片'), True)
print('/ '.join(jieba.cut(sent, HMM=False)))
  • jieba分词示例
#jieba分词示例
def get_content(path):
    
    with open(path, 'r', encoding='gbk', errors='ignore') as f:
        content = ''
        for l in f:
            l = l.strip()
            content += l
        return content


def get_TF(words, topK=10):
    
    tf_dic = {}
    for w in words:
        tf_dic[w] = tf_dic.get(w, 0) + 1
    return sorted(tf_dic.items(), key = lambda x: x[1], reverse=True)[:topK]

def stop_words(path):
    with open(path,encoding='UTF-8') as f:
        return [l.strip() for l in f]
stop_words('./data/stop_words.utf8')

#分词
def main():
    import glob
    import random
    import jieba
    
    files = glob.glob('./data/news/C000013/*.txt')
    corpus = [get_content(x) for x in files[:5]]
    
    
    sample_inx = random.randint(0, len(corpus))
    sample_inx = 3
    
    import jieba.posseg as psg
    
    split_words = [x for x in jieba.cut(corpus[sample_inx]) if x not in stop_words('./data/stop_words.utf8')]  # 去除常用停用词
    print('样本之一:'+corpus[sample_inx])
    print('样本分词效果:'+'/ '.join(split_words))
    print('样本的topK(10)词:'+str(get_TF(split_words)))
main()
  • jieba词性标注示例
# 词性列表

1. 名词 (1个一类,7个二类,5个三类)
n 名词
nr 人名
nr1 汉语姓氏
nr2 汉语名字
nrj 日语人名
nrf 音译人名
ns 地名
nsf 音译地名
nt 机构团体名
nz 其它专名
nl 名词性惯用语
ng 名词性语素

2. 时间词(1个一类,1个二类)
t 时间词
tg 时间词性语素

3. 处所词(1个一类)
s 处所词 (家中、门外、境内、西方……)

4. 方位词(1个一类)
f 方位词

5. 动词(1个一类,9个二类)
v 动词
vd 副动词
vn 名动词
vshi 动词“是”
vyou 动词“有”
vf 趋向动词
vx 形式动词
vi 不及物动词(内动词)
vl 动词性惯用语
vg 动词性语素

6. 形容词(1个一类,4个二类)
a 形容词
ad 副形词
an 名形词
ag 形容词性语素
al 形容词性惯用语

7. 区别词(1个一类,2个二类)
b 区别词 (主要、整个、所有……)
bl 区别词性惯用语

8. 状态词(1个一类)
z 状态词

9. 代词(1个一类,4个二类,6个三类)
r 代词
rr 人称代词
rz 指示代词
rzt 时间指示代词
rzs 处所指示代词
rzv 谓词性指示代词
ry 疑问代词
ryt 时间疑问代词
rys 处所疑问代词
ryv 谓词性疑问代词
rg 代词性语素

10. 数词(1个一类,1个二类)
m 数词
mq 数量词

11. 量词(1个一类,2个二类)
q 量词
qv 动量词
qt 时量词

12. 副词(1个一类)
d 副词

13. 介词(1个一类,2个二类)
p 介词
pba 介词“把”
pbei 介词“被”

14. 连词(1个一类,1个二类)
c 连词
cc 并列连词

15. 助词(1个一类,15个二类)
u 助词
uzhe 着
ule 了 喽
uguo 过
ude1 的 底
ude2 地
ude3 得
usuo 所
udeng 等 等等 云云
uyy 一样 一般 似的 般
udh 的话
uls 来讲 来说 而言 说来
uzhi 之
ulian 连 (“连小学生都会”)

16. 叹词(1个一类)
e 叹词

17. 语气词(1个一类)
y 语气词(delete yg)

18. 拟声词(1个一类)
o 拟声词

19. 前缀(1个一类)
h 前缀

20. 后缀(1个一类)
k 后缀

21. 字符串(1个一类,2个二类)
x 字符串
xx 非语素字
xu 网址URL

22. 标点符号(1个一类,16个二类)
w 标点符号
wkz 左括号,全角:( 〔 [ { 《 【 〖 〈 半角:( [ { <
wky 右括号,全角:) 〕 ] } 》 】 〗 〉 半角: ) ] { >
wyz 左引号,全角:“ ‘ 『
wyy 右引号,全角:” ’ 』
wj 句号,全角:。
ww 问号,全角:? 半角:?
wt 叹号,全角:! 半角:!
wd 逗号,全角:, 半角:,
wf 分号,全角:; 半角: ;
wn 顿号,全角:、
wm 冒号,全角:: 半角: :
ws 省略号,全角:…… …
wp 破折号,全角:—— -- ——- 半角:--- ----
wb 百分号千分号,全角:% ‰ 半角:%
wh 单位符号,全角:¥ $ £ ° ℃ 半角:$
import jieba.posseg as psg

sent = '中文分词是文本处理不可或缺的一步!'

seg_list = psg.cut(sent)

print(' '.join(['{0}/{1}'.format(w, t) for w, t in seg_list]))

# 中文/nz 分词/n 是/v 文本处理/n 不可或缺/l 的/uj 一步/m !/x
  • jieba自定义词典
import jieba 
#加载系统词典
jieba.set_dictionary('./data/dict.txt.big')

print('自定义词典内容:')
with open('./data/user_dict.utf8', 'r',encoding='UTF-8') as f:
    for l in f:
        print(l)

print('------华丽的分割线-------')
sent = 'jieba分词非常好用,可以自定义金融词典!'
seg_list = jieba.cut(sent)
print('加载词典前:', '/ '.join(seg_list))

jieba.load_userdict('./data/user_dict.utf8')
seg_list = jieba.cut(sent)
print('加载词典后:', '/ '.join(seg_list))
  • jieba关键词提取
import jieba.analyse as aly

content = '''
自然语言处理(NLP)是计算机科学,人工智能,语言学关注计算机和人类(自然)语言之间的相互作用的领域。
因此,自然语言处理是与人机交互的领域有关的。在自然语言处理面临很多挑战,包括自然语言理解,因此,自然语言处理涉及人机交互的面积。
在NLP诸多挑战涉及自然语言理解,即计算机源于人为或自然语言输入的意思,和其他涉及到自然语言生成。
'''

#加载自定义idf词典
aly.set_idf_path('./data/idf.txt.big')
#加载停用词典
aly.set_stop_words('./data/stop_words.utf8')

# 第一个参数:待提取关键词的文本
# 第二个参数:返回关键词的数量,重要性从高到低排序
# 第三个参数:是否同时返回每个关键词的权重
# 第四个参数:词性过滤,为空表示不过滤,若提供则仅返回符合词性要求的关键词
keywords = aly.extract_tags(content, topK=10, withWeight=True, allowPOS=())

for item in keywords:
    # 分别为关键词和相应的权重
    print(item[0], item[1])
import jieba.analyse as aly

content = '''
交战双方,一边是长期被冠以“韭菜”之名的散户,一边是久经沙场、擅长收割“韭菜”的华尔街“地主”——投资机构。战斗围绕着一家叫做“游戏驿站”(GameStop)的公司股票展开。
'''
# 第一个参数:待提取关键词的文本
# 第二个参数:返回关键词的数量,重要性从高到低排序
# 第三个参数:是否同时返回每个关键词的权重
# 第四个参数:词性过滤,为空表示过滤所有,与TF—IDF不一样!
keywords = jieba.analyse.textrank(content, topK=10, withWeight=True, allowPOS=('ns', 'n', 'vn', 'v'))
for item in keywords:
    # 分别为关键词和相应的权重
    print(item[0], item[1])

总结

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值