文章目录
1. 数据描述
数据集为IMDB影评数据,每条评论根据正负态度标注为0或1。数据集包括已标注数据集、未标注的额外训练数据集、测试数据集以及提交样例。
2. 使用方法
针对文本分类问题,首先需要对文本进行预处理,之后对文本进行特征提取,最后使用机器学习里面的分类器或RNN/CNN神经网络对文本分类。
3. 具体实现
3.1 文本预处理
由于数据集为英文文本,因此可以根据空格直接对评论进行分词。由于数据中包含一些HTML标签,因此需要对HTML标签以及其他一些非英文字符去除。可以通过使用BeautifulSoup
以及正则表达式替换操作完成。
def clean_stopwords(df: pd.DataFrame) -> pd.DataFrame:
reviews_len = len(df['review'].values)
for i in range(reviews_len):
review = df.loc[i, 'review']
review = BeautifulSoup(review, features='lxml').get_text().lower()
review = re.sub('[^a-zA-Z]', ' ', review).split(' ')
review = [w for w in review if w not in stopwords.words('english')]
review = ' '.join(review)
df.loc[i, 'review'] = review
return df
除了对文本进行清理外,由于因为语法、时态等原因,一些单词会以不同的形式出现,比如ate
与eat
,good
与best
等,需要将它们还原到原始形式。使用nltk
包中的WordNetLemmatizer
可以对英文单词的词性进行还原,但是使用该方法对词性还原前需要知道该单词当前的词性。比如还原friendly
需要在方法中添加参数r
表示当前的单词为副词形式。获取单词词性的方法可以参考[1]。不过由于获取的词性标签不能直接传入作为参数,因此需要进行一下转换:
def get_word_net_pos(tag: str):
if tag.startswith('J'):
return 'a'
elif tag.startswith('V'):
return 'v'
elif tag.startswith('R'):
return 'r'
elif tag.startswith('N'):
return 'n'
else:
return None
标签以及对应的字符串的对应关系可以查看nltk
的文档[2]。
3.2 文本特征
由于分类器、神经网络需要输入数值型的数据,因此需要将文本转换为向量形式。常用的文本表示方法有: 1 ◯ \raisebox{.5pt}{\textcircled{\raisebox{-.9pt} {1}}} 1◯基于one-hot、tf-idf的词袋模型(bag of word); 2 ◯ \raisebox{.5pt}{\textcircled{\raisebox{-.9pt} {2}}} 2◯基于文本主题模型的SVD、LDA等; 3 ◯ \raisebox{.5pt}{\textcircled{\raisebox{-.9pt} {3}}} 3◯基于词向量静态特征的word2vec、fasttext等; 4 ◯ \raisebox{.5pt}{\textcircled{\raisebox{-.9pt} {4}}} 4◯基于词向量的动态特征的BERT、GPT等[3]。
本次文本情感分类尝试使用了词袋模型、word2vec以及fasttext对文本进行表示。
3.3 文本表示及分类
共尝试使用词袋模型+tensorflow、word2vec+分类器stacking以及fasttext+Keras分别对待测文本进行分类。其中前两种方法未能取得较高的分类效果,第三种方法的效果较好,分别在训练集以及验证集上取得90%以及87%左右的准确率。
3.3.1 词袋模型+tensorflow
基本方法是首先根据TF-IDF对训练语料库中的词语进行过滤,将TF-IDF值较低的词语过滤掉。之后创建一个词库(list列表),其中下标0位置置为unk
意为未登录词。之后对训练文本根据此词库转换为对应的向量。
由于很多评论较长,因此采取将评论分为一个个的句子,对句子进行编码。之后对每个句子使用0
padding,补齐长度至64,同时每个评论的句子个数也要padding。(也许这样麻烦了一点,直接对整个评论编码、padding会更好?)。最后训练数据x
的shape为[review_num,sentence_num,sentence_len]
。time_step
设置为sentence_num,关于time_step
可以参考[4]。
完整代码如下:
# -*- coding:utf-8 -*-
import pandas as pd
import tensorflow as tf
from nltk.corpus import stopwords
from bs4 import BeautifulSoup
import re
import nltk
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
import numpy as np
from os import listdir, path
import pickle
nltk.download('stopwords')
nltk.download('punkt')
nltk.download('averaged_perceptron_tagger')
nltk.download('wordnet')
train = pd.read_csv('labeledTrainData.tsv', delimiter='\t', header=0)
train = train[:2000]
test = pd.read_csv('testData.tsv', delimiter='\t', header=0)
test = test[:400]
def clean_stopwords(df: pd.DataFrame) -> pd.DataFrame:
"""
去除停用词、HTML标签
:param df:
:return:
"""
assert 'review' in df.columns, 'Error: DataFrame doesn\'t has review column'
review_len = len(df['review'].values)
for i_cl in range(review_len):
review = df.iloc[i_cl]['review']
review = BeautifulSoup(review, features='lxml').get_text() # 去除HTML标签
review = re.sub('[^a-zA-Z]', ' ', review).lower()
review = review.split(' ')
review = [w for w in review if review not in stopwords.words('english')]
review = ' '.join(review)
df.loc[i_cl, 'review'] = review
return df
# 预处理文本内容
if 'train.pkl' not in listdir('.'):
train = clean_stopwords(train)
with open('train.pkl', 'wb') as fw:
pickle.dump(train, fw)
else:
with open('train.pkl', 'rb') as fr:
train = pickle.load(fr)
if 'test.pkl' not in listdir('.'):
test = clean_stopwords(test)
with open('test.pkl', 'wb') as fw:
pickle.dump(test, fw)
else:
with open('test.pkl', 'rb') as fr:
test = pickle.load(fr)
def get_wordnet_pos(tag: str):
if tag.startswith('J'): # 形容词
return 'a'
else:
return tag[0].lower()
def preprocess_sentence(paragraph: str) -> list:
"""
将review段落转换成句子列表,并使用nltk还原词形
:param paragraph:
:return:
"""
sentence_list = paragraph.split(' ')
c