卷积神经网络(CNN)的应用领域广泛,包括:
1.自然语言处理:CNN能够处理文本数据,执行如文本分类、情感分析和语言建模等任务。通过将文本转换为向量形式,CNN能够识别并利用文本中的关键特征进行分类或生成。
2.图像识别与处理:在图像处理领域,CNN展现出卓越的性能。例如,在MNIST手写数字识别任务中,CNN能够准确识别数字。此外,CNN也被用于人脸识别、目标检测和图像分类等多个任务。
3.语音信号处理:CNN在语音处理方面同样有着显著的应用,如语音识别和语音合成。通过学习语音信号的特性,CNN能够实现高效的语音识别和合成,为语音助手和语音输入技术提供了强大的支持。
4.计算机视觉与视频处理:CNN在计算机视觉领域,尤其是在视频处理任务中,如视频分类和目标跟踪,也表现出色。通过对每一帧图像的分析,CNN能够学习到视频的动态特征,从而进行有效的分类和跟踪。
卷积神经网络的优势包括:
1.强大的特征提取能力:CNN能够自动提取图像、文本或语音信号中的关键特征,无需手动特征提取。这使得CNN能够适应各种数据类型,并且随着数据量的增加,其性能也会相应提升。
2.参数共享与稀疏连接:CNN通过参数共享和稀疏连接减少模型参数,降低复杂度。这不仅提高了数据利用效率,还增强了模型的泛化能力,同时减少了计算和存储需求。
cnn图像处理和文本处理的区别是处理内容和卷积不同。根据查询相关资料信息,cnn图像处理和文本处理在处理内容和卷积方面有所区别。
1、处理内容不同:cnn图像处理是基于图像任务的平移不变性进行设计图像,文本处理是对代码进行文本注释。
2、卷积不同:cnn图像处理是二维卷积,文本处理是一维卷积。
选自AhmedBESBES
作者:AhmedBesbes
机器之心编译
参与:李诗萌、路
本文介绍了用于文本分类任务的7个模型,包括传统的词袋模型、循环神经网络,也有常用于计算机视觉任务的卷积神经网络,以及RNN+CNN。
本文是我之前写过的一篇基于推特数据进行情感分析的文章(
https://ahmedbesbes.com/sentiment-analysis-on-twitter-using-word2vec-and-keras.html)的延伸内容。那时我建立了一个简单的模型:基于keras训练的两层前馈神经网络。用组成推文的词嵌入的加权平均值作为文档向量来表示输入推文。
我用的嵌入是用gensim基于语料库从头训练出来的word2vec模型。该是一个二分类任务,准确率能达到79%。
本文目标在于探索其他在相同数据集上训练出来的NLP模型,然后在给定的测试集上对这些模型的性能进行评估。
我们将通过不同的模型(从依赖于词袋表征的简单模型到部署了卷积/循环网络的复杂模型)了解能否得到高于79%的准确率!
首先,将从简单的模型开始,逐步增加模型的复杂度。这项工作是为了说明简单的模型也能很有效。
我会进行这些尝试:
文末附有这些NLP技术的样板代码。这些代码可以帮助你开启自己的NLP项目并获得最优结果(这些模型中有一些非常强大)。
我们还可以提供一个综合基准,我们可以利用该基准分辨哪个模型最适合预测推文中的情绪。
在相关的GitHub库中还有不同的模型、这些模型的预测结果以及测试集。你可以自己尝试并得到可信的结果。
importosimportreimportwarningswarnings.simplefilter("ignore",UserWarning)frommatplotlibimportpyplotasplt%matplotlibinlineimportpandasaspdpd.options.mode.chained_assignment=Noneimportnumpyasnpfromstringimportpunctuationfromnltk.tokenizeimportword_tokenizefromsklearn.model_selectionimporttrain_test_splitfromsklearn.feature_extraction.textimportTfidfVectorizerfromsklearn.linear_modelimportLogisticRegressionfromsklearn.metricsimportaccuracy_score,auc,roc_auc_scorefromsklearn.externalsimportjoblibimportscipyfromscipy.sparseimporthstack
0.数据预处理
你可以从该链接(
http://thinknook.com/twitter-sentiment-analysis-training-corpus-dataset-2012-09-22/)下载数据集。
加载数据并提取所需变量(情感及情感文本)。
该数据集包含1,578,614个分好类的推文,每一行都用1(积极情绪)和0(消极情绪)进行了标记。
作者建议用1/10的数据进行测试,其余数据用于训练。
data=pd.read_csv('./data/tweets.csv',encoding='latin1',usecols=['Sentiment','SentimentText'])data.columns=['sentiment','text']data=data.sample(frac=1,random_state=42)print(data.shape)(1578614,2)forrowindata.head(10).iterrows():print(row[1]['sentiment'],row[1]['text'])1http://www.popsugar.com/2999655keepvotingforrobertpattinsoninthepopsugar100aswell!!1@GamrothTaylorIamstartingtoworryaboutyou,onlyIhaveNavySealtypesleephours.0sunburned...nosunbaked!ow.ithurtstosit.1Celebratingmy50thbirthdaybydoingexactlythesameasIdoeveryotherday-workingonourwebsites.It'sjustanotherday.1LeahandAidenGosselinarethecutestkidsonthefaceoftheEarth1@MissHell23Oh.Ididn'tevennotice.0WTFiswrongwithme?!!!I'mcompletelymiserable.Ineedtosnapoutofthis0WashavingthebesttimeinthegymuntilIgottothecarandhadmessageswaitingforme...backtothedownstage!1@JENTSYYohwhathappened??0@catawuGhodforbidheshouldfeelresponsibleforanything!推文数据中存在很多噪声,我们删除了推文中的网址、主题标签和用户提及来清理数据。
deftokenize(tweet):tweet=re.sub(r'http\S+','',tweet)tweet=re.sub(r"#(\w+)",'',tweet)tweet=re.sub(r"@(\w+)",'',tweet)tweet=re.sub(r'[^\w\s]','',tweet)tweet=tweet.strip().lower()tokens=word_tokenize(tweet)returntokens
将清理好的数据保存在硬盘上。
data['tokens']=data.text.progress_map(tokenize)data['cleaned_text']=data['tokens'].map(lambdatokens:''.join(tokens))data[['sentiment','cleaned_text']].to_csv('./data/cleaned_text.csv')data=pd.read_csv('./data/cleaned_text.csv')print(data.shape)(1575026,2)data.head()
既然数据集已经清理干净了,就可以准备分割训练集和测试集来建立模型了。
本文数据都是用这种方式分割的。
x_train,x_test,y_train,y_test=train_test_split(data['cleaned_text'],data['sentiment'],test_size=0.1,random_state=42,stratify=data['sentiment'])print(x_train.shape,x_test.shape,y_train.shape,y_test.shape)(1417523,)(157503,)(1417523,)(157503,)
将测试集标签存储在硬盘上以便后续使用。
pd.DataFrame(y_test).to_csv('./predictions/y_true.csv',index=False,encoding='utf-8')
接下来就可以应用机器学习方法了。
1.基于词级ngram的词袋模型
那么,什么是n-gram呢?
如图所示,ngram是将可在源文本中找到的长度为n的相邻词的所有组合。
我们的模型将以unigrams(n=1)和bigrams(n=2)为特征。
用矩阵表示数据集,矩阵的每一行表示一条推文,每一列表示从推文(已经经过分词和清理)中提取的特征(一元模型或二元模型)。每个单元格是tf-idf分数(也可以用更简单的值,但tf-idf比较通用且效果较好)。我们将该矩阵称为文档-词项矩阵。
略经思考可知,拥有150万推文的语料库的一元模型和二元模型去重后的数量还是很大的。事实上,出于计算力的考虑,我们可将这个数设置为固定值。你可以通过交叉验证来确定这个值。
在向量化之后,语料库如下图所示:
Ilikepizzaalot假设使用上述特征让模型对这句话进行预测。
由于我们使用的是一元模型和二元模型后,因此模型提取出了下列特征:
i,like,pizza,a,lot,ilike,likepizza,pizzaa,alot因此,句子变成了大小为N(分词总数)的向量,这个向量中包含0和这些ngram的tf-idf分数。所以接下来其实是要处理这个大而稀疏的向量。
一般而言,线性模型可以很好地处理大而稀疏的数据。此外,与其他模型相比,线性模型的训练速度也更快。
从过去的经验可知,logistic回归可以在稀疏的tf-idf矩阵上良好地运作。
vectorizer_word=TfidfVectorizer(max_features=40000,min_df=5,max_df=0.5,analyzer='word',stop_words='english',ngram_range=(1,2))vectorizer_word.fit(x_train,leave=False)tfidf_matrix_word_train=vectorizer_word.transform(x_train)tfidf_matrix_word_test=vectorizer_word.transform(x_test)
在为训练集和测试集生成了tf-idf矩阵后,就可以建立第一个模型并对其进行测试。
tf-idf矩阵是logistic回归的特征。
lr_word=LogisticRegression(solver='sag',verbose=2)lr_word.fit(tfidf_matrix_word_train,y_train)
一旦训练好模型后,就可以将其应用于测试数据以获得预测值。然后将这些值和模型一并存储在硬盘上。
joblib.dump(lr_word,'./models/lr_word_ngram.pkl')y_pred_word=lr_word.predict(tfidf_matrix_word_test)pd.DataFrame(y_pred_word,columns=['y_pred']).to_csv('./predictions/lr_word_ngram.csv',index=False)
得到准确率:
y_pred_word=pd.read_csv('./predictions/lr_word_ngram.csv')print(accuracy_score(y_test,y_pred_word))0.782042246814
第一个模型得到了78.2%的准确率!真不赖。接下来了解一下第二个模型。
2.基于字符级ngram的词袋模型
我们从未说过ngram仅为词服务,也可将其应用于字符上。
如你所见,我们将对字符级ngram使用与图中一样的代码,现在直接来看4-grams建模。
基本上这意味着,像「Ilikethismovie」这样的句子会有下列特征:
I,l,i,k,e,...,Ili,lik,like,...,this,...,ism,smo,movi,...字符级ngram很有效,在语言建模任务中,甚至可以比分词表现得更好。像垃圾邮件过滤或自然语言识别这样的任务就高度依赖字符级ngram。
与之前学习单词组合的模型不同,该模型学习的是字母组合,这样就可以处理单词的形态构成。
基于字符的表征的一个优势是可以更好地解决单词拼写错误的问题。
我们来运行同样的流程:
vectorizer_char=TfidfVectorizer(max_features=40000,min_df=5,max_df=0.5,analyzer='char',ngram_range=(1,4))vectorizer_char.fit(tqdm_notebook(x_train,leave=False));tfidf_matrix_char_train=vectorizer_char.transform(x_train)tfidf_matrix_char_test=vectorizer_char.transform(x_test)lr_char=LogisticRegression(solver='sag',verbose=2)lr_char.fit(tfidf_matrix_char_train,y_train)y_pred_char=lr_char.predict(tfidf_matrix_char_test)joblib.dump(lr_char,'./models/lr_char_ngram.pkl')pd.DataFrame(y_pred_char,columns=['y_pred']).to_csv('./predictions/lr_char_ngram.csv',index=False)y_pred_char=pd.read_csv('./predictions/lr_char_ngram.csv')print(accuracy_score(y_test,y_pred_char))0.80420055491
80.4%的准确率!字符级ngram模型的性能要比词级的ngram更好。
3.基于词级ngram和字符级ngram的词袋模型
与词级ngram的特征相比,字符级ngram特征似乎提供了更好的准确率。那么将字符级ngram和词级ngram结合效果又怎么样呢?
我们将两个tf-idf矩阵连接在一起,建立一个新的、混合tf-idf矩阵。该模型有助于学习单词形态结构以及与这个单词大概率相邻单词的形态结构。
将这些属性结合在一起。
tfidf_matrix_word_char_train=hstack((tfidf_matrix_word_train,tfidf_matrix_char_train))tfidf_matrix_word_char_test=hstack((tfidf_matrix_word_test,tfidf_matrix_char_test))lr_word_char=LogisticRegression(solver='sag',verbose=2)lr_word_char.fit(tfidf_matrix_word_char_train,y_train)y_pred_word_char=lr_word_char.predict(tfidf_matrix_word_char_test)joblib.dump(lr_word_char,'./models/lr_word_char_ngram.pkl')pd.DataFrame(y_pred_word_char,columns=['y_pred']).to_csv('./predictions/lr_word_char_ngram.csv',index=False)y_pred_word_char=pd.read_csv('./predictions/lr_word_char_ngram.csv')print(accuracy_score(y_test,y_pred_word_char))0.81423845895
得到了81.4%的准确率。该模型只加了一个整体单元,但结果比之前的两个都要好。
关于词袋模型
现在要用到深度学习模型了。深度学习模型的表现优于词袋模型是因为深度学习模型能够捕捉到句子中单词间的顺序依赖关系。这可能要归功于循环神经网络这一特殊神经网络结构的出现了。
本文并未涵盖RNN的理论基础,但该链接(
http://colah.github.io/posts/2015-08-Understanding-LSTMs/)中的内容值得一读。这篇文章来源于CristopherOlah的博客,详细叙述了一种特殊的RNN模型:长短期记忆网络(LSTM)。
在开始之前,要先设置一个深度学习专用的环境,以便在TensorFlow上使用Keras。诚实地讲,我试着在个人笔记本上运行这些代码,但考虑到数据集的大小和RNN架构的复杂程度,这是很不实际的。还有一个很好的选择是AWS。我一般在EC2p2.xlarge实例上用深度学习AMI(
https://aws.amazon.com/marketplace/pp/B077GCH38C?qid=1527197041958sr=0-1ref_=srh_res_product_title)。亚马逊AMI是安装了所有包(TensorFlow、PyTorch和Keras等)的预先配置过的VM图。强烈推荐大家使用!
fromkeras.preprocessing.textimportTokenizerfromkeras.preprocessing.textimporttext_to_word_sequencefromkeras.preprocessing.sequenceimportpad_sequencesfromkeras.modelsimportModelfromkeras.modelsimportSequentialfromkeras.layersimportInput,Dense,Embedding,Conv1D,Conv2D,MaxPooling1D,MaxPool2Dfromkeras.layersimportReshape,Flatten,Dropout,Concatenatefromkeras.layersimportSpatialDropout1D,concatenatefromkeras.layersimportGRU,Bidirectional,GlobalAveragePooling1D,GlobalMaxPooling1Dfromkeras.callbacksimportCallbackfromkeras.optimizersimportAdamfromkeras.callbacksimportModelCheckpoint,EarlyStoppingfromkeras.modelsimportload_modelfromkeras.utils.vis_utilsimportplot_model
4.没有预训练词嵌入的循环神经网络
RNN可能看起来很可怕。尽管它们因为复杂而难以理解,但非常有趣。RNN模型封装了一个非常漂亮的设计,以克服传统神经网络在处理序列数据(文本、时间序列、视频、DNA序列等)时的短板。
RNN是一系列神经网络的模块,它们彼此连接像锁链一样。每一个都将消息向后传递。强烈推荐大家从Colah的博客中深入了解它的内部机制,下面的图就来源于此。
我们要处理的序列类型是文本数据。对意义而言,单词顺序很重要。RNN考虑到了这一点,它可以捕捉长期依赖关系。
为了在文本数据上使用Keras,我们首先要对数据进行预处理。可以用Keras的Tokenizer类。该对象用num_words作为参数,num_words是根据词频进行分词后保留下来的最大词数。
MAX_NB_WORDS=80000tokenizer=Tokenizer(num_words=MAX_NB_WORDS)tokenizer.fit_on_texts(data['cleaned_text'])
当分词器适用于数据时,我们就可以用分词器将文本字符级ngram转换为数字序列。
这些数字表示每个单词在字典中的位置(将其视为映射)。
如下例所示:
x_train[15]'breakfasttimehappytime'
这里说明了分词器是如何将其转换为数字序列的。
tokenizer.texts_to_sequences([x_train[15]])[[530,50,119,50]]
接下来在训练序列和测试序列中应用该分词器:
train_sequences=tokenizer.texts_to_sequences(x_train)test_sequences=tokenizer.texts_to_sequences(x_test)
将推文映射到整数列表中。但是由于长度不同,还是没法将它们在矩阵中堆叠在一起。还好Keras允许用0将序列填充至最大长度。我们将这个长度设置为35(这是推文中的最大分词数)。
MAX_LENGTH=35padded_train_sequences=pad_sequences(train_sequences,maxlen=MAX_LENGTH)padded_test_sequences=pad_sequences(test_sequences,maxlen=MAX_LENGTH)padded_train_sequencesarray([[0,0,0,...,2383,284,9],[0,0,0,...,13,30,76],[0,0,0,...,19,37,45231],...,[0,0,0,...,43,502,1653],[0,0,0,...,5,1045,890],[0,0,0,...,13748,38750,154]])padded_train_sequences.shape(1417523,35)
现在就可以将数据传入RNN了。
以下是我将使用的架构的一些元素:
双向GRU的输出是有维度的(批尺寸、时间步和单元)。这意味着如果用的是经典的256的批尺寸,维度将会是(256,35,200)。
defget_simple_rnn_model():embedding_dim=300embedding_matrix=np.random.random((MAX_NB_WORDS,embedding_dim))inp=Input(shape=(MAX_LENGTH,))x=Embedding(input_dim=MAX_NB_WORDS,output_dim=embedding_dim,input_length=MAX_LENGTH,weights=[embedding_matrix],trainable=True)(inp)x=SpatialDropout1D(0.3)(x)x=Bidirectional(GRU(100,return_sequences=True))(x)avg_pool=GlobalAveragePooling1D()(x)max_pool=GlobalMaxPooling1D()(x)conc=concatenate([avg_pool,max_pool])outp=Dense(1,activation="sigmoid")(conc)model=Model(inputs=inp,outputs=outp)model.compile(loss='binary_crossentropy',optimizer='adam',metrics=['accuracy'])returnmodelrnn_simple_model=get_simple_rnn_model()
该模型的不同层如下所示:
plot_model(rnn_simple_model,to_file='./images/article_5/rnn_simple_model.png',show_shapes=True,show_layer_names=True)
在训练期间使用了模型检查点。这样可以在每个epoch的最后将最佳模型(可以用准确率度量)自动存储(在硬盘上)。
filepath="./models/rnn_no_embeddings/weights-improvement-{epoch:02d}-{val_acc:.4f}.hdf5"checkpoint=ModelCheckpoint(filepath,monitor='val_acc',verbose=1,save_best_only=True,mode='max')batch_size=256epochs=2history=rnn_simple_model.fit(x=padded_train_sequences,y=y_train,validation_data=(padded_test_sequences,y_test),batch_size=batch_size,callbacks=[checkpoint],epochs=epochs,verbose=1)best_rnn_simple_model=load_model('./models/rnn_no_embeddings/weights-improvement-01-0.8262.hdf5')y_pred_rnn_simple=best_rnn_simple_model.predict(padded_test_sequences,verbose=1,batch_size=2048)y_pred_rnn_simple=pd.DataFrame(y_pred_rnn_simple,columns=['prediction'])y_pred_rnn_simple['prediction']=y_pred_rnn_simple['prediction'].map(lambdap:1ifp>=0.5else0)y_pred_rnn_simple.to_csv('./predictions/y_pred_rnn_simple.csv',index=False)y_pred_rnn_simple=pd.read_csv('./predictions/y_pred_rnn_simple.csv')print(accuracy_score(y_test,y_pred_rnn_simple))0.826219183127
准确率达到了82.6%!这真是很不错的结果了!现在的模型表现已经比之前的词袋模型更好了,因为我们将文本的序列性质考虑在内了。
还能做得更好吗?
5.用GloVe预训练词嵌入的循环神经网络
在最后一个模型中,嵌入矩阵被随机初始化了。那么如果用预训练过的词嵌入对其进行初始化又当如何呢?举个例子:假设在语料库中有「pizza」这个词。遵循之前的架构对其进行初始化后,可以得到一个300维的随机浮点值向量。这当然是很好的。这很好实现,而且这个嵌入可以在训练过程中进行调整。但你还可以使用在很大的语料库上训练出来的另一个模型,为「pizza」生成词嵌入来代替随机选择的向量。这是一种特殊的迁移学习。
使用来自外部嵌入的知识可以提高RNN的精度,因为它整合了这个单词的相关新信息(词汇和语义),而这些信息是基于大规模数据语料库训练和提炼出来的。
我们使用的预训练嵌入是GloVe。
官方描述是这样的:GloVe是一种获取单词向量表征的无监督学习算法。该算法的训练基于语料库全局词-词共现数据,得到的表征展示出词向量空间有趣的线性子结构。
本文使用的GloVe嵌入的训练数据是数据量很大的网络抓取,包括:
下载压缩文件要2.03GB。请注意,该文件无法轻松地加载在标准笔记本电脑上。
GloVe嵌入有300维。
GloVe嵌入来自原始文本数据,在该数据中每一行都包含一个单词和300个浮点数(对应嵌入)。所以首先要将这种结构转换为Python字典。
defget_coefs(word,*arr):try:returnword,np.asarray(arr,dtype='float32')except:returnNone,Noneembeddings_index=dict(get_coefs(*o.strip().split())forointqdm_notebook(open('./embeddings/glove.840B.300d.txt')))embed_size=300forkintqdm_notebook(list(embeddings_index.keys())):v=embeddings_index[k]try:ifv.shape!=(embed_size,):embeddings_index.pop(k)except:passembeddings_index.pop(None)
一旦创建了嵌入索引,我们就可以提取所有的向量,将其堆叠在一起并计算它们的平均值和标准差。
values=list(embeddings_index.values())all_embs=np.stack(values)emb_mean,emb_std=all_embs.mean(),all_embs.std()
现在生成了嵌入矩阵。按照mean=emb_mean和std=emb_std的正态分布对矩阵进行初始化。遍历语料库中的80000个单词。对每一个单词而言,如果这个单词存在于GloVe中,我们就可以得到这个单词的嵌入,如果不存在那就略过。
word_index=tokenizer.word_indexnb_words=MAX_NB_WORDSembedding_matrix=np.random.normal(emb_mean,emb_std,(nb_words,embed_size))oov=0forword,iintqdm_notebook(word_index.items()):ifi>=MAX_NB_WORDS:continueembedding_vector=embeddings_index.get(word)ifembedding_vectorisnotNone:embedding_matrix[i]=embedding_vectorelse:oov+=1print(oov)defget_rnn_model_with_glove_embeddings():embedding_dim=300inp=Input(shape=(MAX_LENGTH,))x=Embedding(MAX_NB_WORDS,embedding_dim,weights=[embedding_matrix],input_length=MAX_LENGTH,trainable=True)(inp)x=SpatialDropout1D(0.3)(x)x=Bidirectional(GRU(100,return_sequences=True))(x)avg_pool=GlobalAveragePooling1D()(x)max_pool=GlobalMaxPooling1D()(x)conc=concatenate([avg_pool,max_pool])outp=Dense(1,activation="sigmoid")(conc)model=Model(inputs=inp,outputs=outp)model.compile(loss='binary_crossentropy',optimizer='adam',metrics=['accuracy'])returnmodelrnn_model_with_embeddings=get_rnn_model_with_glove_embeddings()filepath="./models/rnn_with_embeddings/weights-improvement-{epoch:02d}-{val_acc:.4f}.hdf5"checkpoint=ModelCheckpoint(filepath,monitor='val_acc',verbose=1,save_best_only=True,mode='max')batch_size=256epochs=4history=rnn_model_with_embeddings.fit(x=padded_train_sequences,y=y_train,validation_data=(padded_test_sequences,y_test),batch_size=batch_size,callbacks=[checkpoint],epochs=epochs,verbose=1)best_rnn_model_with_glove_embeddings=load_model('./models/rnn_with_embeddings/weights-improvement-03-0.8372.hdf5')y_pred_rnn_with_glove_embeddings=best_rnn_model_with_glove_embeddings.predict(padded_test_sequences,verbose=1,batch_size=2048)y_pred_rnn_with_glove_embeddings=pd.DataFrame(y_pred_rnn_with_glove_embeddings,columns=['prediction'])y_pred_rnn_with_glove_embeddings['prediction']=y_pred_rnn_with_glove_embeddings['prediction'].map(lambdap:1ifp>=0.5else0)y_pred_rnn_with_glove_embeddings.to_csv('./predictions/y_pred_rnn_with_glove_embeddings.csv',index=False)y_pred_rnn_with_glove_embeddings=pd.read_csv('./predictions/y_pred_rnn_with_glove_embeddings.csv')print(accuracy_score(y_test,y_pred_rnn_with_glove_embeddings))0.837203100893准确率达到了83.7%!来自外部词嵌入的迁移学习起了作用!本教程剩余部分都会在嵌入矩阵中使用GloVe嵌入。
6.多通道卷积神经网络
这一部分实验了我曾了解过的卷积神经网络结构(
http://www.wildml.com/2015/11/understanding-convolutional-neural-networks-for-nlp/)。CNN常用于计算机视觉任务。但最近我试着将其应用于NLP任务,而结果也希望满满。
简要了解一下当在文本数据上使用卷积网络时会发生什么。为了解释这一点,我从wildm.com(一个很好的博客)中找到了这张非常有名的图(如下所示)。
了解一下使用的例子:Ilikethismovieverymuch!(7个分词)
背后的原理是什么?
检测到特殊模式会激活每一次卷积的结果。通过改变卷积核的大小和连接它们的输出,你可以检测多个尺寸(2个、3个或5个相邻单词)的模式。
模式可以是像是「我讨厌」、「非常好」这样的表达式(词级的ngram?),因此CNN可以在不考虑其位置的情况下从句子中分辨它们。
defget_cnn_model():embedding_dim=300filter_sizes=[2,3,5]num_filters=256drop=0.3inputs=Input(shape=(MAX_LENGTH,),dtype='int32')embedding=Embedding(input_dim=MAX_NB_WORDS,output_dim=embedding_dim,weights=[embedding_matrix],input_length=MAX_LENGTH,trainable=True)(inputs)reshape=Reshape((MAX_LENGTH,embedding_dim,1))(embedding)conv_0=Conv2D(num_filters,kernel_size=(filter_sizes[0],embedding_dim),padding='valid',kernel_initializer='normal',activation='relu')(reshape)conv_1=Conv2D(num_filters,kernel_size=(filter_sizes[1],embedding_dim),padding='valid',kernel_initializer='normal',activation='relu')(reshape)conv_2=Conv2D(num_filters,kernel_size=(filter_sizes[2],embedding_dim),padding='valid',kernel_initializer='normal',activation='relu')(reshape)maxpool_0=MaxPool2D(pool_size=(MAX_LENGTH-filter_sizes[0]+1,1),strides=(1,1),padding='valid')(conv_0)maxpool_1=MaxPool2D(pool_size=(MAX_LENGTH-filter_sizes[1]+1,1),strides=(1,1),padding='valid')(conv_1)maxpool_2=MaxPool2D(pool_size=(MAX_LENGTH-filter_sizes[2]+1,1),strides=(1,1),padding='valid')(conv_2)concatenated_tensor=Concatenate(axis=1)([maxpool_0,maxpool_1,maxpool_2])flatten=Flatten()(concatenated_tensor)dropout=Dropout(drop)(flatten)output=Dense(units=1,activation='sigmoid')(dropout)model=Model(inputs=inputs,outputs=output)adam=Adam(lr=1e-4,beta_1=0.9,beta_2=0.999,epsilon=1e-08,decay=0.0)model.compile(optimizer=adam,loss='binary_crossentropy',metrics=['accuracy'])returnmodelcnn_model_multi_channel=get_cnn_model()plot_model(cnn_model_multi_channel,to_file='./images/article_5/cnn_model_multi_channel.png',show_shapes=True,show_layer_names=True)
filepath="./models/cnn_multi_channel/weights-improvement-{epoch:02d}-{val_acc:.4f}.hdf5"checkpoint=ModelCheckpoint(filepath,monitor='val_acc',verbose=1,save_best_only=True,mode='max')batch_size=256epochs=4history=cnn_model_multi_channel.fit(x=padded_train_sequences,y=y_train,validation_data=(padded_test_sequences,y_test),batch_size=batch_size,callbacks=[checkpoint],epochs=epochs,verbose=1)best_cnn_model=load_model('./models/cnn_multi_channel/weights-improvement-04-0.8264.hdf5')y_pred_cnn_multi_channel=best_cnn_model.predict(padded_test_sequences,verbose=1,batch_size=2048)y_pred_cnn_multi_channel=pd.DataFrame(y_pred_cnn_multi_channel,columns=['prediction'])y_pred_cnn_multi_channel['prediction']=y_pred_cnn_multi_channel['prediction'].map(lambdap:1ifp>=0.5else0)y_pred_cnn_multi_channel.to_csv('./predictions/y_pred_cnn_multi_channel.csv',index=False)y_pred_cnn_multi_channel=pd.read_csv('./predictions/y_pred_cnn_multi_channel.csv')print(accuracy_score(y_test,y_pred_cnn_multi_channel))0.826409655689
准确率为82.6%,没有RNN那么高,但是还是比BOW模型要好。也许调整超参数(滤波器的数量和大小)会带来一些提升?
7.RNN+CNNRNN很强大。但有人发现可以通过在循环层上叠加卷积层使网络变得更强大。
这背后的原理在于RNN允许嵌入序列和之前单词的相关信息,CNN可以使用这些嵌入并从中提取局部特征。这两个层一起工作可以称得上是强强联合。
更多相关信息请参阅:
http://konukoii.com/blog/2018/02/19/twitter-sentiment-analysis-using-combined-lstm-cnn-models/
defget_rnn_cnn_model():embedding_dim=300inp=Input(shape=(MAX_LENGTH,))x=Embedding(MAX_NB_WORDS,embedding_dim,weights=[embedding_matrix],input_length=MAX_LENGTH,trainable=True)(inp)x=SpatialDropout1D(0.3)(x)x=Bidirectional(GRU(100,return_sequences=True))(x)x=Conv1D(64,kernel_size=2,padding="valid",kernel_initializer="he_uniform")(x)avg_pool=GlobalAveragePooling1D()(x)max_pool=GlobalMaxPooling1D()(x)conc=concatenate([avg_pool,max_pool])outp=Dense(1,activation="sigmoid")(conc)model=Model(inputs=inp,outputs=outp)model.compile(loss='binary_crossentropy',optimizer='adam',metrics=['accuracy'])returnmodelrnn_cnn_model=get_rnn_cnn_model()plot_model(rnn_cnn_model,to_file='./images/article_5/rnn_cnn_model.png',show_shapes=True,show_layer_names=True)
filepath="./models/rnn_cnn/weights-improvement-{epoch:02d}-{val_acc:.4f}.hdf5"checkpoint=ModelCheckpoint(filepath,monitor='val_acc',verbose=1,save_best_only=True,mode='max')batch_size=256epochs=4history=rnn_cnn_model.fit(x=padded_train_sequences,y=y_train,validation_data=(padded_test_sequences,y_test),batch_size=batch_size,callbacks=[checkpoint],epochs=epochs,verbose=1)best_rnn_cnn_model=load_model('./models/rnn_cnn/weights-improvement-03-0.8379.hdf5')y_pred_rnn_cnn=best_rnn_cnn_model.predict(padded_test_sequences,verbose=1,batch_size=2048)y_pred_rnn_cnn=pd.DataFrame(y_pred_rnn_cnn,columns=['prediction'])y_pred_rnn_cnn['prediction']=y_pred_rnn_cnn['prediction'].map(lambdap:1ifp>=0.5else0)y_pred_rnn_cnn.to_csv('./predictions/y_pred_rnn_cnn.csv',index=False)y_pred_rnn_cnn=pd.read_csv('./predictions/y_pred_rnn_cnn.csv')print(accuracy_score(y_test,y_pred_rnn_cnn))0.837882453033
这样可得到83.8%的准确率,这也是到现在为止最好的结果。
8.总结
在运行了7个不同的模型后,我们对比了一下:
importseabornassnsfromsklearn.metricsimportroc_auc_scoresns.set_style("whitegrid")sns.set_palette("pastel")predictions_files=os.listdir('./predictions/')predictions_dfs=[]forfinpredictions_files:aux=pd.read_csv('./predictions/{0}'.format(f))aux.columns=[f.strip('.csv')]predictions_dfs.append(aux)predictions=pd.concat(predictions_dfs,axis=1)scores={}forcolumnintqdm_notebook(predictions.columns,leave=False):ifcolumn!='y_true':s=accuracy_score(predictions['y_true'].values,predictions[column].values)scores[column]=sscores=pd.DataFrame([scores],index=['accuracy'])mapping_name=dict(zip(list(scores.columns),['Charngram+LR','(Word+Charngram)+LR','Wordngram+LR','CNN(multichannel)','RNN+CNN','RNNnoembd.','RNN+GloVeembds.']))scores=scores.rename(columns=mapping_name)scores=scores[['Wordngram+LR','Charngram+LR','(Word+Charngram)+LR','RNNnoembd.','RNN+GloVeembds.','CNN(multichannel)','RNN+CNN']]scores=scores.Tax=scores['accuracy'].plot(kind='bar',figsize=(16,5),ylim=(scores.accuracy.min()*0.97,scores.accuracy.max()*1.01),color='red',alpha=0.75,rot=45,fontsize=13)ax.set_title('Comparativeaccuracyofthedifferentmodels')foriinax.patches:ax.annotate(str(round(i.get_height(),3)),(i.get_x()+0.1,i.get_height()*1.002),color='dimgrey',fontsize=14)
我们可以很快地看出在这些模型的预测值之间的关联。
fig=plt.figure(figsize=(10,5))sns.heatmap(predictions.drop('y_true',axis=1).corr(method='kendall'),cmap="Blues",annot=True);
结论
以下是几条我认为值得与大家分享的发现:
这篇文章很长。希望本文能对大家有所帮助。