C1W2.Assignment: Naive Bayes

理论课:C1W2.Sentiment Analysis with Naïve Bayes


理论课: C1W2.Sentiment Analysis with Naïve Bayes

加载包与数据

from utils import process_tweet, lookup
import pdb
from nltk.corpus import stopwords, twitter_samples
import numpy as np
import pandas as pd
import nltk
import string
from nltk.tokenize import TweetTokenizer
from os import getcwd
import w2_unittest

注意,需要提前下载NLTK推特数据集和停用词表
加载数据并切分训练和测试集

# get the sets of positive and negative tweets
all_positive_tweets = twitter_samples.strings('positive_tweets.json')
all_negative_tweets = twitter_samples.strings('negative_tweets.json')

# split the data into two pieces, one for training and one for testing (validation set)
test_pos = all_positive_tweets[4000:]
train_pos = all_positive_tweets[:4000]
test_neg = all_negative_tweets[4000:]
train_neg = all_negative_tweets[:4000]

train_x = train_pos + train_neg
test_x = test_pos + test_neg

# avoid assumptions about the length of all_positive_tweets
train_y = np.append(np.ones(len(train_pos)), np.zeros(len(train_neg)))
test_y = np.append(np.ones(len(test_pos)), np.zeros(len(test_neg)))

Part 1: Process the Data

对于任何机器学习项目来说,一旦收集到数据,第一步就是对其进行处理,为模型提供有用的输入。

  • 去除噪音: 首先,要去除数据中的噪音,也就是去除那些不能说明太多内容的词语(停用词)。其中包括 "I, you, are, is,… "等所有常见词语,这些词语无法提供足够的情感信息。
  • 删除股市行情、转发符号、超链接和标签,因为它们无法提供大量的情感信息。
  • 删除推文中的所有标点符号。这样做的原因是,我们希望将带标点符号或不带标点符号的词视为同一个词,而不是将 “happy”、“happy?”、“happy!”、"happy "和 "happy. "视为不同的词。
  • 使用词干法,只跟踪每个词的词干。换句话说,我们将把 “motivation”、"motivated "和 "motivate "归入同一个词干 “motiv-”,从而对它们进行类似的处理。

之前的C1W1中的函数 process_tweet,可以完成以上功能。

custom_tweet = "RT @Twitter @chapagain Hello There! Have a great day. :) #good #morning http://chapagain.com.np"

# print cleaned tweet
print(process_tweet(custom_tweet))

答案:[‘hello’, ‘great’, ‘day’, ‘😃’, ‘good’, ‘morn’]

Part 1.1 Implementing your helper functions

这里要完成情感词频字典(freqs)的构建,字典的键是一个元组(单词、标签),值是相应的频率。
在utils.py中,还实现了一个查询辅助函数(lookup),该函数接收 freqs 词典、一个单词和一个标签(1 或 0),并返回该单词和标签元组在推文集合中出现的次数。
例如,给定以下推文:
[“i am rather excited”, “you are rather happy”]
标签都是为1,函数将返回一个包含以下键值对的字典:
{
(“rather”, 1): 2,
(“happi”, 1) : 1,
(“excit”, 1) : 1
}

  • 请注意,对于给定字符串中的每个单词,都会为其分配相同的标签 1。
  • 请注意,"i "和 "am "这两个词没有被保存,因为它是一个停用词,已被 process_tweet 删除。
  • 请注意 "ather "这个词在推文列表中出现了两次,因此它的计数值是 2。

Instructions

创建一个函数 count_tweets,将推文列表作为输入,对所有推文进行数据预处理,然后返回一个字典。

  • 字典中的键是一个元组,包含词干及其类别标签,例如(“happi”,1)。
  • 值是该词在给定推文中出现的次数(整数)。
# UNQ_C1 GRADED FUNCTION: count_tweets

def count_tweets(result, tweets, ys):
    '''
    Input:
        result: a dictionary that will be used to map each pair to its frequency
        tweets: a list of tweets
        ys: a list corresponding to the sentiment of each tweet (either 0 or 1)
    Output:
        result: a dictionary mapping each pair to its frequency
    '''
    ### START CODE HERE ###
    for y, tweet in zip(ys, tweets):
        for word in process_tweet(tweet):
            # define the key, which is the word and label tuple
            pair = (word,y)
            
            # if the key exists in the dictionary, increment the count
            if pair in result:
                result[pair] += 1

            # else, if the key is new, add it to the dictionary and set the count to 1
            else:
                result[pair] = 1
    ### END CODE HERE ###

    return result
# Testing your function

result = {}
tweets = ['i am happy', 'i am tricked', 'i am sad', 'i am tired', 'i am tired']
ys = [1, 0, 0, 0, 0]
count_tweets(result, tweets, ys)

结果:
{(‘happi’, 1): 1, (‘trick’, 0): 1, (‘sad’, 0): 1, (‘tire’, 0): 2}

Part 2: Train your model using Naive Bayes

步骤

  • 训练朴素贝叶斯分类器的第一个步骤是确定类别的数量。
  • 然后为每个类别创建一个概率。
    P ( D p o s ) P(D_{pos}) P(Dpos)是文档为正类的概率。
    P ( D n e g ) P(D_{neg}) P(Dneg)是文档为负类的概率。
    使用以下公式,并将值存储在字典中:
    P ( D p o s ) = D p o s D (1) P(D_{pos}) = \frac{D_{pos}}{D}\tag{1} P(Dpos)=DDpos(1)

P ( D n e g ) = D n e g D (2) P(D_{neg}) = \frac{D_{neg}}{D}\tag{2} P(Dneg)=DDneg(2)

其中, D D D 是推文的总数, D p o s D_{pos} Dpos 是正面推文的总数, D n e g D_{neg} Dneg 是负面推文的总数。

Prior and Logprior

先验概率表示目标人群中某条推文是正面还是负面的潜在概率。换句话说,如果我们没有任何具体信息,盲目地从人群集合中挑选一条推文,那么这条推文是正面还是负面的概率是多少?具体公式为:
P ( D p o s ) P ( D n e g ) \cfrac{P(D_{pos})}{P(D_{neg})} P(Dneg)P(Dpos)
我们可以取先验值的对数来调整它的大小,我们称之为 logprior:
logprior = log ⁡ ( P ( D p o s ) P ( D n e g ) ) = log ⁡ ( D p o s D n e g ) \text{logprior} = \log \left( \frac{P(D_{pos})}{P(D_{neg})} \right) = \log \left( \frac{D_{pos}}{D_{neg}} \right) logprior=log(P(Dneg)P(Dpos))=log(DnegDpos)
注意, l o g ( A B ) log(\frac{A}{B}) log(BA) 等于 l o g ( A ) − l o g ( B ) log(A)-log(B) log(A)log(B)。 因此,logprior 也可以计算为两个对数之差:
logprior = log ⁡ ( P ( D p o s ) ) − log ⁡ ( P ( D n e g ) ) = log ⁡ ( D p o s ) − log ⁡ ( D n e g ) (3) \text{logprior} = \log (P(D_{pos})) - \log (P(D_{neg})) = \log (D_{pos}) - \log (D_{neg})\tag{3} logprior=log(P(Dpos))log(P(Dneg))=log(Dpos)log(Dneg)(3)

Positive and Negative Probability of a Word

要计算词汇表中某个特定单词的正向概率和负向概率,可使用以下输入:

  • f r e q p o s freq_{pos} freqpos f r e q n e g freq_{neg} freqneg是该词在正分类或负分类中的频率。换句话说,一个词的正向频率是该词被标为 1 的次数。
  • N p o s N_{pos} Npos N n e g N_{neg} Nneg 分别是所有文档(所有推文)中正词和负词的总数。
  • V V V 是整个文档集合中所有类别(无论是正面还是负面)的唯一单词数。

可使用以下公式计算特定单词的正负概率:
P ( W p o s ) = f r e q p o s + 1 N p o s + V (4) P(W_{pos}) = \frac{freq_{pos} + 1}{N_{pos} + V}\tag{4} P(Wpos)=Npos+Vfreqpos+1(4)
P ( W n e g ) = f r e q n e g + 1 N n e g + V (5) P(W_{neg}) = \frac{freq_{neg} + 1}{N_{neg} + V}\tag{5} P(Wneg)=Nneg+Vfreqneg+1(5)
这里使用了平滑处理。

Log likelihood

要计算同一个词的对数似然,可以用下面的公式来计算:
loglikelihood = log ⁡ ( P ( W p o s ) P ( W n e g ) ) (6) \text{loglikelihood} = \log \left(\frac{P(W_{pos})}{P(W_{neg})} \right)\tag{6} loglikelihood=log(P(Wneg)P(Wpos))(6)

数据处理:

  • 使用给定count_tweets函数,计算出一个名为freqs的字典,其中包含所有频率。
  • 在这个 freqs 字典中,键是元组(单词、标签)
  • 值是它出现的次数。
# Build the freqs dictionary for later uses
freqs = count_tweets({}, train_x, train_y)

接下来是构建分类器的步骤:
给定 freqs 词典、train_x(推文列表)和 train_y(每条推文的标签列表),实现一个 naive bayes 分类器。
1.计算词表大小V
计算出现在 freqs 词典中的唯一单词数,从而得到词表大小V,可以使用集合函数去掉重复值。
2. 计算 f r e q p o s freq_{pos} freqpos f r e q n e g freq_{neg} freqneg
使用 freqs 字典,计算每个词的正负频率 f r e q p o s freq_{pos} freqpos f r e q n e g freq_{neg} freqneg
3.计算 N p o s N_{pos} Npos N n e g N_{neg} Nneg
使用 freqs 字典,还可以计算正词和反词的总数 N p o s N_{pos} Npos N n e g N_{neg} Nneg
4.计算 D D D D p o s D_{pos} Dpos D n e g D_{neg} Dneg
使用 "train_y "输入标签列表,计算文档(推文)的数量 D D D,以及正面文档(推文)的数量 D p o s D_{pos} Dpos 和负面文档(推文)的数量 D n e g D_{neg} Dneg
计算文档(推文)为正的概率 P ( D p o s ) P(D_{pos}) P(Dpos),以及文档(推文)为负的概率 P ( D n e g ) P(D_{neg}) P(Dneg)
5.计算对logprior
根据公式3对数先验值为: l o g ( D p o s ) − l o g ( D n e g ) log(D_{pos}) - log(D_{neg}) log(Dpos)log(Dneg)
6.计算对数似然
最后,遍历词汇表中的每个单词,使用 "lookup "函数获取该特定单词的正向频率 f r e q p o s freq_{pos} freqpos 和负向频率 f r e q n e g freq_{neg} freqneg
使用公式 4 和 5 计算每个词的正概率 P ( W p o s ) P(W_{pos}) P(Wpos),以及每个词的负概率 P ( W n e g ) P(W_{neg}) P(Wneg)
注意: 这里使用一个字典来存储每个单词的对数可能性。 键是单词,值是该单词的对数似然值。

# UNQ_C2 GRADED FUNCTION: train_naive_bayes

def train_naive_bayes(freqs, train_x, train_y):
    '''
    Input:
        freqs: dictionary from (word, label) to how often the word appears
        train_x: a list of tweets
        train_y: a list of labels correponding to the tweets (0,1)
    Output:
        logprior: the log prior. (equation 3 above)
        loglikelihood: the log likelihood of you Naive bayes equation. (equation 6 above)
    '''
    loglikelihood = {}
    logprior = 0

    ### START CODE HERE ###

    # calculate V, the number of unique words in the vocabulary
    vocab = [pair[0] for pair in freqs.keys()]
    V = len(set(vocab))

    # calculate N_pos, N_neg, V_pos, V_neg
    N_pos = N_neg = 0
    for pair in freqs.keys():
        # if the label is positive (greater than zero)
        if pair[1] > 0:

            # Increment the number of positive words by the count for this (word, label) pair
            N_pos += freqs[pair]

        # else, the label is negative
        else:

            # increment the number of negative words by the count for this (word,label) pair
            N_neg += freqs[pair]
    
    # Calculate D, the number of documents
    D = len(train_y)

    # Calculate D_pos, the number of positive documents
    D_pos = (len(list(filter(lambda x: x > 0, train_y))))

    # Calculate D_neg, the number of negative documents
    D_neg = (len(list(filter(lambda x: x <= 0, train_y))))

    # Calculate logprior
    logprior = np.log(D_pos)-np.log(D_neg)
    
    # For each word in the vocabulary...
    for word in vocab:
        # get the positive and negative frequency of the word
        freq_pos = lookup(freqs,word,1)
        freq_neg = lookup(freqs,word,0)

        # calculate the probability that each word is positive, and negative
        p_w_pos = (freq_pos+1)/(N_pos+V)
        p_w_neg = (freq_neg+1)/(N_neg+V)

        # calculate the log likelihood of the word
        loglikelihood[word] = np.log(p_w_pos/p_w_neg)

    ### END CODE HERE ###

    return logprior, loglikelihood

Part 3: Test your naive bayes

实现 NAIVE_BAYES_PECKE
说明
执行 naive_bayes_predict 函数对推文进行预测。

  • 该函数接收 tweet, logprior, loglikelihood
  • 返回推文属于正面或负面类别的概率。
  • 对于每条推文,汇总推文中每个单词的 loglikelihoods。
  • 同时将 logprior 加到这个总和中,得到该条推文的预测情感值。

p = l o g p r i o r + ∑ i N ( l o g l i k e l i h o o d i ) p = logprior + \sum_i^N (loglikelihood_i) p=logprior+iN(loglikelihoodi)
注意,这里是根据训练数据计算先验值的,训练数据在正负标签之间平均分配(4000 条正向推文和 4000 条负向推文)。正负标签的比例为 1,因此公式第一项对数先验值为 0。而在实际任务中,只要数据不是完全平衡,logprior 就会是一个非零值。

# UNQ_C4 GRADED FUNCTION: naive_bayes_predict

def naive_bayes_predict(tweet, logprior, loglikelihood):
    '''
    Input:
        tweet: a string
        logprior: a number
        loglikelihood: a dictionary of words mapping to numbers
    Output:
        p: the sum of all the logliklihoods of each word in the tweet (if found in the dictionary) + logprior (a number)

    '''
    ### START CODE HERE ###
    # process the tweet to get a list of words
    word_l = process_tweet(tweet)

    # initialize probability to zero
    p = 0

    # add the logprior
    p += logprior

    for word in word_l:

        # check if the word exists in the loglikelihood dictionary
        if word in loglikelihood:
            # add the log likelihood of that word to the probability
            p += loglikelihood[word]

    ### END CODE HERE ###

    return p

测试:

# UNQ_C5 (UNIQUE CELL IDENTIFIER, DO NOT EDIT)
# Experiment with your own tweet.
my_tweet = 'She smiled.'
p = naive_bayes_predict(my_tweet, logprior, loglikelihood)
print('The expected output is', p)

结果:
The expected output is 1.5626795809988954

实现test_naive_bayes

  • 执行 test_naive_bayes 来检查预测的准确性。
  • 该函数接收test_xtest_y、log_prior 和 loglikelihood。
  • 返回模型的准确性。
  • 首先,使用 naive_bayes_predict 函数对 text_x 中的每条推文进行预测。
# UNQ_C6 GRADED FUNCTION: test_naive_bayes

def test_naive_bayes(test_x, test_y, logprior, loglikelihood, naive_bayes_predict=naive_bayes_predict):
    """
    Input:
        test_x: A list of tweets
        test_y: the corresponding labels for the list of tweets
        logprior: the logprior
        loglikelihood: a dictionary with the loglikelihoods for each word
    Output:
        accuracy: (# of tweets classified correctly)/(total # of tweets)
    """
    accuracy = 0  # return this properly

    ### START CODE HERE ###
    y_hats = []
    for tweet in test_x:
        # if the prediction is > 0
        if naive_bayes_predict(tweet, logprior, loglikelihood) > 0:
            # the predicted class is 1
            y_hat_i = 1
        else:
            # otherwise the predicted class is 0
            y_hat_i = 0

        # append the predicted class to the list y_hats
        y_hats.append(y_hat_i)

    # error is the average of the absolute values of the differences between y_hats and test_y
    error = np.mean(np.abs(y_hats - test_y))

    # Accuracy is 1 minus the error
    accuracy = 1-error

    ### END CODE HERE ###

    return accuracy

测试:

print("Naive Bayes accuracy = %0.4f" %
      (test_naive_bayes(test_x, test_y, logprior, loglikelihood)))

结果:
Naive Bayes accuracy = 0.9950
也可以测试自己构造的推文:

# UNQ_C7 (UNIQUE CELL IDENTIFIER, DO NOT EDIT)
# Run this cell to test your function
for tweet in ['I am happy', 'I am bad', 'this movie should have been great.', 'great', 'great great', 'great great great', 'great great great great']:    
    p = naive_bayes_predict(tweet, logprior, loglikelihood)
    print(f'{tweet} -> {p:.2f}')

结果:
I am happy -> 2.14
I am bad -> -1.31
this movie should have been great. -> 2.13
great -> 2.13
great great -> 4.27
great great great -> 6.40
great great great great -> 8.54

Part 4: Filter words by Ratio of positive to negative counts

根据理论课内容可知:

  • 有些词语的积极意义大于其他词语,因此可被视为 “积极词汇”。 同样,有些词也可以被认为比其他词更消极。
  • 在不计算对数似然的情况下,定义词的积极或消极程度的一种方法是比较词的积极频率和消极频率。
    • 注意,我们也可以使用对数似然计算来比较词语的相对积极性或消极性。
  • 可以计算一个词的正负频率之比。
  • 计算出这些比率后,可筛选出具有最小正负比率或更高比率的词语子集。
  • 同样,也可以筛选出具有最大正负比或更低正负比的词语子集(至少与给定阈值一样负面,甚至比给定阈值更负面的词语)。

实现get_ratio

  • 给定 freqs 词典和一个特定的词,使用 lookup(freqs,word,1) 获得该词的正计数。
  • 同样,使用 lookup 函数获取该词的负计数。
  • 计算正计数除以负计数的比率
    r a t i o = pos_words + 1 neg_words + 1 ratio = \cfrac{\text{pos\_words} +1}{\text{neg\_words} + 1} ratio=neg_words+1pos_words+1
    其中,pos_words 和 neg_words 对应各自类别中的词频,这里也用了平滑。例如:
WordsPositive word countNegative Word Count
glad412
arriv574
: (13663
: -(0378
# UNQ_C8 GRADED FUNCTION: get_ratio

def get_ratio(freqs, word):
    '''
    Input:
        freqs: dictionary containing the words

    Output: a dictionary with keys 'positive', 'negative', and 'ratio'.
        Example: {'positive': 10, 'negative': 20, 'ratio': 0.5}
    '''
    pos_neg_ratio = {'positive': 0, 'negative': 0, 'ratio': 0.0}
    ### START CODE HERE ###
    # use lookup() to find positive counts for the word (denoted by the integer 1)
    pos_neg_ratio['positive'] = lookup(freqs, word,1)
    
    # use lookup() to find negative counts for the word (denoted by integer 0)
    pos_neg_ratio['negative'] = lookup(freqs, word,0)
    
    # calculate the ratio of positive to negative counts for the word
    pos_neg_ratio['ratio'] = (pos_neg_ratio['positive']+1) / (pos_neg_ratio['negative']+1)
    ### END CODE HERE ###
    return pos_neg_ratio

例子:

get_ratio(freqs, 'happi')

结果:
{‘positive’: 162, ‘negative’: 18, ‘ratio’: 8.578947368421053}

实现get_words_by_threshold(freqs,label,threshold)

设置阈值。

  • 如果标签为 1,那么我们将查找所有正/负阈值至少与该阈值相同或更高的词语。
  • 如果标签为 0,那么我们将查找所有正/负阈值至少与给定阈值相同或更低的词语。
  • 使用 get_ratio 函数获取包含正向计数、负向计数以及正负计数比的字典。
  • 将 "get_ratio "字典添加到另一个字典中,其中键是单词,值是 "get_ratio "函数返回的 "pos_neg_ratio "字典。
    键值对的结构示例如下
{'happi':
    {'positive': 10, 'negative': 20, 'ratio': 0.524}
}
# 定义函数 get_words_by_threshold,它接收四个参数:
# freqs: 一个字典,包含词汇及其出现的频率信息
# label: 一个整数,1 表示正面,0 表示负面
# threshold: 一个浮点数,用作筛选词汇的正负比例的阈值
# get_ratio: 一个函数,用于计算给定词汇的正负比例(之前已实现)
def get_words_by_threshold(freqs, label, threshold, get_ratio=get_ratio):
    '''
    Input:
        freqs: 字典,键为词汇,值为该词汇的正负计数
        label: 1 表示正面,0 表示负面
        threshold: 用作筛选的正负比例阈值
    Output:
        word_list: 字典,包含词汇及其正面计数、负面计数和正负比例
        例如:
        {'happi': {'positive': 10, 'negative': 20, 'ratio': 0.5}}
    '''
    # 初始化一个空字典,用于存储符合条件的词汇及其信息
    word_list = {}

    ### START CODE HERE ###
    # 遍历 freqs 字典中的所有键(即所有词汇)
    for key in freqs.keys():
        # 假设每个键是一个元组,包含词汇和一些其他信息,这里只关心词汇
        word, _ = key

        # 调用 get_ratio 函数计算词汇的正负比例
        pos_neg_ratio = get_ratio(freqs, word)

        # 如果标签是 1(正面)并且比例大于或等于阈值,则将词汇添加到 word_list 中
        if label == 1 and pos_neg_ratio['ratio'] >= threshold:
            word_list[word] = pos_neg_ratio

        # 如果标签是 0(负面)并且比例小于或等于阈值,则将词汇添加到 word_list 中
        elif label == 0 and pos_neg_ratio['ratio'] <= threshold:
            word_list[word] = pos_neg_ratio

        # 否则,不将这个词汇包含在列表中(不做任何操作)

    ### END CODE HERE ###
    # 返回包含筛选后词汇的字典
    return word_list

测试:

# Test your function: find negative words at or below a threshold
get_words_by_threshold(freqs, label=0, threshold=0.05)

结果:
在这里插入图片描述

Part 5: Error Analysis

这里列出部分分类错误的样本,思考什么会出现分类错误?朴素贝叶斯模型是否有任何假设?

# Some error analysis done for you
print('Truth Predicted Tweet')
for x, y in zip(test_x, test_y):
    y_hat = naive_bayes_predict(x, logprior, loglikelihood)
    if y != (np.sign(y_hat) > 0):
        print('%d\t%0.2f\t%s' % (y, np.sign(y_hat) > 0, ' '.join(
            process_tweet(x)).encode('ascii', 'ignore')))

在这里插入图片描述

Part 6: Predict with your own tweet

# Test with your own tweet - feel free to modify `my_tweet`
my_tweet = 'I am happy because I am learning :)'

p = naive_bayes_predict(my_tweet, logprior, loglikelihood)
print(p)
  • 11
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oldmao_2000

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值