过去了17天,按照学习计划来说,已经严重超时了。
主要的问题是在数据预处理部分。
Kaggle的IMDB情感分析任务其实很简单,train文件用于训练,test文件用于测试。
步骤
- 整合train和test(就是说所有语料库)生成词袋或词向量模型。也可以下载已有的word2vec或是glove词向量模型。
- 生成的词向量模型就是将每一个词向量化,方便后面的计算。依据生成的词向量模型对train和test语料进行向量化。
- 把向量化的train数据和标签输入分类模型中进行预测,完成模型训练。
- 评估模型,并对test进行预测。
具体实现
1. 数据集
数据集是tsv格式数据,说白了是分成了5类:
0 - negative
1 - somewhat negative
2 - neutral
3 - somewhat positive
4 - positive
我们先读取一下看看。TSV文件和CSV的文件的区别是:前者使用\t作为分隔符,后者使用,作为分隔符。
可以看到总共156060条记录,其中打2分的数据最多,说明大家都还是很中庸啊。
import pandas as pd
train_data=pd.read_csv(r'C:\Users\jwc19\Desktop\sentiment-analysis-on-movie-reviews\train.tsv',sep='\t',header=0)
test_data=pd.read_csv(r'C:\Users\jwc19\Desktop\sentiment-analysis-on-movie-reviews\test.tsv',sep='\t',header=0)
train_data.head()
train_data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 156060 entries, 0 to 156059
Data columns (total 4 columns):
PhraseId 156060 non-null int64
SentenceId 156060 non-null int64
Phrase 156060 non-null object
Sentiment 156060 non-null int64
dtypes: int64(3), object(1)
memory usage: 4.8+ MB
train_data.describe()
以上是数据的描述性统计结果,当然,还可以做一下可视化,这些参考kaggle的泰坦尼克号任务操作。
2. 构建word2vec
接下来是要对语料库向量化,这其实就是一种对文本特征的抽取。
文中提到了两种方式:BOW(词袋)和Word2Vec,相比而言,Word2Vec所包含的信息更多,而且gensim库已经打包好了,在这里使用word2vec。
在2017年新的特征抽取算法transformer将会横扫一切……
在完成词向量抽取后,将进行分类器的训练。
在这里,我们将train和test数据集进行合并,构造出一个维度为200的词向量模型,使用gensim库进行构建。
# 合并test和train的数据,用于训练词向量模型
newDf = pd.concat([test_data["Phrase"], train_data["Phrase"]], axis=0)
newDf.describe()
newDf.to_csv(r"C:\Users\jwc19\Desktop\sentiment-analysis-on-movie-reviews\wordEmbdiing.txt", index=False)
在这里使用gensim的word2vec api来训练模型
主要参数介绍如下:
sentences:我们要分析的语料,可以是一个列表,或者从文件中遍历读出(word2vec.LineSentence(filename) )。
size:词向量的维度,默认值是100。这个维度的取值一般与我们的语料的大小相关,如果是不大的语料,比如小于100M的文本语料,则使用默认值一般就可以了。如果是超大的语料,建议增大维度。
window:即词向量上下文最大距离,window越大,则和某一词较远的词也会产生上下文关系。默认值为5,在实际使用中,可以根据实际的需求来动态调整这个window的大小。如果是小语料则这个值可以设的更小。对于一般的语料这个值推荐在[5;10]之间。
sg:即我们的word2vec两个模型的选择了。如果是0, 则是CBOW模型;是1则是Skip-Gram模型;默认是0即CBOW模型。
hs:即我们的word2vec两个解法的选择了。如果是0, 则是Negative Sampling;是1的话并且负采样个数negative大于0, 则是Hierarchical Softmax。默认是0即Negative Sampling。
negative:即使用Negative Sampling时负采样的个数,默认是5。推荐在[3,10]之间。这个参数在我们的算法原理篇中标记为neg。
cbow_mean:仅用于CBOW在做投影的时候,为0,则算法中的xw为上下文的词向量之和,为1则为上下文的词向量的平均值。在我们的原理篇中,是按照词向量的平均值来描述的。个人比较喜欢用平均值来表示xw,默认值也是1,不推荐修改默认值。
min_count:需要计算词向量的最小词频。这个值可以去掉一些很生僻的低频词,默认是5。如果是小语料,可以调低这个值。
iter:随机梯度下降法中迭代的最大次数,默认是5。对于大语料,可以增大这个值。
alpha:在随机梯度下降法中迭代的初始步长。算法原理篇中标记为η,默认是0.025。
min_alpha: 由于算法支持在迭代的过程中逐渐减小步长,min_alpha给出了最小的迭代步。
from nltk.corpus import stopwords
StopWords = stopwords.words('english')
import logging
import gensim
from gensim.models import word2vec
# 设置输出日志
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
sentences=word2vec.LineSentence(r"C:\Users\jwc19\Desktop\sentiment-analysis-on-movie-reviews\wordEmbdiing.txt")
sentences=list(sentences)
for idx,sentence in enumerate(sentences):
sentence = [w for w in sentence if w not in StopWords]
sentences[idx]=sentence
print(type(sentences))
model=gensim.models.Word2Vec(sentences,size=200, min_count=2,sg=1,iter=2)
model.wv.save_word2vec_format("./word2Vec02" + ".bin", binary=True)
搞定之后运行模型,还可以看看效果
model=gensim.models.KeyedVectors.load_word2vec_format("word2Vec.bin",binary=True)
model["flower"]
array([-0.2378106 , -0.00272736, 0.31298155, 0.03572753, -0.32978794,
0.5877859 , 0.24954697, 0.10183911, 0.30661255, 0.280979 ,
0.04722883, -0.01303975, 0.08539272, 0.04781984, -0.17838825,
0.13571365, -0.07219279, 0.04345001, -0.10493791, 0.05438785,
0.33817822, 0.15342301, -0.01376961, 0.5400121 , 0.41749138,
0.0906916 , 0.04341062, -0.15571249, -0.17380357, -0.1934123 ,
-0.02405222, -0.22066571, -0.14180358, -0.09150579, -0.2944634 ,
0.07576216, -0.1660684 , 0.20156585, 0.1215609 , 0.5412449 ,
-0.1711439 , 0.3214155 , -0.3667486 , -0.18460636, -0.15220495,
-0.07949002, 0.22074243, -0.04971108, -0.09505122, 0.29928744,
0.03575212, -0.13769385, 0.18068919, 0.31546128, 0.10954601,
0.18582347, -0.04675937, -0.03966061, 0.20546672, -0.04146346,
-0.0196472 , 0.0578943 , 0.20681728, -0.04692319, -0.1698708 ,
-0.09567603, -0.11117092, 0.30465436, -0.04794674, -0.06839596,
-0.02868674, -0.20524485, 0.0295146 , -0.01159863, -0.15453497,
0.48093846, -0.3897168 , 0.02332748, 0.0439175 , 0.23415217,
-0.06639539, -0.03457333, -0.2735414 , 0.03905383, 0.038656 ,
0.23679397, -0.33047786, 0.31122783, 0.00199052, -0.30952674,
-0.10884447, -0.40330866, 0.25768963, -0.16997696, 0.12618165,
-0.08964632, -0.01782297, 0.12821278, 0.00424662, -0.11926408,
0.04985361, -0.16177899, -0.06548072, -0.018849 , 0.07923622,
-0.00496559, -0.0372107 , 0.05142358, -0.3297481 , 0.23669559,
0.16632096, 0.12055472, 0.36679494, 0.11643603, 0.05669545,
-0.26389235, -0.06538889, 0.09600764, -0.15645082, -0.00284773,
0.12941402, 0.08277974, 0.09082151, -0.12873018, 0.13429202,
-0.00188877, -0.10478543, 0.20682792, -0.18579291, -0.18376978,
-0.15438502, 0.6078415 , -0.05618986, -0.00372298, -0.34480548,
-0.00986845, 0.20730568, -0.28601113, 0.08377945, 0.2517998 ,
0.08157796, 0.24523894, -0.34019017, 0.10557748, 0.02105924,
0.03729287, -0.52203006, 0.1191924 , -0.32391408, -0.25671792,
-0.24574052, 0.21722569, 0.05409996, -0.1944298 , 0.05195828,
-0.30965397, 0.31671712, 0.23532335, 0.34292328, -0.04460131,
-0.24952726, 0.1692848 , -0.2680034 , -0.20551267, 0.31070685,
-0.01980814, 0.24538256, 0.11438795, -0.52290195, -0.25548056,
-0.12335302, -0.32273138, -0.15207022, 0.03945559, -0.02261233,
-0.11034735, -0.27235347, -0.17029978, -0.37533283, -0.0962036 ,
-0.21412134, -0.04120854, 0.12733105, -0.22446166, -0.26129523,
-0.01468701, 0.24803281, 0.0242933 , 0.12278723, -0.06079411,
0.14851114, 0.04741063, -0.16954847, -0.1654084 , 0.3050954 ,
0.0125294 , -0.03766926, 0.06326802, -0.11463621, -0.02890763],
dtype=float32)
到这里,word2vec的生成就已经搞定了。要感谢google的colab,替我节省了大量的时间,我用笔记本跑了4个小时的模型,在colab上只用了几分钟……
接下来是将语料库进行向量化。我在这里卡了有一周,因为在生成word2vec的时候,会将频率低于2的低频词干掉,但是在语料库数据向量化的时候会遇到低频词未登记(UNK)的情况,怎么解决,查了很多材料。后来才发现,我特么傻了,在转换的时候直接写判断过滤掉不就行了……
import logging
import gensim
from gensim.models import word2vec
model=gensim.models.KeyedVectors.load_word2vec_format("./sample_data/word2Vec03.bin",binary=True)
index2word=model.index2word
print(len(index2word))
index2word_set=set(model.index2word)
print(len(index2word_set))
print(model)
# text是输入的已经分好词的语料库文本
# model是之前生成的word2vec模型
# num_features是word2vec模型中每个词维度大小,这里是200
def word2vec(text,model,num_features):
featureVec = np.zeros((200,),dtype="float32")
nwords=0
for word in text:
if word in index2word_set:
nwords+=1
featureVec=np.add(featureVec,model[word])
featureVec = np.divide(featureVec,nwords)
return featureVec
# print(word2vec(token))
def getAvgFeatureVecs(phrases,model,num_features):
counter=0
phraseFeatureVecs = np.zeros((len(phrases),num_features),dtype="float32")
for phrase in phrases:
if counter % 2000==0:
print("Phrase %d of %d" % (counter, len(phrases)))
phraseFeatureVecs[counter]=word2vec(phrase, model, num_features)
counter = counter+1
return phraseFeatureVecs
from nltk.corpus import stopwords
import re
def phrase_to_wordlist(phrase, remove_stopwords=False):
phrase_text = re.sub("[^a-zA-Z]"," ", phrase)
words = phrase_text.lower().split()
if remove_stopwords:
stops = set(stopwords.words("english"))
words = [w for w in words if not w in stops]
return(words)
clean_train_phrases = []
for phrase in train_data["Phrase"]:
clean_train_phrases.append( phrase_to_wordlist( phrase, remove_stopwords=True ))
num_features=200
trainDataVecs = getAvgFeatureVecs( clean_train_phrases, model, num_features )
clean_test_phrases = []
for phrase in test_data["Phrase"]:
clean_test_phrases.append( phrase_to_wordlist( phrase, remove_stopwords=True ))
num_features=200
testDataVecs = getAvgFeatureVecs( clean_test_phrases, model, num_features )
现在好了吧,可以送进去训练了吧。
但是……又遇到问题了,在使用sklearn训练的时候报错
ValueError: Input contains NaN, infinity or a value too large for dtype('float32').
原因是我们在做语料库向量化处理的时候有一些无意义的评论,所包含的内容都在停用词之中,向量化之后就变成了空值,所以,向量化之后还需要对数据值进行空值检验,将其中为空的向量指定一个缺省值。我在这里省事就指定为0了
trainDataVecs[np.isnan(trainDataVecs)] = 0
testDataVecs[np.isnan(testDataVecs)] = 0
from sklearn.ensemble import RandomForestClassifier
forest = RandomForestClassifier( n_estimators = 100 )
print ("Fitting a random forest to labeled training data...")
forest = forest.fit( trainDataVecs, train["Sentiment"] )
训练后进行预测,输出预测结果
# Test & extract results
result = forest.predict( testDataVecs )
# Write the test results
output = pd.DataFrame( data={"id":test["PhraseId"], "sentiment":result} )
output.to_csv( "Word2Vec_AverageVectors.csv", index=False, quoting=3 )
到这里大致就完成了,但是,我们希望使用RNN来处理,接下来就是构建LSTM作为分类器。