在本次练习中,我们主要实现build_freqs()函数,并且把数据喂进去看看可视化的结果。在整个推特情感分析项目中,这个函数的任务是构建一个字典。我们可以在字典里面查找每个词出现的次数。字典对于后续提取数据集的特征值是非常有帮助的。
不单单是计算频次,而是计算一个单词,描述正向的次数和负向的次数。也就是说,当一个单词出现在一个句子时,这个句子更可能是在讲正向的话,还是负向的。
导入库
先来导入我们需要用到的库
import nltk
from nltk.corpus import twitter_samples
import matplotlib.pyplot as plt
import numpy as np
导入两个在utils.py中定义好的函数
- process_tweet() 数据清洗,分词函数
- build_freqs() 计算一个单词关联正向或负向的频次,然后构建出 freqs 字典,其中的数据键是 (word, label),数据值是频次(一个数值)
from nltk.book import *
from utils import process_tweet, build_freqs
*** Introductory Examples for the NLTK Book ***
Loading text1, ..., text9 and sent1, ..., sent9
Type the name of the text or sentence to view it.
Type: 'texts()' or 'sents()' to list the materials.
text1: Moby Dick by Herman Melville 1851
text2: Sense and Sensibility by Jane Austen 1811
text3: The Book of Genesis
text4: Inaugural Address Corpus
text5: Chat Corpus
text6: Monty Python and the Holy Grail
text7: Wall Street Journal
text8: Personals Corpus
text9: The Man Who Was Thursday by G . K . Chesterton 1908
加载 NLTK 样本数据集
像前一篇做数据预处理的步骤一样,先把数据加载到列表中来。在这个练习当中,加载数据非常简单,只需要一行命令。但我们在实际情况中会碰到多种数据源,比如:关系型数据库,非关系型数据库,接口如WebSerivices,文本,excel表格等等。因此许多从业人员也会抱怨,做数据科学80~90%的时间都花在了处理数据本身之上。
all_positve_tweets = twitter_samples.strings('positive_tweets.json')
all_negative_tweets = twitter_samples.strings('negative_tweets.json')
tweets = all_positve_tweets + all_negative_tweets
print("Number of tweets", len(tweets))
Number of tweets 10000
接下来,我们会创建一个标签数组(array),后续用于关联实际数据的情感。数组的使用跟列表差不多,但会更便于计算。
这里labels
数组共10000个元素,前5000个全是 ‘1‘ 代表正向情感,后5000个全是 ‘0‘ 代表负向。
我们用numpy库来进行这样的处理。
-
np.ones()
创建全 1 数组 -
np.zeros()
创建全 0 数组 -
np.append()
连接数组
# 分别给 np.ones() 和 np.zeros() 输入两个list的长度值,然后再连接到一块去
labels = np.append(np.ones((len(all_positve_tweets))), np.zeros((len(all_negative_tweets))))
# 显示出来看看更直观
labels
array([ 1., 1., 1., ..., 0., 0., 0.])
构建字典
在Python,字典是可变的,被索引好的集合。它把每一个成员存储为 键-值对(key-value pairs),并使用哈希表来建立查找索引。这有利于在上千条数据中进行快速检索。
定义
Python字典的声明使用大括号
dictionary = {'key1': 1, 'key2': 2}
这里定义的字典包含两个条目,在这个例子里,我们使用了字符串类型,根据实际情况还可以是浮点数,整数,元组(tuple)等
增加/修改入口
用中括号可以对字典进行操作,如果字典键已存在,值会被覆盖
# 增加新入口
dictionary['key3'] = -5
# 覆盖值
dictionary['key1'] = 0
print(dictionary)
{'key1': 0, 'key2': 2, 'key3': -5}
访问值和查找键
对字典进行检索和提取是常用操作,建议反复练习,这里有两个方法:
- 使用中括号:键已存在时用,若键不存在则报错
- 使用get()函数:如果值不存在,则可以存在一个默认值
# 中括号查找
print(dictionary['key2'])
2
我们来试试报错的情况,查找一个不存在的键
print(dictionary['key666'])
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-8-3e8f34e21792> in <module>()
----> 1 print(dictionary['key666'])
KeyError: 'key666'
在使用中括号进行检索时,一个常用的办法是通过条件语句来处理键是否能被找到的问题。或者你也可以使用get()函数来为找不到的键生成一个默认值。
if 'key1' in dictionary:
print("item found: ", dictionary['key1']) # 输出 key1 对应的值为:0
else:
print('key1 is not defined')
print("item found: ", dictionary.get('key1', -1)) # 同上,若 key1 没有对应值才会输出 -1
item found: 0
item found: 0
if 'key7' in dictionary:
print(dictionary['key7'])
else:
print('key does not exists') # 字典中没有 key7 因此会输出这一行
print(dictionary.get('key7', -1)) # 字典中没有 key7 所以会直接给 key7 一个默认值为 -1
print(dictionary['key7']) # 最后的报错意味着上一步get()的执行并没有给字典赋值
key does not exists
-1
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-10-ff9ae431bf94> in <module>()
5
6 print(dictionary.get('key7', -1)) # 字典中没有 key7 所以会直接给 key7 一个默认值为 -1
----> 7 print(dictionary['key7']) # 最后的报错意味着上一步get()的执行并没有给字典赋值
KeyError: 'key7'
词频统计
下面我们一起来看看 build_freqs()
函数是如何实现的
def build_freqs(tweets, ys):
"""Build frequencies.
Input:
tweets: a list of tweets
ys: an m x 1 array with the sentiment label of each tweet
(either 0 or 1)
Output:
freqs: a dictionary mapping each (word, sentiment) pair to its
frequency
"""
# Convert np array to list since zip needs an iterable.
# The squeeze is necessary or the list ends up with one element.
# Also note that this is just a NOP if ys is already a list.
yslist = np.squeeze(ys).tolist()
# Start with an empty dictionary and populate it by looping over all tweets
# and over all processed words in each tweet.
freqs = {}
for y, tweet in zip(yslist, tweets):
for word in process_tweet(tweet):
pair = (word, y)
if pair in freqs:
freqs[pair] += 1
else:
freqs[pair] = 1
return freqs
如前所述,你也可以使用get()函数处理键值匹配问题,如:
for y, tweet in zip(yslist, tweets):
for word in process_tweet(tweet):
pair = (word, y)
freqs[pair] = freqs.get(pair, 0) + 1
如上所示,(word, y) 是一对键-值。word是被处理后的推特数据,而y则是对应正向/负向的标记,即1或0。例如:
# "folowfriday" 在正向推特中出现了 25 次
('followfriday', 1.0): 25
# "shame" 在负向推特中出现了 19 次
('shame', 0.0): 19
到这里,或者你就能够理解自然语言为何如此难,因为人类表达中独有的诸如先抑后扬,用负面词汇表达正向情感等。
例如,一款好玩的游戏,我们常常会说“有毒”,但“有毒”这一词在通常情况下又是负面的。
人类语言(特别是中文)这样的特性让机器“理解”语言变得尤为困难。
下面,我们就来看看build_freqs()函数返回的字典长什么样子
freqs = build_freqs(tweets, labels)
print(f'type(freqs) = {type(freqs)}') # print的一种用法,把大括号执行语句的输出,跟前面的字符串合并起来
print(f'len(freqs) = {len(freqs)}')
type(freqs) = <class 'dict'>
len(freqs) = 13076
再把所有词的词频print出来看看
print(freqs)
{('followfriday', 1.0): 25, ......('misser', 0.0): 1}
然而,这样的结果对于理解数据并没有带来太多帮助。更好的办法是对数据进行可视化。
词频表格
在可视化之后,把我们感兴趣的那部分数据放进一张临时表(table)是更好的办法。
keys = ['happi', 'merri', 'nice', 'good', 'bad', 'sad', 'mad', 'best', 'pretti',
'❤', ':)', ':(', '😒', '😬', '😄', '😍', '♛',
'song', 'idea', 'power', 'play', 'magnific']
data = []
for word in keys:
pos = 0
neg = 0
if (word, 1) in freqs:
pos = freqs[(word, 1)]
if (word, 0) in freqs:
neg = freqs[(word, 0)]
data.append([word, pos, neg])
data
[['happi', 211, 25],
['merri', 1, 0],
['nice', 98, 19],
['good', 238, 101],
['bad', 18, 73],
['sad', 5, 123],
['mad', 4, 11],
['best', 65, 22],
['pretti', 20, 15],
['❤', 29, 21],
[':)', 3568, 2],
[':(', 1, 4571],
['😒', 1, 3],
['😬', 0, 2],
['😄', 5, 1],
['😍', 2, 1],
['♛', 0, 210],
['song', 22, 27],
['idea', 26, 10],
['power', 7, 6],
['play', 46, 48],
['magnific', 2, 0]]
这张表格看起来就清晰多了,更进一步,我们可以使用散点图来表示这些数据。
我们不用原始数值来绘制,因为那会导致图样范围太大,取而代之我们在对数尺度上绘制,这样所有的计数更能够在一个维度上看清。
例如,“:)” 有3568个计数为正,只有2个为负,对数取值后变为8.17和0.69。(想通过计算器验证的同学要用 ln 以自然对数e为底,而不是一般的以10为底的 log)
我们还要在图上添加一条红线,标志正区域和负区域的边界。靠近红线的词可被归类为中性词。
fig, ax = plt.subplots(figsize = (8,8))
x = np.log([x[1] + 1 for x in data])
y = np.log([x[2] + 1 for x in data])
ax.scatter(x, y)
plt.xlabel("Log Positive count")
plt.ylabel("Log Negative count")
for i in range(0, len(data)):
ax.annotate(data[i][0], (x[i], y[i]), fontsize = 12)
ax.plot([0,9], [0,9], color = 'red')
plt.show()
这张图很容易理解,特别是表情符号:(和:)在情感分析中似乎很重要。因此,在进行数据预处理时不能去掉这些符号。
此外我们还看到,皇冠所表示的意义似乎还蛮消极的。
感兴趣的同学建议可以试试把所有数据都放到图上来回发生什么。
ChangeLog
- 2021/2/2 09:59:22 完成1-2步骤
- 2021/2/2 17:11:14 完成剩余步骤及理解