NLP笔记Day2:词频统计及可视化

在本次练习中,我们主要实现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 完成剩余步骤及理解
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342