NLP入门(1)

 

天然语言处理,简称:NLP,是指对人们平时平常使用的交流语言进行处理的一项技术。NLP 通过多年的发展,现今能够划分为两部份内容,即:天然语言的理解和天然语言的生成。html

本文将以文本分类为目标,介绍天然语言处理相关的基础操做和应用。git

(来自https://www.shiyanlou.com/courses/1208正则表达式

作一个中文文本分类任务,首先要作的是文本的预处理,对文本进行分词和去停用词操做,来把字符串分割成词与词组合而成的字符串集合并去掉其中的一些非关键词汇(像是:的、地、得等)。再就是对预处理事后的文本进行特征提取。最后将提取到的特征送进分类器进行训练。算法

术语解释:app

  • 分词:词是 NLP 中可以独立活动的有意义的语言成分。即便某个中文单字也有活动的意义,但其实这些单字也是词,属于单字成词。dom

  • 词性标注:给每一个词语的词性进行标注,好比 跑/动词、美丽的/形容词等等。函数

  • 命名实体识别:从文本中识别出具备特定类别的实体。像是识别文本中的日期,地名等等。工具

  • 词义消歧:多义词判断最合理的词义。学习

  • 句法分析:解析句子中各个成分的依赖关系。测试

  • 指代消解:消除和解释代词「这个,他,你」等的指代问题。

1.Python字符串操做

变量名.count("A"):返回子串A在字符串中出现的次数

.strip()方法能够去除字符串首尾的指定符号。无指定时,默认去除空格符 ' ' 和换行符 '\n'

只去除字符串开头的某个字符串使用 .lstrip() 方法

一样,可使用.rstrip() 方法来单独去除末尾的字符

字符串拼接:直接用“+”,须要将字符串用特定的符号拼接起来的字符的时候,能够用 .join() 方法来进行拼接。

seq = ['2018', '10', '31']
seq = '-'.join(seq)  # 用 '-' 拼接

比较大小:直接用运算符,也可用 operator

.upper() 和 .lower()转换英文字符大小写

为了查找到某段字符串当中某个子串的位置信息,有两种方法。一种是.index ,一种是 .find。 两种方法均可实现这个功能,不一样的是 index 若是未找到的话,会报错,而 find 未找到的则会返回 -1 值。

注意第一个位置是0

有的时候,须要把一个字符串按照某个字符切分开处理。好比‘今每天气很好,咱们出去玩’,要把两句话以 ','切开成两句话。split()函数能够完成这个操做,函数返回一个由切分好的字符串组成的列表。

翻转字符串:

seq = '12345'
seq = seq[::-1]

 in 关键字能够用在任何容器对象上,判断一个子对象是否存在于容器当中,并不局限于判断字符串是否存在某子串,还能够用在其余容器对象例如 listtupleset 等类型。可配合if 进行后续操做。

.replace(a,b)替换操做,把a替换成b

当遇到须要判断字符串是否以某段字符开头的时候。好比想要判断‘abcdefg’是否以 'a'开头。能够用 .startswish() 方法。一样的方法,咱们能够用 .endswith() 来肯定字符串是否以某段字符串结尾。

.isdigit() 判断字符串是否由纯数字组成。

2.正则表达式

例子:找出 '2018/01/01', '01/01/2019', '01.2017.01' 这几种不一样日期表达中的年份。

规则:连续4个字符 都是 0-9的数字

import re
pattern = re.compile(r'[0-9]{4}')
times = ('2018/01/01', '01/01/2019', '01.2017.01')
for time in times:
    match = pattern.search(time)
    if match:
        print('年份有:', match.group())

输出为:

re 模块的更多使用:

下面咱们经过几个例子来体会一下 re 模块对于正则表达式的其余用法。

.findall()

这个方法能够找到符合正则表达式的全部匹配结果。这里咱们使用了 \d 规则的正则表达式,这个正则表达式能够替咱们识别数字。

 

一样的方法,咱们编写一个 \D 正则表达式,这个能够匹配一个非数字字符。

.match() 方法与 .search() 方法相似,只匹配一次,而且只从字符串的开头开始匹配。一样,match 结果也是存在 group() 当中。

3.中英文分词方法及实现

以前介绍了正则表达式和词的切分,如今介绍中英文分词方法。英文分词比较容易,由于英文词与词之间有空格,可是中文须要按照语义进行切分。

3.1 英文分词

直接按照空格进行分词

 

按空格指定切分次数:

按照关键词进行切分

若是须要切分的句子中混入一些其余符号,须要编写函数将其去除,以下:

 1 def tokenize_english_text(text):
 2     # 首先,咱们按照标点来分句
 3     # 先创建一个空集用来,用来将分好的词添加到里面做为函数的返回值
 4     tokenized_text = []
 5     # 一个text中可能不止一个内容,咱们对每一个文本单独处理并存放在各自的分词结果中。
 6     for data in text:
 7         # 创建一个空集来存储每个文本本身的分词结果,每对data一次操做咱们都归零这个集合
 8         tokenized_data = []
 9         # 以 '.'分割整个句子,对分割后的每一小快s:
10         for s in data.split('.'):
11             # 将's'以 '?'分割,分割后的每一小快s2:
12             for s1 in s.split('?'):
13                 # 一样的道理分割s2,
14                 for s2 in s1.split('!'):
15                     # 同理
16                     for s3 in s2.split(','):
17                         # 将s3以空格分割,而后将结果添加到tokenized_data当中
18                         tokenized_data.extend(
19                             s4 for s4 in s3.split(' ') if s4 != '')
20                         # 括号内的部分拆开理解
21                         # for s4 in s3.split(' '):
22                         #    if s4!='':  这一步是去除空字符''。注意与' ' 的区别。
23         # 将每一个tokenized_data分别添加到tokenized_text当中
24         tokenized_text.append(tokenized_data)
25     return tokenized_text

注意的是 必须判断是否为空字符,再将其加入列表。运行结果以下:

3.2 中文分词

中文分词这个概念自提出以来,通过多年的发展,主要能够分为三个方法:机械分词方法,统计分词方法,以及两种结合起来的分词。

机械分词方法又叫作基于规则的分词方法,这种分词方法按照必定的规则将待处理的字符串与一个词表词典中的词进行逐一匹配,若在词典中找到某个字符串,则切分,不然不切分。机械分词方法按照匹配规则的方式,又能够分为:正向最大匹配法逆向最大匹配法双向匹配法三种。

如下展现的是正向最大匹配法,其计算步骤以下:

  1. 导入分词词典 dic,待分词文本 text,建立空集 words 。
  2. 遍历分词词典,找到最长词的长度,max_len_word 。
  3. 将待分词文本从左向右取 max_len=max_len_word 个字符做为待匹配字符串 word 。
  4. 将 word 与词典 dic 匹配
  5. 若匹配失败,则 max_len = max_len - 1 ,而后
  6. 重复 3 - 4 步骤
  7. 匹配成功,将 word 添加进 words 当中。
  8. 去掉待分词文本前 max_len 个字符
  9. 重置 max_len 值为 max_len_word
  10. 重复 3 - 8 步骤
  11. 返回列表 words

如下是一个实例:

 1 text = "我在实验楼学习呢"
 2 dic = ("","正在","","实验楼","学习","")
 3 words = []
 4 #获得最大词长
 5 max_len = 0
 6 for key in dic:
 7     if len(key)>max_len:
 8         max_len = len(key)
 9         
10 # 判断text的长度是否大于0,若是大于0则进行下面的循环
11 while len(text) > 0:
12     # 初始化想要取的字符串长度
13     # 按照最长词长度初始化
14     word_len = max_len
15     # 对每一个字符串可能会有(max_len_word)次循环
16     for i in range(0, max_len):
17         # 令word 等于text的前word_len个字符
18         word = text[0:word_len]
19         # 为了便于观察过程,咱们打印一下当前分割结果
20         print('', word, '进行匹配')
21         # 判断word是否在词典dic当中
22         # 若是不在词典当中
23         if word not in dic:
24             #则以word_len - 1
25             word_len -= 1
26             # 清空word
27             word = []
28         # 若是word 在词典当中
29         else:
30             # 更新text串起始位置
31             text = text[word_len:]
32             # 为了方便观察过程,咱们打印一下当前结果
33             print('{}   匹配成功,添加进words当中'.format(word))
34             # 把匹配成功的word添加进上面建立好的words当中
35             words.append(word)
36             # 清空word
37             word = []
38             break
39 
40 print(words)

 运行结果为


上述方法虽然简单易行,可是很是依赖于原始词典的构成,因此不具备普适性。接下来介绍一个更为成熟的方法——基于统计规则的分词方法

简单的来讲,就是经过分析字与字之间在一块儿的频率,判断它们是否是一个词。

基于统计的分词,通常状况下有两个步骤:

  1. 创建统计语言模型。
  2. 对句子进行单词划分,而后对划分结果进行几率计算,得到几率最大的分词方式。这里就须要用到统计学习算法,如隐马可夫,条件随机场等。

使用的工具:结巴分词

import jieba

cut_all:False表示显示精确模式分词结果,True表示全模式

如下是搜索引擎模式,与全模式匹配结果相同。全模式和搜索引擎模式,jieba 会把所有可能组成的词都打印出来。在通常的任务当中,咱们使用默认的精确模式就好了,在模糊匹配时,则须要用到全模式或者搜索引擎模式。

修改词典:

1. 使用 suggest_freq(segment, tune=True) 可调节单个词语的词频,使其能(或不能)被分出来。

“今每天气不错”,若是直接使用jieba,会分解成为:今每天气和不错两个词,因此须要调整。

也能够从词典直接删除该词语:

还有一种状况,就是有一些字,他们的组词频率很低,好比“台”和“中”,“台中”出现的几率低于这两个字单独成词的几率,因此此时能够添加词典,强制调高词频。

1 jieba.add_word('台中')
2 seg_list = jieba.cut(string, cut_all=False)
3 '/'.join(seg_list)

如下是一个利用jieba作的简单过滤器,在实际应用中很常见,由于像“的”,“得”,“地”这样的助词须要被过滤掉。

方法是创建一个停用词列表,若是分的词不在列表中,就保留。

1 stopwords = ["","",""]
2 new_list = []
3 s_list = jieba.cut(s,cut_all=False)
4 for word in s_list:
5     if word not in stopwords:
6         new_list.append(word)

4.特征提取

特征提取是将文本应用于分词前的重要步骤。作文本分类等问题的时,须要从大量语料中提取特征,并将这些文本特征变换为数值特征。通常而言,特征提取有下面的两种经典方法。

4.1 词袋模型

词袋模型是最原始的一类特征集,忽略掉了文本的语法和语序,用一组无序的单词序列来表达一段文字或者一个文档。能够这样理解,把整个文档集的全部出现的词都丢进袋子里面,而后无序的排出来(去掉重复的)。对每个文档,按照词语出现的次数来表示文档。

4.2 TF-IDF 模型

这种模型主要是用词汇的统计特征来做为特征集。TF-IDF 由两部分组成:TF(Term frequency,词频),IDF(Inverse document frequency,逆文档频率)两部分组成。

4.3 Python实现

在Python中可使用sklearn来实现上述两种模型。这里要用到 CountVectorizer() 类以及TfidfVectorizer() 类。

下面介绍这两个类:

 1 #词袋
 2 from sklearn.feature_extraction.text import CountVectorizer
 3 #调整参数
 4 vectorizer = CountVectorizer(min_df=1, ngram_range=(1, 1))
 5 corpus = ['This is the first document.',
 6           'This is the second second document.',
 7           'And the third one.',
 8           'Is this the first document?']
 9 a = vectorizer.fit_transform(corpus)
10 vectorizer.get_feature_names()
11 a.toarray()

 特征名词:

['and', 'document', 'first', 'is', 'one', 'second', 'the', 'third', 'this']

 

1 #TF-IDF 
2 from sklearn.feature_extraction.text import TfidfVectorizer
3 vectorizer = TfidfVectorizer(
4     min_df=1, norm='l2', smooth_idf=True, use_idf=True, ngram_range=(1, 1))
5 b = vectorizer.fit_transform(corpus)
6 vectorizer.get_feature_names()
7 b.toarray()

5.实战

邮件样本分类器项目,二分类。分析工具:SVM(支持向量机),步骤:预处理、特征提取、分类

from sklearn.model_selection import train_test_split

train_d, test_d, train_l, test_l = train_test_split(
    datas, labels, test_size=0.25, random_state=5)

实例代码以下:

 1 #加载数据集,包括正常邮件、垃圾邮件、停用词
 2 from urllib import request
 3 request.urlretrieve('http://labfile.oss.aliyuncs.com/courses/1208/ham_data.txt','ham_data.txt')
 4 request.urlretrieve('http://labfile.oss.aliyuncs.com/courses/1208/spam_data.txt','spam_data.txt')
 5 request.urlretrieve('http://labfile.oss.aliyuncs.com/courses/1208/stop_word.txt','stop_word.txt')
 6 
 7 #数据集处理
 8 import numpy as np
 9 from sklearn.model_selection import train_test_split
10 path1 = 'ham_data.txt'  # 正常邮件存放地址
11 path2 = 'spam_data.txt'  # 垃圾邮件地址
12 h = open(path1, encoding='utf-8')
13 h_data = h.readlines()
14 s = open(path2, encoding='utf-8')
15 s_data = s.readlines()
16 #生成标签
17 h_labels = np.ones(len(h_data)).tolist() #正常邮件标签为1  
18 s_labels = np.zeros(len(s_data)).tolist() #垃圾邮件标签为0
19 #整合数据集和标签
20 datas = h_data + s_data  # 将正常样本和垃圾样本整合到datas当中
21 labels = h_labels + s_labels
22 #分类,随机划出 25% 个样本和标签来做为咱们的测试集,剩下的 75% 做为样本集来进行咱们的分类。
23 train_d, test_d, train_l, test_l = train_test_split(
24     datas, labels, test_size=0.25, random_state=5)
25 
26 import jieba
27 #分词
28 def tokenize_words(corpus):
29     tokenized_words = jieba.cut(corpus)
30     tokenized_words = [token.strip() for token in tokenized_words]#去掉词的空格
31     return tokenized_words
32 
33 #去除停用词
34 def remove_stopwords(corpus):  # 函数输入为样本集
35     sw = open('stop_word.txt', encoding='utf-8')  # stopwords 停词表
36     sw_list = [l.strip() for l in sw]  # 去掉文本中的回车符,而后存放到 sw_list 当中
37     # 调用前面定义好的分词函数返回到 tokenized_data 当中
38     tokenized_data = tokenize_words(corpus)
39     # 过滤停用词,对每一个在 tokenized_data 中的词 data 进行判断,若是 data 不在 sw_list 则添加到 filtered_data 当中
40     filtered_data = [data for data in tokenized_data if data not in sw_list]
41     # 用''将 filtered_data 串起来赋值给 filtered_datas
42     filtered_datas = ''.join(filtered_data)
43     return filtered_datas  # 返回去停用词以后的 datas
44 
45 from tqdm import tqdm_notebook #显示进度
46 #预处理数据集
47 def preprocessing_datas(datas):
48     preprocessed_datas = []
49     # 对 datas 当中的每个 data 进行去停用词操做,并添加到上面刚刚创建的 preprocessed_datas 当中
50     for data in tqdm_notebook(datas):
51         data = remove_stopwords(data)
52         preprocessed_datas.append(data)
53     return preprocessed_datas  # 返回去停用词以后的新的样本集
54 
55 pred_train_d = preprocessing_datas(train_d)#预处理样本集
56 pred_test_d = preprocessing_datas(test_d)#预处理测试集
57 '''
58 据此,获得了分词事后而且去除停用词
59 的样本集 pred_train_d 和 测试集 pred_test_d
60 '''
61 
62 #特征提取,用 TF-IDF 模型
63 from sklearn.feature_extraction.text import TfidfVectorizer
64 from sklearn.linear_model import SGDClassifier
65 from sklearn import metrics
66 #设置 TF-IDF 模型训练器 vectorizer
67 vectorizer = TfidfVectorizer(
68     min_df=1, norm='l2', smooth_idf=True, use_idf=True, ngram_range=(1, 1))
69 #进行特征词提取,.get_feature_names()获得特征词列表
70 tfidf_train_features = vectorizer.fit_transform(pred_train_d)
71 '''
72 用训练集训练好特征后的 vectorizer 来提取测试集的特征.。
73 注意这里不能用 vectorizer.fit_transform() 要用 vectorizer.transform(),
74 不然,将会对测试集单独训练 TF-IDF 模型,而不是在训练集的词数量基础上作训练。
75 这样词总量跟训练集不同多,排序也不同,将会致使维数不一样,最终没法完成测试。
76 '''
77 tfidf_test_features = vectorizer.transform(pred_test_d)
78 
79 #调用 SGDClassifier() 类来训练 SVM 分类器
80 svm = SGDClassifier(loss='hinge')
81 #SGDClassifier 是一个多个分类器的组合,当参数 loss='hinge' 时是一个支持向量机分类器
82 svm.fit(tfidf_train_features, train_l)#训练
83 predictions = svm.predict(tfidf_test_features)#测试
84 
85 #用 scikit-learn 库中的 accuracy_score 函数来计算一下分类器的准确率 
86 accuracy_score = np.round(metrics.accuracy_score(test_l, predictions), 2)
87 #np.round(X,2) 的做用是 X 四舍五入后保留小数点后2位数字
88 
89 print(accuracy_score)#打印训练结果——预测准确率
90 
91 #抽样测试
92 print('邮件类型:', test_l[21])
93 print('预测邮件类型:', predictions[21])
94 print('文本:', test_d[21])
相关文章
相关标签/搜索