这里使用Twitter情感文本数据集,进行举例,下载地址
先看一下数据
import pandas as pd
train=pd.read_csv("./data/train_E6oV3lV.csv")
print(train.head(10))
id label tweet
0 1 0 @user when a father is dysfunctional and is s…
1 2 0 @user @user thanks for #lyft credit i can’t us…
2 3 0 bihday your majesty
3 4 0 #model i love u take with u all the time in …
4 5 0 factsguide: society now #motivation
5 6 0 [2/2] huge fan fare and big talking before the…
6 7 0 @user camping tomorrow @user @user @user @use…
7 8 0 the next school year is the year for exams.�
8 9 0 we won!!! love the land!!! #allin #cavs #champ…
9 10 0 @user @user welcome here ! i’m it’s so #gr…
对于每一条数据,我们可以提取到的最基本的特征之一就是词汇数量。通常情况下,负面情绪评论含有的词语数量多于正面情绪评论。
可以使用 split 函数,将句子进行切分:
train['word_count']=train['tweet'].apply(lambda x:len(str(x).split(" ")))
train[['tweet','word_count']].head()
train['char_count']=train['tweet'].str.len()
train[['tweet','char_count']].head()
接下来将计算每条推文的平均词汇长度作为另一个特征,这个有可能帮助我们改善模型。将每条推文所有单词的长度然后除以每条推文单词的个数,即可作为平均词汇长度。
def avg_word(sentence):
words=sentence.split()
return (sum(len(word) for word in words)/len(words))
train['avg_word']=train['tweet'].apply(lambda x:avg_word(x))
train[['tweet','avg_word']].head()
停用词就是句子中没什么必要的单词,去掉他们以后对理解整个句子的语义没有影响。文本中,会存在大量的虚词、代词或者没有特定含义的动词、名词,这些词语对文本分析起不到任何的帮助,我们往往希望能去掉这些“停用词”。
sentence = "this is a apple"
filter_sentence= [w for w in sentence.split(' ') if w not in stopwords.words('english')]
print(filter_sentence)
[‘apple’]
我们对该数据应用停用词:
from nltk.corpus import stopwords
stop = set(stopwords.words('english'))
train['stopwords']=train['tweet'].apply(lambda sen:len([x for x in sen.split() if x in stop]))
train[['tweet','stopwords']].head()
该数据集中存在大量‘#’,‘@’符号,这里我们用 startswith 函数来处理。
train['hashtags']=train['tweet'].apply(lambda sen:len([x for x in sen.split() if x.startswith("#")]))
train[['tweet','hashtags']].head()
这个特征并不常用,但是在做相似任务时,数字数量是一个比较有用的特征。
train['numerics']=train['tweet'].apply(lambda sen:len([x for x in sen.split() if x.isdigit()]))
train[['tweet','numerics']].head()
“Anger”或者 “Rage”通常情况下使用大写来表述,所以有必要去识别出这些词。
train['upper']=train['tweet'].apply(lambda sen:len([x for x in sen.split() if x.isupper()]))
train[['tweet','upper']].head()
做预处理时,将数据变成小写,就避免出现相同的多个副本。比如,在我们计算词汇数量时,‘Analytics’和‘analytics’会被认为不同的单词。
train['tweet']=train['tweet'].apply(lambda sen:" ".join(x.lower() for x in sen.split()))
train['tweet'].head()
0 @user when a father is dysfunctional and is so…
1 @user @user thanks for #lyft credit i can’t us…
2 bihday your majesty
3 #model i love u take with u all the time in ur…
4 factsguide: society now #motivation
Name: tweet, dtype: object
标点符号在文本数据中不添加任何额外的信息,删除所有的符号将帮助我们减少训练数据的大小。
train['tweet'] = train['tweet'].str.replace('[^\w\s]','')
train['tweet'].head()
0 user when a father is dysfunctional and is so …
1 user user thanks for lyft credit i cant use ca…
2 bihday your majesty
3 model i love u take with u all the time in urð…
4 factsguide society now motivation
Name: tweet, dtype: object
这里我们可以创建一个列表 stopwords 作为自己停用词库或使用预定义的库。
from nltk.corpus import stopwords
stop=stopwords.words('english')
train['tweet']=train['tweet'].apply(lambda sen:" ".join(x for x in sen.split() if x not in stop))
train['tweet'].head()
0 user father dysfunctional selfish drags kids d…
1 user user thanks lyft credit cant use cause do…
2 bihday majesty
3 model love u take u time urð ðððð ððð
4 factsguide society motivation
Name: tweet, dtype: object
我们可以把常见的单词从文本数据中去除。首先,让我们来检查中最常出现的10个字文本数据然后再调用删除或保留。
freq=pd.Series(' '.join(train['tweet']).split()).value_counts()[:10]
freq=list(freq.index)
freq
[‘user’, ‘love’, ‘ð’, ‘day’, ‘â’, ‘happy’, ‘amp’, ‘im’, ‘u’, ‘time’]
将常见词去掉
train['tweet']=train['tweet'].apply(lambda sen:' '.join(x for x in sen.split() if x not in freq))
train['tweet'].head()
0 father dysfunctional selfish drags kids dysfun…
1 thanks lyft credit cant use cause dont offer w…
2 bihday majesty
3 model take urð ðððð ððð
4 factsguide society motivation
Name: tweet, dtype: object
正如我们删除最常见的话,这里我们从文本中删除很少出现的词。因为它们很稀有,它们之间的联系和其他词主要是噪音。可以替换罕见的单词更一般的形式,然后这将有更高的计数。
freq = pd.Series(' '.join(train['tweet']).split()).value_counts()[-10:]
freq = list(freq.index)
freq
[‘happenedâ’, ‘britmumspics’, ‘laterr’, ‘2230’, ‘dkweddking’, ‘ampsize’, ‘moviescenes’, ‘kaderimsin’, ‘nfinity’, ‘babynash’]
train['tweet'] = train['tweet'].apply(lambda x: " ".join(x for x in x.split() if x not in freq))
train['tweet'].head()
0 father dysfunctional selfish drags kids dysfun…
1 thanks lyft credit cant use cause dont offer w…
2 bihday majesty
3 model take urð ðððð ððð
4 factsguide society motivation
Name: tweet, dtype: object
这里可以使用 textblob 库。
TextBlob是一个用Python编写的开源的文本处理库。它可以用来执行很多自然语言处理的任务,比如,词性标注,名词性成分提取,情感分析,文本翻译,等等。你可以在官方文档阅读TextBlog的所有特性。
from textblob import TextBlob
train['tweet'][:5].apply(lambda x: str(TextBlob(x).correct()))
0 father dysfunctional selfish drags kiss dysfun…
1 thanks left credit can use cause dont offer wh…
2 midday majesty
3 model take or ðððð ððð
4 factsguide society motivation
Name: tweet, dtype: object
最大匹配是指以词典为依据,取词典中最长单词为第一次取字数量的扫描串,在词典中进行扫描(为提升扫描效率,还可以跟据字数多少设计多个字典,然后根据字数分别从不同字典中进行扫描)。
例如:词典中最长词为“中华人民共和国”共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)(越少越好)
因此最终输出为逆向结果。
词干提取(stemming)是抽取词的词干或词根形式(不一定能够表达完整语义)。
词干提取(stemming)是指通过基于规则的方法去除单词的后缀,比如“ing”,“ly”,“s”等等。
from nltk.stem import PorterStemmer
st=PorterStemmer()
train['tweet'][:5].apply(lambda x:" ".join([st.stem(word) for word in x.split()]))
0 father dysfunct selfish drag kid dysfunct run
1 thank lyft credit cant use caus dont offer whe…
2 bihday majesti
3 model take urð ðððð ððð
4 factsguid societi motiv
Name: tweet, dtype: object
在上面的输出中,“dysfunctional ”已经变为“dysfunct ”
词形还原(lemmatization),是把一个任何形式的语言词汇还原为一般形式(能表达完整语义)。
from textblob import Word
train['tweet']=train['tweet'].apply(lambda x:" ".join([Word(word).lemmatize() for word in x.split()]))
train['tweet'].head()
基
0 father dysfunctional selfish drag kid dysfunct…
1 thanks lyft credit cant use cause dont offer w…
2 bihday majesty
3 model take urð ðððð ððð
4 factsguide society motivation
Name: tweet, dtype: object
使用Python中的collections.Counter模块。
词频率统计:第一步分词,然后根据分词后的结果进行词频率统计。
基于jieba的实现代码:
import jieba
seg_list = list(jieba.cut('今天是我学习自然语言处理NLP的计划二,所需时间为两天',cut_all=False))
WordCount = Counter(seg_list )
print(WordCount)
字符频率统计:按单个字符切分并统计出现频率。
基于collections的实现代码:
from collections import Counter
txt = '今天是我学习自然语言处理NLP的计划二,所需时间为两天'
StatisticalCharacters = Counter(txt)
print(StatisticalCharacters)
统计语言模型是一个单词序列上的概率分布,对于一个给定长度为m的序列,它可以为整个序列产生一个概率 P(w_1,w_2,…,w_m) 。其实就是想办法找到一个概率分布,它可以表示任意一个句子或序列出现的概率。
目前在自然语言处理相关应用非常广泛,如语音识别(speech recognition) , 机器翻译(machine translation), 词性标注(part-of-speech tagging), 句法分析(parsing)等。传统方法主要是基于统计学模型,最近几年基于神经网络的语言模型也越来越成熟。
常见的方法有n-gram模型方法、决策树方法、最大熵模型方法、最大熵马尔科夫模型方法、条件随机域方法、神经网络方法,等等。
n-gram模型(考虑句子中单词之间的顺序)
当n取1、2、3时,n-gram模型分别称为unigram、bigram、trigram语言模型
unigram一元分词,把句子分成一个一个的汉字
bigram二元分词,把句子从头到尾每两个字组成一个词语
trigram三元分词,把句子从头到尾每三个字组成一个词语
比如:
西安交通大学:
unigram 形式为:西/安/交/通/大/学
bigram形式为: 西安/安交/交通/通大/大学
trigram形式为:西安交/安交通/交通大/通大学
词袋模型(不考虑句子中单词之间的顺序)
Bag-of-words模型是信息检索领域常用的文档表示方法。在信息检索中,BOW模型假定对于一个文档,忽略它的单词顺序和语法、句法等要素,将其仅仅看作是若干个词汇的集合,文档中每个单词的出现都是独立的,不依赖于其它单词是否出现。也就是说,文档中任意一个位置出现的任何单词,都不受该文档语意影响而独立选择的。
将所有词语装进一个袋子里,不考虑其词法和语序的问题,即每个词语都是独立的。
1:Bob likes to play basketball, Jim likes too.
2:Bob also likes to play football games.
基于这两个文本文档,构造一个词典如下:
Dictionary = {1:”Bob”, 2. “like”, 3. “to”, 4. “play”, 5. “basketball”, 6. “also”, 7. “football”, 8. “games”, 9. “Jim”, 10. “too”}。
这个词典一共包含10个不同的单词,利用词典的索引号,上面两个文档每一个都可以用一个10维向量表示(用整数数字0~n(n为正整数)表示某个单词在文档中出现的次数):
1:[1, 2, 1, 1, 1, 0, 0, 0, 1, 1]
2:[1, 1, 1, 1 ,0, 1, 1, 1, 0, 0]
向量中每个元素表示词典中相关元素在文档中出现的次数。不过,在构造文档向量的过程中可以看到,我们并没有表达单词在原来句子中出现的次序。
参见 jieba
# encoding=utf-8
import jieba
seg_list = jieba.cut("我来到北京清华大学", cut_all=True)
print("Full Mode: " + "/ ".join(seg_list)) # 全模式
seg_list = jieba.cut("我来到北京清华大学", cut_all=False)
print("Default Mode: " + "/ ".join(seg_list)) # 精确模式
seg_list = jieba.cut("他来到了网易杭研大厦") # 默认是精确模式
print(", ".join(seg_list))
seg_list = jieba.cut_for_search("小明硕士毕业于中国科学院计算所,后在日本京都大学深造") # 搜索引擎模式
print(", ".join(seg_list))
输出:
【全模式】: 我/ 来到/ 北京/ 清华/ 清华大学/ 华大/ 大学
【精确模式】: 我/ 来到/ 北京/ 清华大学
【新词识别】:他, 来到, 了, 网易, 杭研, 大厦 (此处,“杭研”并没有在词典中,但是也被Viterbi算法识别出来了)
【搜索引擎模式】: 小明, 硕士, 毕业, 于, 中国, 科学, 学院, 科学院, 中国科学院, 计算, 计算所, 后, 在, 日本, 京都, 大学, 日本京都大学, 深造
载入词典
调整词典
1、基于TF-IDF算法的关键词抽取
import jieba.analyse
关键词提取所使用逆向文件频率(IDF)文本语料库可以切换成自定义语料库的路径
用法: jieba.analyse.set_idf_path(file_name) # file_name为自定义语料库的路径
关键词提取所使用停止词(Stop Words)文本语料库可以切换成自定义语料库的路径
用法: jieba.analyse.set_stop_words(file_name) # file_name为自定义语料库的路径
2、 基于 TextRank 算法的关键词抽取
import jieba.posseg as pseg
words = pseg.cut("我爱北京天安门")
for word, flag in words:
print('%s %s' % (word, flag))
我 r
爱 v
北京 ns
天安门 ns
用法:
jieba.enable_parallel(4) # 开启并行分词模式,参数为并行进程数
jieba.disable_parallel() # 关闭并行分词模式