【NLP实战】Task2:特征处理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012736685/article/details/88206485

一、基本文本处理技能

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

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

0、分词

分词算法设计的基本原则:

  • 颗粒度越大越好。用于进行语义分析的文本分词,单词的字数越多,所能表示的含义越确切。eg: “公安局长”可以分为“公安 局长”、“公安局 长”、“公安局长”都算对,但是要用于语义分析,则“公安局长”的分词结果最好.
  • 切分结果中非词典词越少越好,单字字典词数越少越好。“非词典词”就是不包含在词典中的单字,而“单字字典词”指的是可以独立运用的单字。eg:“技术和服务”可切分为“技术 和服 务”,但“务”字无法独立成词,有1个非词典词;“技术 和 服务”其中“和”字可以单独成词(词典中要包含)有0个非词典词,因此选用后者。
  • 总体词数越少越好,在相同字数的情况下,总词数越少。

1、分词匹配方法

最大匹配法:最大匹配是指以词典为依据,取词典中最长单词为第一个次取字数量的扫描串,在词典中进行扫描(为提升扫描效率,还可以跟据字数多少设计多个字典,然后根据字数分别从不同字典中进行扫描。下面以“我们在野生动物园玩”详细说明一下这几种匹配方法:

(1)正向最大匹配法

正向即从前往后取词,从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。

(2)反向最大匹配法

逆向即从后往前取词,其他逻辑和正向相同。即:
第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)双向最大匹配法

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

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

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

非字典词:正向(1)>逆向(0)(越少越好)
单字字典词:正向(2)=逆向(2)(越少越好)
总词数:正向(6)>逆向(4)(越少越好)
因此最终输出为逆向结果。

2、词、字符频率统计

python中 collections.Counter 模块
新建两个txt文件,其内容分别为:

word1.txt

hello
python
goodbye
python

word2.txt

i
like
python
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))

输出结果

i:1
like:1
python:3
hello:1
goodbye:1

二、语言模型

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

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

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

1、n-gram模型:unigram、bigram、trigram

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

当n取1、2、3时,n-gram模型分别称为unigram、bigram、trigram语言模型

  • unigram 一元分词,把句子分成一个一个的汉字
  • bigram 二元分词,把句子从头到尾每两个字组成一个词语,也叫一阶马尔科夫链
  • trigram 三元分词,把句子从头到尾每三个字组成一个词语,也叫二阶马尔科夫链

2、文本矩阵化

过程:
加载数据集->分词->生成词汇表->生成word_index->加载预训练词向量模型->生成词向量矩阵

(1)分词——jieba

jieba:https://github.com/fxsjy/jieba

三种分词模式:

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

输出结果

[全模式]:  我/ 来到/ 北京/ 清华/ 清华大学/ 华大/ 大学
[精确模式]:  我/ 来到/ 北京/ 清华大学
[默认模式]:  我/ 来到/ 北京/ 清华大学
[搜索引擎模式]:  我/ 来到/ 北京/ 清华/ 华大/ 大学/ 清华大学

(2)新词识别

新词识别: “杭研”并没有在词典中,但是也被 Viterbi 算法识别出来了

import jieba

seg_list = jieba.cut("他来到了网易杭研大厦")
print(u"[新词识别]: ", "/ ".join(seg_list))

(3)自定义词典

自定义词典,以便包含 jieba 词库中没有的词语(尤其是专有名称),以提升正确率。

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认出了专有名词”太和殿”,但没有认出”乾清宫”和”黄琉璃瓦”。

设置自定义字典
每一行分三部分,第一部分为词语,中间部分为词频,最后部分为词性(可省略,ns为地点名词),用空格隔开。如下所示。

乾清宫 1 n
黄琉璃瓦 1 n

改进

import jieba

# 设置并加载自定义字典
filename = './mydict.txt'
jieba.load_userdict(filename)

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))

输出结果:新添加的两个专有名词已经被结巴分词工具辨别出来了。

[全模式: ] 故宫/ 的/ 著名/ 著名景点/ 景点/ 包括/ 乾清宫/ 清宫/ / / 太和/ 太和殿/ 和/ 黄琉璃瓦/ 琉璃/ 琉璃瓦/ 等
[精确模式: ] 故宫/的/著名景点/包括/乾清宫/、/太和殿/和/黄琉璃瓦/等
[搜索引擎模式: ] 故宫/的/著名/景点/著名景点/包括/清宫/乾清宫/、/太和/太和殿/和/琉璃/琉璃瓦/黄琉璃瓦/等

(4)关键词

jieba.analyse.extract_tags(text, topK=)

import jieba
import jieba.analyse

filename = './mydict.txt'
jieba.load_userdict(filename)

text = "故宫的著名景点包括乾清宫、太和殿和午门等。其中乾清宫非常精美,午门是紫禁城的正门,午门居中向阳。"

seg_list = jieba.cut(text, cut_all=False)
print (u"分词结果:")
print ("/ ".join(seg_list))


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

输出结果

分词结果:
故宫/ 的/ 著名景点/ 包括/ 乾清宫/ 、/ 太和殿/ 和/ 午门/ 等/ 。/ 其中/ 乾清宫/ 非常/ 精美/ ,/ 午门/ 是/ 紫禁城/ 的/ 正门/ ,/ 午门/ 居中/ 向阳/ 。
关键词: 
午门 乾清宫 著名景点 太和殿 向阳

分析:输出结果按出现词频降序,在词频一样是,根据TF/IDF的升序输出。

(5)去除停用词

为节省存储空间和提高搜索效率,常会自动过滤某些字或词,eg:“的”、“是”、“而且”、“但是”、”非常“等。这些字或词被称为 stop words (停用词)

在分词之前去除停用词,再进行分词。

import jieba
import jieba.analyse

filename = './mydict.txt'
jieba.load_userdict(filename)

text = "故宫的著名景点包括乾清宫、太和殿和午门等。其中乾清宫非常精美,午门是紫禁城的正门,午门居中向阳。"
stopwords = {}.fromkeys(['的', '包括', '等', '是'])

seg_list = jieba.cut(text, cut_all=False)
final = ''
for seg in seg_list:
    if seg not in stopwords:
        final += seg
print(final, '\n')

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

输出结果

故宫著名景点乾清宫、太和殿和午门。其中乾清宫非常精美,午门紫禁城正门,午门居中向阳。

故宫/ 著名景点/ 乾清宫/ 、/ 太和殿/ 和/ 午门/ 。/ 其中/ 乾清宫/ 非常/ 精美/ ,/ 午门/ 紫禁城/ 正门/ ,/ 午门/ 居中/ 向阳/ 。

(6)构建词表

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')

(7)文档向量化

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/cnews.test.txt', names=['title', 'content'], sep='\t')  # (10000, 2)
    data = data.head(50)

    stopword = read_stopword('stopword.txt')
    data = cut_data(data, stopword)

    fea_vec = text_vec(data)
    print(fea_vec)

猜你喜欢

转载自blog.csdn.net/u012736685/article/details/88206485