douban_starter-checkpoint.ipynb 96.4 KB

豆瓣评分的预测

在这个项目中,我们要预测一部电影的评分,这个问题实际上就是一个分类问题。给定的输入为一段文本,输出为具体的评分。 在这个项目中,我们需要做:

  • 文本的预处理,如停用词的过滤,低频词的过滤,特殊符号的过滤等
  • 文本转化成向量,将使用三种方式,分别为tf-idf, word2vec以及BERT向量。
  • 训练逻辑回归和朴素贝叶斯模型,并做交叉验证
  • 评估模型的准确率

在具体标记为TODO的部分填写相应的代码。

In [1]:
#导入数据处理的基础包
import numpy as np
import pandas as pd

#导入用于计数的包
from collections import Counter

#导入tf-idf相关的包
from sklearn.feature_extraction.text import TfidfTransformer    
from sklearn.feature_extraction.text import CountVectorizer

#导入模型评估的包
from sklearn import metrics

#导入与word2vec相关的包
from gensim.models import KeyedVectors

#导入与bert embedding相关的包,关于mxnet包下载的注意事项参考实验手册
from bert_embedding import BertEmbedding
import mxnet

#包tqdm是用来对可迭代对象执行时生成一个进度条用以监视程序运行过程
from tqdm import tqdm

#导入其他一些功能包
import requests
import os
import re
import warnings
warnings.filterwarnings("ignore")
In [38]:
# 由于bert嵌入无法使用GPU进行加速,所以这里使用了比例系数K来减小数据量
K = 5000

1. 读取数据并做文本的处理

你需要完成以下几步操作:

  • 去掉无用的字符如!&,可自行定义
  • 中文分词
  • 去掉低频词
In [3]:
#读取数据
data = pd.read_csv('data/DMSC.csv')
#观察数据格式
data.head()
ID Movie_Name_EN Movie_Name_CN Crawl_Date Number Username Date Star Comment Like
0 0 Avengers Age of Ultron 复仇者联盟2 2017-01-22 1 然潘 2015-05-13 3 连奥创都知道整容要去韩国。 2404
1 10 Avengers Age of Ultron 复仇者联盟2 2017-01-22 11 影志 2015-04-30 4 “一个没有黑暗面的人不值得信任。” 第二部剥去冗长的铺垫,开场即高潮、一直到结束,会有人觉... 381
2 20 Avengers Age of Ultron 复仇者联盟2 2017-01-22 21 随时流感 2015-04-28 2 奥创弱爆了弱爆了弱爆了啊!!!!!! 120
3 30 Avengers Age of Ultron 复仇者联盟2 2017-01-22 31 乌鸦火堂 2015-05-08 4 与第一集不同,承上启下,阴郁严肃,但也不会不好看啊,除非本来就不喜欢漫威电影。场面更加宏大... 30
4 40 Avengers Age of Ultron 复仇者联盟2 2017-01-22 41 办公室甜心 2015-05-10 5 看毕,我激动地对友人说,等等奥创要来毁灭台北怎么办厚,她拍了拍我肩膀,没事,反正你买了两份... 16
In [4]:
#输出数据的一些相关信息
data.info()
Out [4]:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 212506 entries, 0 to 212505
Data columns (total 10 columns):
 #   Column         Non-Null Count   Dtype 
---  ------         --------------   ----- 
 0   ID             212506 non-null  int64 
 1   Movie_Name_EN  212506 non-null  object
 2   Movie_Name_CN  212506 non-null  object
 3   Crawl_Date     212506 non-null  object
 4   Number         212506 non-null  int64 
 5   Username       212496 non-null  object
 6   Date           212506 non-null  object
 7   Star           212506 non-null  int64 
 8   Comment        212506 non-null  object
 9   Like           212506 non-null  int64 
dtypes: int64(4), object(6)
memory usage: 16.2+ MB
In [5]:
#只保留数据中我们需要的两列:Comment列和Star列
data = data[['Comment','Star']]
#观察新的数据的格式
data.head()
Comment Star
0 连奥创都知道整容要去韩国。 3
1 “一个没有黑暗面的人不值得信任。” 第二部剥去冗长的铺垫,开场即高潮、一直到结束,会有人觉... 4
2 奥创弱爆了弱爆了弱爆了啊!!!!!! 2
3 与第一集不同,承上启下,阴郁严肃,但也不会不好看啊,除非本来就不喜欢漫威电影。场面更加宏大... 4
4 看毕,我激动地对友人说,等等奥创要来毁灭台北怎么办厚,她拍了拍我肩膀,没事,反正你买了两份... 5
In [6]:
# 这里的star代表具体的评分。但在这个项目中,我们要预测的是正面还是负面。我们把评分为1和2的看作是负面,把评分为3,4,5的作为正面
data['Star']=(data.Star/3).astype(int)
data.head(20)
Comment Star
0 连奥创都知道整容要去韩国。 1
1 “一个没有黑暗面的人不值得信任。” 第二部剥去冗长的铺垫,开场即高潮、一直到结束,会有人觉... 1
2 奥创弱爆了弱爆了弱爆了啊!!!!!! 0
3 与第一集不同,承上启下,阴郁严肃,但也不会不好看啊,除非本来就不喜欢漫威电影。场面更加宏大... 1
4 看毕,我激动地对友人说,等等奥创要来毁灭台北怎么办厚,她拍了拍我肩膀,没事,反正你买了两份... 1
5 绝逼不质疑尾灯的导演和编剧水平 1
6 avengers1睡着1次 avengers2睡着两次。。。 0
7 谁再喊我看这种电影我和谁急!实在是接受无能。。。 0
8 超愉悦以及超满足。在历经了第一阶段比漫画更普世的设定融合之后,发展到#AoU#居然出现了不... 1
9 观影过程中,耳边一直有一种突突突突突的声音,我还感慨电影为了让奥创给观众带来紧张感,声音上... 1
10 Long takes, no stakes. 最后大战灾难性得乱 olsen到底什么能力完... 1
11 视觉效果的极限是视觉疲劳 1
12 感觉有略黑暗了点,不过还是萌点满满,但是一想到就要完结了又心碎了一地,,,, 1
13 妇联成员都只会讲不好笑的笑话,唯一加分的是朱莉·德培 0
14 只算還OK的商業片。現在這類片型第一品牌就是漫威了,熱鬧打鬥大場面,人神機甲齊飛,各型超級... 1
15 好看!好看!好看! 1
16 难看一笔 0
17 6/10。第一部精准的节奏、巧妙的悬念和清楚的内心戏不见了,或许导演不想把超级英雄打造成战... 1
18 欧洲竟然真的是最早上映啊= =法国比美国还早一周……没怎么看懂的我想找科普说明都不容易!嘛... 1
19 我是美队的忠实脑!残!粉!!!!!!!!! 1

任务1: 去掉一些无用的字符

In [7]:
# TODO1: 去掉一些无用的字符,自行定一个字符几何,并从文本中去掉
def pre_process(input_str):
    # input_str = re.sub('[0-9]+', 'DIG', input_str)
    # 去除标点符号
    # input_str = re.sub(r"[{}]+".format(punc), " ", input_str)
    
    input_str = re.sub(
        "[0-9a-zA-Z\-\s+\.\!\/_,$%^*\(\)\+(+\"\')]+|[+——!,。?、~@#¥%……&*()<>\[\]::★◆【】《》;;=??]+", " ", input_str)
    # 其他非中文字符
    input_str = re.sub(r"[^\u4e00-\u9fff]", " ", input_str)
    return input_str.strip()

# 正则去除标点符号
data['comment_clean'] = data['Comment'].apply(pre_process)
data.head(20)
Comment Star comment_clean
0 连奥创都知道整容要去韩国。 1 连奥创都知道整容要去韩国
1 “一个没有黑暗面的人不值得信任。” 第二部剥去冗长的铺垫,开场即高潮、一直到结束,会有人觉... 1 一个没有黑暗面的人不值得信任 第二部剥去冗长的铺垫 开场即高潮 一直到结束 会有人觉得只...
2 奥创弱爆了弱爆了弱爆了啊!!!!!! 0 奥创弱爆了弱爆了弱爆了啊
3 与第一集不同,承上启下,阴郁严肃,但也不会不好看啊,除非本来就不喜欢漫威电影。场面更加宏大... 1 与第一集不同 承上启下 阴郁严肃 但也不会不好看啊 除非本来就不喜欢漫威电影 场面更加宏大 ...
4 看毕,我激动地对友人说,等等奥创要来毁灭台北怎么办厚,她拍了拍我肩膀,没事,反正你买了两份... 1 看毕 我激动地对友人说 等等奥创要来毁灭台北怎么办厚 她拍了拍我肩膀 没事 反正你买了两份旅...
5 绝逼不质疑尾灯的导演和编剧水平 1 绝逼不质疑尾灯的导演和编剧水平
6 avengers1睡着1次 avengers2睡着两次。。。 0 睡着 次 睡着两次
7 谁再喊我看这种电影我和谁急!实在是接受无能。。。 0 谁再喊我看这种电影我和谁急 实在是接受无能
8 超愉悦以及超满足。在历经了第一阶段比漫画更普世的设定融合之后,发展到#AoU#居然出现了不... 1 超愉悦以及超满足 在历经了第一阶段比漫画更普世的设定融合之后 发展到 居然出现了不少传统...
9 观影过程中,耳边一直有一种突突突突突的声音,我还感慨电影为了让奥创给观众带来紧张感,声音上... 1 观影过程中 耳边一直有一种突突突突突的声音 我还感慨电影为了让奥创给观众带来紧张感 声音上真...
10 Long takes, no stakes. 最后大战灾难性得乱 olsen到底什么能力完... 1 最后大战灾难性得乱 到底什么能力完全没明白 是巴菲里的 其实剧本没那么差 美国例外论的主...
11 视觉效果的极限是视觉疲劳 1 视觉效果的极限是视觉疲劳
12 感觉有略黑暗了点,不过还是萌点满满,但是一想到就要完结了又心碎了一地,,,, 1 感觉有略黑暗了点 不过还是萌点满满 但是一想到就要完结了又心碎了一地
13 妇联成员都只会讲不好笑的笑话,唯一加分的是朱莉·德培 0 妇联成员都只会讲不好笑的笑话 唯一加分的是朱莉 德培
14 只算還OK的商業片。現在這類片型第一品牌就是漫威了,熱鬧打鬥大場面,人神機甲齊飛,各型超級... 1 只算還 的商業片 現在這類片型第一品牌就是漫威了 熱鬧打鬥大場面 人神機甲齊飛 各型超級英雄...
15 好看!好看!好看! 1 好看 好看 好看
16 难看一笔 0 难看一笔
17 6/10。第一部精准的节奏、巧妙的悬念和清楚的内心戏不见了,或许导演不想把超级英雄打造成战... 1 第一部精准的节奏 巧妙的悬念和清楚的内心戏不见了 或许导演不想把超级英雄打造成战斗机器 所以...
18 欧洲竟然真的是最早上映啊= =法国比美国还早一周……没怎么看懂的我想找科普说明都不容易!嘛... 1 欧洲竟然真的是最早上映啊 法国比美国还早一周 没怎么看懂的我想找科普说明都不容易 嘛 我...
19 我是美队的忠实脑!残!粉!!!!!!!!! 1 我是美队的忠实脑 残 粉

任务2:使用结巴分词对文本做分词

In [8]:
# TODO2: 导入中文分词包jieba, 并用jieba对原始文本做分词
import jieba
def comment_cut(content):
    # TODO: 使用结巴完成对每一个comment的分词
    # 分词并过滤空字符串
    return ' '.join([w for w in jieba.lcut(content.strip()) if len(w) > 0])

# 输出进度条
tqdm.pandas(desc='apply')
data['comment_processed'] = data['comment_clean'].progress_apply(comment_cut)

# 观察新的数据的格式
data.head()
Out [8]:
apply:   0%|                                                                                | 0/212506 [00:00<?, ?it/s]Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\avaws\AppData\Local\Temp\jieba.cache
Loading model cost 0.630 seconds.
Prefix dict has been built successfully.
apply: 100%|█████████████████████████████████████████████████████████████████| 212506/212506 [00:35<00:00, 5973.38it/s]

任务3:设定停用词并去掉停用词

In [9]:
# TODO3: 设定停用词并从文本中去掉停用词

# 下载中文停用词表至data/stopWord.json中,下载地址:https://github.com/goto456/stopwords/
if not os.path.exists('./data/stopWord.json'):
    stopWord = requests.get("https://raw.githubusercontent.com/goto456/stopwords/master/cn_stopwords.txt")
    with open("./data/stopWord.json", "wb") as f:
         f.write(stopWord.content)

# 读取下载的停用词表,并保存在列表中
with open("./data/stopWord.json","r", encoding="utf-8") as f:
    stopWords = f.read().split("\n")  
    
    
# 去除停用词
def rm_stop_word(input_str):
    # your code, remove stop words
    # TODO
    return [w for w in input_str.split() if w not in stopWords]

#这行代码中.progress_apply()函数的作用等同于.apply()函数的作用,只是写成.progress_apply()函数才能被tqdm包监控从而输出进度条。
data['comment_processed'] = data['comment_processed'].progress_apply(rm_stop_word)
Out [9]:
apply: 100%|█████████████████████████████████████████████████████████████████| 212506/212506 [00:31<00:00, 6837.77it/s]
In [10]:
data.head(20)
Comment Star comment_clean comment_processed
0 连奥创都知道整容要去韩国。 1 连奥创都知道整容要去韩国 [奥创, 知道, 整容, 韩国]
1 “一个没有黑暗面的人不值得信任。” 第二部剥去冗长的铺垫,开场即高潮、一直到结束,会有人觉... 1 一个没有黑暗面的人不值得信任 第二部剥去冗长的铺垫 开场即高潮 一直到结束 会有人觉得只... [一个, 没有, 黑暗面, 值得, 信任, 第二部, 剥去, 冗长, 铺垫, 开场, 高潮,...
2 奥创弱爆了弱爆了弱爆了啊!!!!!! 0 奥创弱爆了弱爆了弱爆了啊 [奥创, 弱, 爆, 弱, 爆, 弱, 爆]
3 与第一集不同,承上启下,阴郁严肃,但也不会不好看啊,除非本来就不喜欢漫威电影。场面更加宏大... 1 与第一集不同 承上启下 阴郁严肃 但也不会不好看啊 除非本来就不喜欢漫威电影 场面更加宏大 ... [第一集, 不同, 承上启下, 阴郁, 严肃, 不会, 好看, 本来, 喜欢, 漫威, 电影...
4 看毕,我激动地对友人说,等等奥创要来毁灭台北怎么办厚,她拍了拍我肩膀,没事,反正你买了两份... 1 看毕 我激动地对友人说 等等奥创要来毁灭台北怎么办厚 她拍了拍我肩膀 没事 反正你买了两份旅... [看毕, 激动, 友人, 说, 奥创, 毁灭, 台北, 厚, 拍了拍, 肩膀, 没事, 反正...
5 绝逼不质疑尾灯的导演和编剧水平 1 绝逼不质疑尾灯的导演和编剧水平 [绝逼, 质疑, 尾灯, 导演, 编剧, 水平]
6 avengers1睡着1次 avengers2睡着两次。。。 0 睡着 次 睡着两次 [睡着, 次, 睡着, 两次]
7 谁再喊我看这种电影我和谁急!实在是接受无能。。。 0 谁再喊我看这种电影我和谁急 实在是接受无能 [喊, 这种, 电影, 急, 实在, 接受, 无能]
8 超愉悦以及超满足。在历经了第一阶段比漫画更普世的设定融合之后,发展到#AoU#居然出现了不... 1 超愉悦以及超满足 在历经了第一阶段比漫画更普世的设定融合之后 发展到 居然出现了不少传统... [超, 愉悦, 超, 满足, 历经, 第一阶段, 漫画, 更普世, 设定, 融合, 之后, ...
9 观影过程中,耳边一直有一种突突突突突的声音,我还感慨电影为了让奥创给观众带来紧张感,声音上... 1 观影过程中 耳边一直有一种突突突突突的声音 我还感慨电影为了让奥创给观众带来紧张感 声音上真... [观影, 过程, 中, 耳边, 一直, 一种, 突突突, 突突, 声音, 感慨, 电影, 奥...
10 Long takes, no stakes. 最后大战灾难性得乱 olsen到底什么能力完... 1 最后大战灾难性得乱 到底什么能力完全没明白 是巴菲里的 其实剧本没那么差 美国例外论的主... [最后, 大战, 灾难性, 得乱, 到底, 能力, 完全, 没, 明白, 巴菲, 里, 其实...
11 视觉效果的极限是视觉疲劳 1 视觉效果的极限是视觉疲劳 [视觉效果, 极限, 视觉, 疲劳]
12 感觉有略黑暗了点,不过还是萌点满满,但是一想到就要完结了又心碎了一地,,,, 1 感觉有略黑暗了点 不过还是萌点满满 但是一想到就要完结了又心碎了一地 [感觉, 有略, 黑暗, 点, 萌点, 满满, 想到, 完结, 心碎, 一地]
13 妇联成员都只会讲不好笑的笑话,唯一加分的是朱莉·德培 0 妇联成员都只会讲不好笑的笑话 唯一加分的是朱莉 德培 [妇联, 成员, 只会, 讲, 不好, 笑, 笑话, 唯一, 加分, 朱莉, 德培]
14 只算還OK的商業片。現在這類片型第一品牌就是漫威了,熱鬧打鬥大場面,人神機甲齊飛,各型超級... 1 只算還 的商業片 現在這類片型第一品牌就是漫威了 熱鬧打鬥大場面 人神機甲齊飛 各型超級英雄... [只算還, 商業片, 現在, 這類, 片型, 第一, 品牌, 漫威, 熱鬧, 打鬥大場, 面...
15 好看!好看!好看! 1 好看 好看 好看 [好看, 好看, 好看]
16 难看一笔 0 难看一笔 [难看, 一笔]
17 6/10。第一部精准的节奏、巧妙的悬念和清楚的内心戏不见了,或许导演不想把超级英雄打造成战... 1 第一部精准的节奏 巧妙的悬念和清楚的内心戏不见了 或许导演不想把超级英雄打造成战斗机器 所以... [第一部, 精准, 节奏, 巧妙, 悬念, 清楚, 内心, 戏, 不见, 或许, 导演, 不...
18 欧洲竟然真的是最早上映啊= =法国比美国还早一周……没怎么看懂的我想找科普说明都不容易!嘛... 1 欧洲竟然真的是最早上映啊 法国比美国还早一周 没怎么看懂的我想找科普说明都不容易 嘛 我... [欧洲, 竟然, 真的, 最早, 上映, 法国, 美国, 早, 一周, 没, 懂, 想, 找...
19 我是美队的忠实脑!残!粉!!!!!!!!! 1 我是美队的忠实脑 残 粉 [美队, 忠实, 脑, 残, 粉]

任务4:去掉低频词,出现次数少于10次的词去掉

In [11]:
# TODO4: 去除低频词, 去掉词频小于10的单词,并把结果存放在data['comment_processed']里
word_counter = Counter([w for s in data['comment_processed'].values for w in s])


def rm_low_frequency_words(word_list):
    return [w for w in word_list if word_counter[w] >= 10]

data['comment_processed'] = data['comment_processed'].progress_apply(rm_low_frequency_words)
data['comment_processed_str'] = data['comment_processed'].apply(lambda x: ' '.join(x))
data.head(10)
Out [11]:
apply: 100%|███████████████████████████████████████████████████████████████| 212506/212506 [00:00<00:00, 227439.46it/s]

2. 把文本分为训练集和测试集

选择语料库中的20%作为测试数据,剩下的作为训练数据

In [12]:
# TODO5: 把数据分为训练集和测试集. comments_train(list)保存用于训练的文本,comments_test(list)保存用于测试的文本。 y_train, y_test是对应的标签(0、1)

from sklearn.model_selection import train_test_split

test_ratio = 0.2

# https://machinelearningmastery.com/train-test-split-for-evaluating-machine-learning-algorithms/
src_training, src_testing = train_test_split(data, test_size=test_ratio, stratify=data['Star'])

comments_train, comments_test = src_training['comment_processed_str'].values, src_testing['comment_processed_str'].values
y_train, y_test = src_training['Star'].values, src_testing['Star'].values

3. 把文本转换成向量的形式

在这个部分我们会采用三种不同的方式:

  • 使用tf-idf向量
  • 使用word2vec
  • 使用bert向量

转换成向量之后,我们接着做模型的训练

任务6:把文本转换成tf-idf向量

In [13]:
# TODO6: 把训练文本和测试文本转换成tf-idf向量。使用sklearn的feature_extraction.text.TfidfTransformer模块
#    请留意fit_transform和transform之间的区别。 常见的错误是在训练集和测试集上都使用 fit_transform,需要避免! 
#    另外,可以留意一下结果是否为稀疏矩阵

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer

count_vectorizer = CountVectorizer(token_pattern=r"(?u)\b\w+\b")
tfidf_transformer = TfidfTransformer()

word_count_train = count_vectorizer.fit_transform(comments_train)
tfidf_train = tfidf_transformer.fit_transform(word_count_train)

word_count_test = count_vectorizer.transform(comments_test)
tfidf_test = tfidf_transformer.transform(word_count_test)

print(tfidf_train.shape, tfidf_test.shape)
Out [13]:
(170004, 15763) (42502, 15763)

任务7:把文本转换成word2vec向量

In [14]:
# 由于训练出一个高效的word2vec词向量往往需要非常大的语料库与计算资源,所以我们通常不自己训练Wordvec词向量,而直接使用网上开源的已训练好的词向量。
# data/sgns.zhihu.word是从https://github.com/Embedding/Chinese-Word-Vectors下载到的预训练好的中文词向量文件
# 使用KeyedVectors.load_word2vec_format()函数加载预训练好的词向量文件
model = KeyedVectors.load_word2vec_format('data/sgns.zhihu.word')
In [15]:
#预训练词向量使用举例
model['我们']
Out [15]:
array([-0.200708,  0.188213, -0.20941 ,  0.048857,  0.116663,  0.547244,
       -0.449441, -0.177554,  0.123547,  0.161301, -0.20861 ,  0.429821,
       -0.429595, -0.45094 ,  0.190053,  0.175438,  0.066855, -0.157346,
        0.134905, -0.128076,  0.111503, -0.03149 , -0.347445, -0.231517,
        0.212383,  0.29857 ,  0.167368, -0.064022, -0.048241,  0.109434,
       -0.156835, -0.558394, -0.005307,  0.127788, -0.053521, -0.154787,
       -0.048875,  0.109031,  0.160019,  0.273365, -0.023131, -0.257962,
       -0.051904,  0.103058,  0.019103,  0.210418, -0.12053 ,  0.084021,
        0.085243, -0.406479, -0.285062, -0.229883, -0.125173, -0.141597,
       -0.018101, -0.215311, -0.091788,  0.315358,  0.242912,  0.013785,
       -0.078914,  0.158206,  0.180421, -0.050306, -0.008539, -0.201157,
        0.047753,  0.293518,  0.340344,  0.098132,  0.356952,  0.189959,
       -0.107122, -0.176698,  0.011044,  0.131703,  0.134601, -0.078891,
        0.217989,  0.05074 ,  0.063365,  0.30178 ,  0.161369,  0.157998,
       -0.128195, -0.060345,  0.047446, -0.146161,  0.005427, -0.06684 ,
        0.056229, -0.04922 , -0.122368,  0.181634,  0.180599,  0.026725,
       -0.383503, -0.10855 ,  0.06524 , -0.095767,  0.08362 ,  0.287755,
       -0.325982, -0.026982,  0.147817,  0.041374,  0.342181, -0.010403,
       -0.082642,  0.124128, -0.104747,  0.002654, -0.086981, -0.044065,
       -0.085694, -0.020068, -0.125195, -0.154542, -0.030115,  0.100488,
        0.081022,  0.06612 ,  0.088058, -0.102289, -0.061927, -0.054882,
        0.510755, -0.154545,  0.029478, -0.191885, -0.048633, -0.218267,
       -0.14659 , -0.028195,  0.223698,  0.101008,  0.100562, -0.237451,
        0.492519, -0.163208, -0.466598,  0.041121,  0.153394,  0.066931,
        0.428429,  0.238117,  0.188347,  0.290581,  0.147405, -0.222624,
        0.336171, -0.128802,  0.032038,  0.036617,  0.042459,  0.031089,
        0.092689,  0.092509, -0.206014, -0.093757, -0.079919,  0.052213,
        0.176261,  0.030587, -0.222407, -0.293368, -0.210982,  0.086169,
       -0.41054 ,  0.168664, -0.110555,  0.104398,  0.131111,  0.034967,
       -0.240558,  0.050963,  0.002297, -0.231932,  0.138751, -0.162152,
        0.128286,  0.11232 ,  0.085235,  0.16869 ,  0.072754,  0.004705,
       -0.175828, -0.082598, -0.245999,  0.103419,  0.357173, -0.05588 ,
        0.030934, -0.13984 ,  0.011164, -0.277783, -0.168691, -0.223155,
       -0.203391, -0.015567,  0.161146, -0.110572, -0.06779 , -0.006586,
       -0.039414,  0.245169, -0.182014,  0.38548 ,  0.039947,  0.36978 ,
        0.167039, -0.055724,  0.051462,  0.044205, -0.255853, -0.194969,
       -0.215543,  0.367193, -0.268322,  0.048425,  0.181398,  0.203609,
        0.04321 , -0.280908,  0.215055, -0.410717,  0.209178,  0.365696,
       -0.26421 ,  0.008008, -0.167048,  0.07082 ,  0.148507, -0.121757,
       -0.227046, -0.161108, -0.084349,  0.173502,  0.07519 , -0.203567,
        0.151776, -0.21104 , -0.334659,  0.090743,  0.049097,  0.080783,
       -0.062416, -0.089825,  0.230757, -0.065472,  0.313976,  0.096314,
       -0.145926,  0.146772, -0.007169, -0.041627, -0.050497, -0.34267 ,
       -0.144144, -0.140267,  0.000677, -0.114036, -0.017044, -0.030107,
       -0.098467, -0.233114,  0.103173,  0.093112, -0.11863 ,  0.086859,
        0.300346,  0.146062, -0.173922,  0.162061,  0.143895, -0.158726,
       -0.123311,  0.166061, -0.196121,  0.207249,  0.053585,  0.025314,
       -0.24309 , -0.074694, -0.238774, -0.056441, -0.099747, -0.271508,
        0.212461,  0.189918,  0.162701, -0.154819,  0.235821, -0.131372,
       -0.052284,  0.101817,  0.088172,  0.107883,  0.020072,  0.188443],
      dtype=float32)
In [16]:
vocabulary = model.vocab

def word_vec_averaging(words, dim=300):
    """
    Average all words vectors in one sentence.
    :param words: input sentence
    :param dim: 'size' of model
    :return: the averaged word vectors as the vector for the sentence
    """
    vec_mean = np.zeros((dim,), dtype=np.float32)
    word_num = 0
    first_dim_sum = 0
    for word in words:
        print(f'Word is: {word}')
        if word in vocabulary:
            word_num += 1
            vec_mean = np.add(vec_mean, model[word])
            first_dim_sum += model[word][0]
            print(f'in vocab with first dimension: {model[word][0]} and first_dim_sum is: {first_dim_sum}')
        else:
            print('not in vocab')
    if word_num > 0:
        vec_mean = np.divide(vec_mean, word_num)
        print(f'word_num is: {word_num}, first dim average is: {first_dim_sum / word_num}')
    return vec_mean

one_sample = comments_train[100]
word_vec_averaging(one_sample.split())
Out [16]:
Word is:in vocab with first dimension: 0.014244000427424908 and first_dim_sum is: 0.014244000427424908
Word is: 羡慕
in vocab with first dimension: -0.15045799314975739 and first_dim_sum is: -0.13621399272233248
Word is: 睡醒
in vocab with first dimension: -0.24956999719142914 and first_dim_sum is: -0.3857839899137616
Word is: 自带
in vocab with first dimension: -0.011017999611794949 and first_dim_sum is: -0.39680198952555656
Word is: 眼线
in vocab with first dimension: 0.5926219820976257 and first_dim_sum is: 0.19581999257206917
Word is: 功能
in vocab with first dimension: -0.23343400657176971 and first_dim_sum is: -0.037614013999700546
word_num is: 6, first dim average is: -0.006269002333283424
In [17]:
comments_train[3]
Out [17]:
'三星 韩寒 朴树 片儿 到底 情况 影评 过来 一星'
In [18]:
vocabulary = model.vocab

def word_vec_averaging(words, dim=300):
    """
    Average all words vectors in one sentence.
    :param words: input sentence
    :param dim: 'size' of model
    :return: the averaged word vectors as the vector for the sentence
    """
    vec_mean = np.zeros((dim,), dtype=np.float32)
    word_num = 0
    first_dim_sum = 0
    for word in words:
        if word in vocabulary:
            word_num += 1
            vec_mean = np.add(vec_mean, model[word])
            first_dim_sum += model[word][0]
    if word_num > 0:
        vec_mean = np.divide(vec_mean, word_num)
    return vec_mean

word2vec_train = np.array([word_vec_averaging(s.split()) for s in comments_train])
word2vec_test = np.array([word_vec_averaging(s.split()) for s in comments_test])
print(word2vec_train.shape, word2vec_test.shape)
Out [18]:
(170004, 300) (42502, 300)

任务8:把文本转换成bert向量

In [39]:
# 导入gpu版本的bert embedding预训练的模型。
# 若没有gpu,则ctx可使用其默认值cpu(0)。但使用cpu会使程序运行的时间变得非常慢
# 若之前没有下载过bert embedding预训练的模型,执行此句时会花费一些时间来下载预训练的模型
ctx = mxnet.cpu()
embedding = BertEmbedding(ctx=ctx)

# TODO8: 跟word2vec一样,计算出训练文本和测试文本的向量,仍然采用单词向量的平均。
def bert_embedding_averaging(sentence):
    """返回sentence bert 句向量"""
    tokens, token_embeddings = embedding([sentence])[0]
    return np.mean(np.array(token_embeddings), axis=0).astype(np.float32)
bert_train = np.array([bert_embedding_averaging(s) for s in comments_train[:len(comments_train)//K]])
bert_test = np.array([bert_embedding_averaging(s) for s in comments_test[:len(comments_test)//K]])
print (bert_train.shape, bert_test.shape)
Out [39]:
(34, 768) (8, 768)
In [40]:
print (tfidf_train.shape, tfidf_test.shape)
print (word2vec_train.shape, word2vec_test.shape)
print (bert_train.shape, bert_test.shape)
Out [40]:
(170004, 15763) (42502, 15763)
(170004, 300) (42502, 300)
(34, 768) (8, 768)

4. 训练模型以及评估

对如上三种不同的向量表示法,分别训练逻辑回归模型,需要做:

  • 搭建模型
  • 训练模型(并做交叉验证)
  • 输出最好的结果
In [21]:
# 导入逻辑回归的包
from sklearn.linear_model import LogisticRegression

任务9:使用tf-idf,并结合逻辑回归训练模型

In [46]:
# TODO9: 使用tf-idf + 逻辑回归训练模型,需要用gridsearchCV做交叉验证,并选择最好的超参数
clf = LogisticRegression()

from sklearn.model_selection import GridSearchCV

search_grid = {
    'C': [0.01, 1, 10, 100],
    'class_weight': [None, 'balanced']
}

grid_search = GridSearchCV(estimator = clf, 
                           param_grid = search_grid, 
                           cv = 55, 
                           n_jobs=-1, 
                           scoring='accuracy')

grid_result = grid_search.fit(tfidf_train, y_train)
print(f'Best parameters: {grid_result.best_params_}')

clf.fit(tfidf_train, y_train)
tf_idf_y_pred = clf.predict(tfidf_test)
print('TF-IDF LR test accuracy %s' % metrics.accuracy_score(y_test, tf_idf_y_pred))
#逻辑回归模型在测试集上的F1_Score
print('TF-IDF LR test F1_score %s' % metrics.f1_score(y_test, tf_idf_y_pred,average="macro"))
Out [46]:
Best parameters: {'C': 1, 'class_weight': None}
TF-IDF LR test accuracy 0.8766175709378382
TF-IDF LR test F1_score 0.7365217068731438

任务10:使用word2vec,并结合逻辑回归训练模型

In [45]:
# TODO10: 使用word2vec + 逻辑回归训练模型,需要用gridsearchCV做交叉验证,并选择最好的超参数
clf = LogisticRegression()

from sklearn.model_selection import GridSearchCV

search_grid = {
    'C': [0.01, 1, 10, 100],
    'class_weight': [None, 'balanced']
}

grid_search = GridSearchCV(estimator = clf, 
                           param_grid = search_grid, 
                           cv = 5, 
                           n_jobs=-1, 
                           scoring='accuracy')

grid_result = grid_search.fit(word2vec_train, y_train)
print(f'Best parameters: {grid_result.best_params_}')
clf.fit(word2vec_train, y_train)
word2vec_y_pred = clf.predict(word2vec_test)
print('Word2vec LR test accuracy %s' % metrics.accuracy_score(y_test, word2vec_y_pred))
#逻辑回归模型在测试集上的F1_Score
print('Word2vec LR test F1_score %s' % metrics.f1_score(y_test, word2vec_y_pred,average="macro"))
Out [45]:
Best parameters: {'C': 100, 'class_weight': None}
Word2vec LR test accuracy 0.848618888522893
Word2vec LR test F1_score 0.6377919282595982

任务11:使用bert,并结合逻辑回归训练模型

In [47]:
# TODO11: 使用bert + 逻辑回归训练模型,需要用gridsearchCV做交叉验证,并选择最好的超参数
clf = LogisticRegression()

from sklearn.model_selection import GridSearchCV

search_grid = {
    'C': [0.01, 1, 10, 100],
    'class_weight': [None, 'balanced']
}

grid_search = GridSearchCV(estimator = clf, 
                           param_grid = search_grid, 
                           cv = 5, 
                           n_jobs=-1, 
                           scoring='accuracy')

grid_result = grid_search.fit(bert_train, y_train[:len(y_train)//K])
print(f'Best parameters: {grid_result.best_params_}')
clf.fit(bert_train, y_train[:len(y_train)//K])
bert_y_pred = lr.predict(bert_test)
print('Bert LR test accuracy %s' % metrics.accuracy_score(y_test[:len(y_test)//K], bert_y_pred))
#逻辑回归模型在测试集上的F1_Score
print('Bert LR test F1_score %s' % metrics.f1_score(y_test[:len(y_test)//K], bert_y_pred,average="macro"))
Out [47]:
Best parameters: {'C': 0.01, 'class_weight': None}
Bert LR test accuracy 0.625
Bert LR test F1_score 0.38461538461538464

任务12:对于以上结果请做一下简单的总结,按照1,2,3,4提取几个关键点,包括:

  • 结果说明什么问题?
  • 接下来如何提高?

1.预训练的词向量和bert模型的好坏对最终结果产生直接影响。如果预训练语料和下游任务不匹配,效果可能会很差。 2.词向量的维度对结果也会产生影响,但是维度并不是越大越好。维度越大,可能会导致噪声变大。 3.本次作业使用的库都比较老,导致在我自己机器声运行是无法使用GPU加速。所以训练bert向量时值用了一小部分数据。 4.对于bert来说使用finetune方法会有所提升。