【NLP实践-Task2 特征提取】文本处理&语言模型&文本矩阵化

目录

基本文本处理技能

分词的概念

正向最大匹配法

逆向最大匹配法

双向最大匹配法

词、字符频率统计

语言模型

语言模型中unigram、bigram、trigram的概念

文本矩阵化

分词

新词识别

自定义词典

关键词提取

去除停用词

构造词表

文档向量化

参考


基本文本处理技能

       目前有三大主流分词方法:基于字符串匹配的分词方法、基于理解的分词方法和基于统计的分词方法。

       在实际运用中常常将字符串匹配分词和统计分词结合使用,这样既体现了匹配分词速度快、效率高的优点,同时又能运用统计分词识别生词、自动消除歧义等方面的特点。

分词的概念

分词算法设计的基本原则

  1. 颗粒度越大越好:用于进行语义分析的文本分词,要求分词结果的颗粒度越大,即单词的字数越多,所能表示的含义越确切,如:“公安局长”可以分为“公安 局长”、“公安局 长”、“公安局长”都算对,但是要用于语义分析,则“公安局长”的分词结果最好(当然前提是所使用的词典中有这个词) 
  2. 切分结果中非词典词越少越好,单字字典词数越少越好,这里的“非词典词”就是不包含在词典中的单字,而“单字字典词”指的是可以独立运用的单字,如“的”、“了”、“和”、“你”、“我”、“他”。例如:“技术和服务”,可以分为“技术 和服 务”以及“技术 和 服务”,但“务”字无法独立成词(即词典中没有),但“和”字可以单独成词(词典中要包含),因此“技术 和服 务”有1个非词典词,而“技术 和 服务”有0个非词典词,因此选用后者。
  3. 总体词数越少越好,在相同字数的情况下,总词数越少,说明语义单元越少,那么相对的单个语义单元的权重会越大,因此准确性会越高。

最大匹配法

       最大匹配是指以词典为依据,取词典中最长单词为第一个次取字数量的扫描串,在词典中进行扫描(为提升扫描效率,还可以跟据字数多少设计多个字典,然后根据字数分别从不同字典中进行扫描)。

       例如:词典中最长词为“中华人民共和国”共7个汉字,则最大匹配起始字数为7个汉字。然后逐字递减,在对应的词典中进行查找。 

正向最大匹配法

正向即从前往后取词,从7->1,每次减一个字,直到词典命中或剩下1个单字。 

举例:“我们在野生动物园玩”。

第1轮扫描: 

第1次:“我们在野生动物”,扫描7字词典,无 
第2次:“我们在野生动”,扫描6字词典,无 
。。。。 
第6次:“我们”,扫描2字词典,有 
扫描中止,输出第1个词为“我们”,去除第1个词后开始第2轮扫描,即: 

 

第2轮扫描: 

第1次:“在野生动物园玩”,扫描7字词典,无 
第2次:“在野生动物园”,扫描6字词典,无 
。。。。 
第6次:“在野”,扫描2字词典,有 
扫描中止,输出第2个词为“在野”,去除第2个词后开始第3轮扫描,即: 

 

第3轮扫描: 

第1次:“生动物园玩”,扫描5字词典,无 
第2次:“生动物园”,扫描4字词典,无 
第3次:“生动物”,扫描3字词典,无 
第4次:“生动”,扫描2字词典,有 
扫描中止,输出第3个词为“生动”,第4轮扫描,即: 

 

第4轮扫描: 

第1次:“物园玩”,扫描3字词典,无 
第2次:“物园”,扫描2字词典,无 
第3次:“物”,扫描1字词典,无 
扫描中止,输出第4个词为“物”,非字典词数加1,开始第5轮扫描,即: 

 

第5轮扫描: 

第1次:“园玩”,扫描2字词典,无 
第2次:“园”,扫描1字词典,有 
扫描中止,输出第5个词为“园”,单字字典词数加1,开始第6轮扫描,即: 

 

第6轮扫描: 

第1次:“玩”,扫描1字字典词,有 
扫描中止,输出第6个词为“玩”,单字字典词数加1,整体扫描结束。 

正向最大匹配法,最终切分结果为:“我们/在野/生动/物/园/玩”,其中,单字字典词为2,非词典词为1。

逆向最大匹配法

逆向即从后往前取词,其他逻辑和正向相同。

举例:“我们在野生动物园玩”。

第1轮扫描:“在野生动物园玩” 

第1次:“在野生动物园玩”,扫描7字词典,无 
第2次:“野生动物园玩”,扫描6字词典,无 
。。。。 
第7次:“玩”,扫描1字词典,有 
扫描中止,输出“玩”,单字字典词加1,开始第2轮扫描 

 

第2轮扫描:“们在野生动物园” 

第1次:“们在野生动物园”,扫描7字词典,无 
第2次:“在野生动物园”,扫描6字词典,无 
第3次:“野生动物园”,扫描5字词典,有 
扫描中止,输出“野生动物园”,开始第3轮扫描 

 

第3轮扫描:“我们在” 

第1次:“我们在”,扫描3字词典,无 
第2次:“们在”,扫描2字词典,无 
第3次:“在”,扫描1字词典,有 
扫描中止,输出“在”,单字字典词加1,开始第4轮扫描 

 

第4轮扫描:“我们” 

第1次:“我们”,扫描2字词典,有 
扫描中止,输出“我们”,整体扫描结束。 

逆向最大匹配法,最终切分结果为:“我们/在/野生动物园/玩”,其中,单字字典词为2,非词典词为0。 

双向最大匹配法

正向最大匹配法和逆向最大匹配法,都有其局限性,因此有人又提出了双向最大匹配法,双向最大匹配法。即,两种算法都切一遍,然后根据大颗粒度词越多越好,非词典词和单字词越少越好的原则,选取其中一种分词结果输出。 

举例:“我们在野生动物园玩” 

正向最大匹配法,最终切分结果为:“我们/在野/生动/物/园/玩”,其中,两字词3个,单字字典词为2,非词典词为1。 
逆向最大匹配法,最终切分结果为:“我们/在/野生动物园/玩”,其中,五字词1个,两字词1个,单字字典词为2,非词典词为0。 
非字典词:正向(1)>逆向(0)(越少越好) 
单字字典词:正向(2)=逆向(2)(越少越好) 
总词数:正向(6)>逆向(4)(越少越好) 

因此最终输出为逆向结果。

词、字符频率统计

使用Python中的collections.Counter模块。

在程序所在路径下创建2个txt文件,文件内容分别为hello python goodbye python 和 i like python。

运行程序

#coding=utf-8
import os
from collections import Counter
sumsdata=[]
for fname in os.listdir(os.getcwd()):
    if os.path.isfile(fname) and fname.endswith('.txt'):
        with open(fname,'r') as fp:
            data=fp.readlines()
            fp.close()
        sumsdata+=[line.strip().lower() for line in data]
cnt=Counter()
for word in sumsdata:
    cnt[word]+=1
cnt=dict(cnt)
for key,value in cnt.items():
    print(key+":"+str(value))

得到以下结果:

语言模型

       统计语言模型是一个单词序列上的概率分布,对于一个给定长度为m的序列,它可以为整个序列产生一个概率 P(w_1,w_2,…,w_m) 。其实就是想办法找到一个概率分布,它可以表示任意一个句子或序列出现的概率。

       目前在自然语言处理相关应用非常广泛,如语音识别(speech recognition) , 机器翻译(machine translation), 词性标注(part-of-speech tagging), 句法分析(parsing)等。传统方法主要是基于统计学模型,最近几年基于神经网络的语言模型也越来越成熟。

       常见的方法有n-gram模型方法、决策树方法、最大熵模型方法、最大熵马尔科夫模型方法、条件随机域方法、神经网络方法,等等。

语言模型中unigram、bigram、trigram的概念

        为了解决自由参数数目过多的问题,引入了马尔科夫假设:随意一个词出现的概率只与它前面出现的有限的n个词有关。基于上述假设的统计语言模型被称为N-gram语言模型。

  1. 当n=1时,即一个词的出现与它周围的词是独立,这种我们称为unigram,也就是一元语言模型,此时自由参数量级是词典大小V。
  2. 当n=2时,即一个词的出现仅与它前面的一个词有关时,这种我们称为bigram,叫二元语言模型,也叫一阶马尔科夫链,此时自由参数数量级是V^2。
  3. 当n=3时,即一个词的出现仅与它前面的两个词有关,称为trigram,叫三元语言模型,也叫二阶马尔科夫链,此时自由参数数量级是V^3。

       n-gram模型的参数就是条件概率P(Wi|Wi-n+1,...,Wi-1)。自由参数的数量级是n取值的指数倍,假设词表的大小为100,000,那么n-gram模型的参数数量为100,000^n。

       从模型的效果来看,理论上n的取值越大,效果越好。但随着n取值的增加,效果提升的幅度是在下降的。同时还涉及到一个可靠性和可区别性的问题,参数越多,可区别性越好,但同时单个参数的实例变少从而降低了可靠性。

       最常用的是bigram,其次是unigram和trigram,n取≥4的情况较少。

文本矩阵化

分词

jieba安装:pip install jieba

结巴中文分词涉及到的算法:
(1) 基于Trie树结构实现高效的词图扫描,生成句子中汉字所有可能成词情况所构成的有向无环图(DAG); 
(2) 采用了动态规划查找最大概率路径, 找出基于词频的最大切分组合; 
(3) 对于未登录词,采用了基于汉字成词能力的HMM模型,使用了Viterbi算法。

结巴中文分词支持的3种分词模式: 

(1) 精确模式:试图将句子最精确地切开,适合文本分析; 
(2) 全模式:把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义问题; 
(3) 搜索引擎模式:在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。

import jieba

# 全模式
text = "我来到北京清华大学"
seg_list = jieba.cut(text, cut_all=True)
print(u"[全模式]: ", "/ ".join(seg_list))

# 精确模式
seg_list = jieba.cut(text, cut_all=False)
print(u"[精确模式]: ", "/ ".join(seg_list))

# 默认是精确模式
seg_list = jieba.cut(text)
print(u"[默认模式]: ", "/ ".join(seg_list))

# 搜索引擎模式
seg_list = jieba.cut_for_search(text)
print(u"[搜索引擎模式]: ", "/ ".join(seg_list))

新词识别

import jieba

#新词识别  “杭研”并没有在词典中,但是也被Viterbi算法识别出来了
seg_list = jieba.cut("他来到了网易杭研大厦")
print (u"[新词识别]: ", "/ ".join(seg_list))

自定义词典

import jieba

text = "故宫的著名景点包括乾清宫、太和殿和黄琉璃瓦等"

# 全模式
seg_list = jieba.cut(text, cut_all=True)
print(u"[全模式]: ", "/ ".join(seg_list))

# 精确模式
seg_list = jieba.cut(text, cut_all=False)
print(u"[精确模式]: ", "/ ".join(seg_list))

# 搜索引擎模式
seg_list = jieba.cut_for_search(text)
print(u"[搜索引擎模式]: ", "/ ".join(seg_list))

       结果显示,jieba认出了专有名词”太和殿”,但没有认出”乾清宫”和”黄琉璃瓦”。 也就是说,专有名词”乾清宫”和”黄琉璃瓦”可能因分词而分开,这也是很多分词工具的一个缺陷。 

       为此,jieba分词支持开发者使用自定定义的词典,以便包含jieba词库里没有的词语。虽然结巴有新词识别能力,但自行添加新词可以保证更高的正确率,尤其是专有名词。 

jieba.load_userdict(file_name) #file_name为自定义词典的路径

词典格式和dict.txt一样,一个词占一行; 
每一行分三部分,第一部分为词语,中间部分为词频,最后部分为词性(可省略,ns为地点名词),用空格隔开。

咱们在jieba的安装目录下添加mydict.txt(用sublime打开进行编辑,否则可能出现字符报错),内容为:

乾清宫 1 n
黄琉璃瓦 1 n

更新代码,主要是添加加载mydict.txt的代码:

import jieba

jieba.load_userdict('F:\system\Anaconda3\Lib\site-packages\jieba\mydict.txt')

text = "故宫的著名景点包括乾清宫、太和殿和黄琉璃瓦等"

# 全模式
seg_list = jieba.cut(text, cut_all=True)
print(u"[全模式]: ", "/ ".join(seg_list))

# 精确模式
seg_list = jieba.cut(text, cut_all=False)
print(u"[精确模式]: ", "/ ".join(seg_list))

# 搜索引擎模式
seg_list = jieba.cut_for_search(text)
print(u"[搜索引擎模式]: ", "/ ".join(seg_list))

可以看到,新添加的两个专有名词已经被结巴分词工具辨别出来了。

关键词提取

import jieba
import jieba.analyse

#导入自定义词典
jieba.load_userdict('F:\system\Anaconda3\Lib\site-packages\jieba\mydict.txt')

#精确模式
text = "故宫的著名景点包括乾清宫、太和殿和午门等。其中乾清宫非常精美,午门是紫禁城的正门,午门居中向阳。"
seg_list = jieba.cut(text, cut_all=False)
print (u"分词结果:")
print ("/".join(seg_list))

#获取关键词
tags = jieba.analyse.extract_tags(text, topK=5)
print (u"关键词:")
print (" ".join(tags))

       这里“午门”出现了3次,所以先输出。“乾清宫”出现了2次,第二个输出。 
       其他词都出现1次,那么为什么接下来输出的是“著名景点”、“太和殿”和“向阳”呢? 这是因为,在词频一样的前题下,根据TF/IDF的顺序来输出,因为其他词的TF一样(都是1),所以IDF小的会先输出来。 
       不知道结巴分词是根据什么来判断IDF的,假如是根据dict.txt中的第二列词频来判断,那么确实是“著名景点” < “太阳殿” < “向阳” < 其他词语。这样第三个输出的就是“著名景点”,最后两个依次为“太阳殿”和“向阳”。

去除停用词

       在信息检索中,为节省存储空间和提高搜索效率,在处理自然语言数据(或文本)之前或之后会自动过滤掉某些字或词,比如“的”、“是”、“而且”、“但是”、”非常“等。这些字或词即被称为Stop Words(停用词)。

import jieba

#导入自定义词典
jieba.load_userdict('F:\system\Anaconda3\Lib\site-packages\jieba\mydict.txt')

# 去除停用词
stopwords = {}.fromkeys(['的', '包括', '等', '是'])
text = "故宫的著名景点包括乾清宫、太和殿和午门等。其中乾清宫非常精美,午门是紫禁城的正门。"
# 精确模式
segs = jieba.cut(text, cut_all=False)
final = ''
for seg in segs:
    if seg not in stopwords:
            final += seg
print (final)

seg_list = jieba.cut(final, cut_all=False)
print ("/ ".join(seg_list))

构造词表

def build_vocab(train_dir, vocab_dir, vocab_size=5000):
    """根据训练集构建词汇表,存储"""
    data_train, _ = read_file(train_dir)

    all_data = []
    for content in data_train:
        all_data.extend(content)

    counter = Counter(all_data)
    count_pairs = counter.most_common(vocab_size - 1)
    words, _ = list(zip(*count_pairs))
    # 添加一个 <PAD> 来将所有文本pad为同一长度
    words = ['<PAD>'] + list(words)
    open_file(vocab_dir, mode='w').write('\n'.join(words) + '\n')

文档向量化

import jieba
import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer
 
 
# 读取停用词
def read_stopword(filename):
    stopword = []
    fp = open(filename, 'r')
    for line in fp.readlines():
        stopword.append(line.replace('\n', ''))
    fp.close()
    return stopword


# 切分数据,并删除停用词
def cut_data(data, stopword):
    words = []
    for content in data['content']:
        word = list(jieba.cut(content))
        for w in list(set(word) & set(stopword)):
            while w in word:
                word.remove(w)
        words.append(' '.join(word))
    data['content'] = words
    return data
 
 
# 获取单词列表
def word_list(data):
    all_word = []
    for word in data['content']:
        all_word.extend(word)
    all_word = list(set(all_word))
    return all_word
 

# 计算文本向量
def text_vec(data):
    count_vec = CountVectorizer(max_features=300, min_df=2)
    count_vec.fit_transform(data['content'])
    fea_vec = count_vec.transform(data['content']).toarray()
    return fea_vec
 
 
if __name__ == '__main__':
    data = pd.read_csv('./cnews/test.txt', names=['title', 'content'], sep='\t')  # (10000, 2)
 
    stopword = read_stopword('./cnews/stopword.txt')
    data = cut_data(data, stopword)
 
    fea_vec = text_vec(data)
    print(fea_vec)
 

参考

中文分词法https://blog.csdn.net/sysu63/article/details/80185555

中文分词 正向最大匹配法 逆向最大匹配法 双向最大匹配法http://blog.sina.com.cn/s/blog_53daccf401011t74.html

python实现统计文本中单词出现的频率https://blog.csdn.net/ls_6468/article/details/78519958

语言模型的基本概念https://www.cnblogs.com/Dream-Fish/p/3963028.html

自然语言处理之语言模型(LM)https://blog.csdn.net/qq_36330643/article/details/80143960

结巴中文分词介绍https://blog.csdn.net/haishu_zheng/article/details/80430106

NLP实践-Task2https://blog.csdn.net/zh11403070219/article/details/88169237

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值