在这篇文章中,笔者将讨论自然语言处理中文本分类的相关问题。笔者将使用一个复旦大学开源的文本分类语料库,对文本分类的一般流程和常用模型进行探讨。首先,笔者会创建一个非常基础的初始模型,然后使用不同的特征进行改进。 接下来,笔者还将讨论如何使用深度神经网络来解决NLP问题,并在文章末尾以一般关于集成的一些想法结束这篇文章。

本文覆盖的NLP方法有:

  • TF-IDF
  • Count Features
  • Logistic Regression
  • Naive Bayes
  • SVM
  • Xgboost
  • Grid Search
  • Word Vectors
  • Dense Network
  • LSTM
  • GRU
  • Ensembling

NOTE: 笔者并不能保证你学习了本notebook之后就能在NLP相关比赛中获得非常高的分数。 但是,如果你正确地“吃透”它,并根据实际情况适时作出一些调整,你可以获得非常高的分数。 废话不多说,让我们开始导入一些我将要使用的重要python模块。

#载入接下来分析用的库
import pandas as pd
import numpy as np
import xgboost as xgb
from tqdm import tqdm
from sklearn.svm import SVC
from keras.models import Sequential
from keras.layers.recurrent import LSTM, GRU
from keras.layers.core import Dense, Activation, Dropout
from keras.layers.embeddings import Embedding
from keras.layers.normalization import BatchNormalization
from keras.utils import np_utils
from sklearn import preprocessing, decomposition, model_selection, metrics, pipeline
from sklearn.model_selection import GridSearchCV
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
from sklearn.decomposition import TruncatedSVD
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.naive_bayes import MultinomialNB
from keras.layers import GlobalMaxPooling1D, Conv1D, MaxPooling1D, Flatten, Bidirectional, SpatialDropout1D
from keras.preprocessing import sequence, text
from keras.callbacks import EarlyStopping
from nltk import word_tokenize

接下来是加载并检视数据集

data=pd.read_excel('/home/kesci/input/Chinese_NLP6474/复旦大学中文文本分类语料.xlsx','sheet1') 
data.head()
  分类 正文
0 艺术 【 文献号 】1-2432\n【原文出处】出版发行研究\n【原刊地名】京\n【原刊期号】1...
1 艺术 【 文献号 】1-2435\n【原文出处】扬州师院学报:社科版\n【原刊期号】199504...
2 艺术 【 文献号 】1-2785\n【原文出处】南通师专学报:社科版\n【原刊期号】199503...
3 艺术 【 文献号 】1-3021\n【原文出处】社会科学战线\n【原刊地名】长春\n【原刊期号】...
4 艺术 【 文献号 】1-3062\n【原文出处】上海文化\n【原刊期号】199505\n【原刊
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 9249 entries, 0 to 9248
Data columns (total 2 columns):
分类    9249 non-null object
正文    9249 non-null object
dtypes: object(2)
memory usage: 144.6+ KB
data.分类.unique()
array(['艺术', '文学', '哲学', '通信', '能源', '历史', '矿藏', '空间', '教育', '交通', '计算机','环境', '电子', '农业', '体育', '时政', '医疗', '经济', '法律'], dtype=object)
import jieba
jieba.enable_parallel() #并行分词开启
data['文本分词'] = data['正文'].apply(lambda i:jieba.cut(i) )
data['文本分词'] =[' '.join(i) for i in data['文本分词']]
data.head()
分类 正文 文本分词
0 艺术 【 文献号 】1-2432\n【原文出处】出版发行研究\n【原刊地名】京\n【原刊期号】1...  【 文献号 】 1 - 2432 \n 【 原文 出处 】 出版发行 研究 \n...
1 艺术 【 文献号 】1-2435\n【原文出处】扬州师院学报:社科版\n【原刊期号】199504...  【 文献号 】 1 - 2435 \n 【 原文 出处 】 扬州 师院 学报 :...
2 艺术 【 文献号 】1-2785\n【原文出处】南通师专学报:社科版\n【原刊期号】199503...  【 文献号 】 1 - 2785 \n 【 原文 出处 】 南通 师专 学报 :...
3 艺术 【 文献号 】1-3021\n【原文出处】社会科学战线\n【原刊地名】长春\n【原刊期号】...  【 文献号 】 1 - 3021 \n 【 原文 出处 】 社会科学 战线 \n...
4 艺术 【 文献号 】1-3062\n【原文出处】上海文化\n【原刊期号】199505\n【原刊页...  【 文献号 】 1 - 3062 \n 【 原文 出处 】 上海 文化 \n 【...

这是一个典型的文本多分类问题,需要将文本划分到给定的14个主题上。 针对该问题,笔者采用了kaggle上通用的 Multi-Class Log-Loss 作为评测指标(Evaluation Metric).

def multiclass_logloss(actual, predicted, eps=1e-15):"""对数损失度量(Logarithmic Loss  Metric)的多分类版本。:param actual: 包含actual target classes的数组:param predicted: 分类预测结果矩阵, 每个类别都有一个概率"""# Convert 'actual' to a binary array if it's not already:if len(actual.shape) == 1:actual2 = np.zeros((actual.shape[0], predicted.shape[1]))for i, val in enumerate(actual):actual2[i, val] = 1actual = actual2clip = np.clip(predicted, eps, 1 - eps)rows = actual.shape[0]vsota = np.sum(actual * np.log(clip))return -1.0 / rows * vsota

接下来用scikit-learn中的LabelEncoder将文本标签(Text Label)转化为数字(Integer)

lbl_enc = preprocessing.LabelEncoder()
y = lbl_enc.fit_transform(data.分类.values)

在进一步研究之前,我们必须将数据分成训练和验证集。 我们可以使用scikit-learn的model_selection模块中的train_test_split来完成它。

xtrain, xvalid, ytrain, yvalid = train_test_split(data.文本分词.values, y, stratify=y, random_state=42, test_size=0.1, shuffle=True)

构建基础模型(Basic Models)¶

Let's start building our very first model. 让我们先创建一个非常基础的模型。

我们非常基础的模型(very first model) 是一个简单的 TF-IDF (Term Frequency - Inverse Document Frequency)+逻辑斯底回归(Logistic Regression).

def number_normalizer(tokens):""" 将所有数字标记映射为一个占位符(Placeholder)。对于许多实际应用场景来说,以数字开头的tokens不是很有用,但这样tokens的存在也有一定相关性。 通过将所有数字都表示成同一个符号,可以达到降维的目的。"""return ("#NUMBER" if token[0].isdigit() else token for token in tokens)class NumberNormalizingVectorizer(TfidfVectorizer):def build_tokenizer(self):tokenize = super(NumberNormalizingVectorizer, self).build_tokenizer()return lambda doc: list(number_normalizer(tokenize(doc)))
# 利用刚才创建的NumberNormalizingVectorizer类来提取文本特征,注意里面各类参数的含义,自己去sklearn官方网站找教程看stwlist=[line.strip() for line in open('/home/kesci/input/stopwords7085/停用词汇总.txt',
'r',encoding='utf-8').readlines()]
tfv = NumberNormalizingVectorizer(min_df=3,  max_df=0.5,max_features=None,                 ngram_range=(1, 2), use_idf=True,smooth_idf=True,stop_words = stwlist)# 使用TF-IDF来fit训练集和测试集(半监督学习)
tfv.fit(list(xtrain) + list(xvalid))
xtrain_tfv =  tfv.transform(xtrain)
xvalid_tfv = tfv.transform(xvalid)
#利用提取的TFIDF特征来fit一个简单的Logistic Regression
clf = LogisticRegression(C=1.0,solver='lbfgs',multi_class='multinomial')
clf.fit(xtrain_tfv, ytrain)
predictions = clf.predict_proba(xvalid_tfv)print ("logloss: %0.3f " % multiclass_logloss(yvalid, predictions))
#print(classification_report(predictions, yvalid))
logloss: 0.607 

做完第一个基础模型后,得出的 multiclass logloss 是0.607.

但笔者“贪婪”,想要获得更好的分数。 基于相同模型采用不同的特征,再看看结果如何。

我们也可以使用词汇计数(Word Counts)作为功能,而不是使用TF-IDF。 这可以使用scikit-learn中的CountVectorizer轻松完成。

ctv = CountVectorizer(min_df=3,max_df=0.5,ngram_range=(1,2),stop_words = stwlist)# 使用Count Vectorizer来fit训练集和测试集(半监督学习)
ctv.fit(list(xtrain) + list(xvalid))
xtrain_ctv =  ctv.transform(xtrain)
xvalid_ctv = ctv.transform(xvalid)
#利用提取的word counts特征来fit一个简单的Logistic Regression clf = LogisticRegression(C=1.0,solver='lbfgs',multi_class='multinomial')
clf.fit(xtrain_ctv, ytrain)
predictions = clf.predict_proba(xvalid_ctv)print ("logloss: %0.3f " % multiclass_logloss(yvalid, predictions))#print(classification_report(predictions, yvalid))
logloss: 0.732 

貌似效果不佳,multiclass logloss达到了0.732!!!

接下来,让我们尝试一个非常简单的模型- 朴素贝叶斯,它在以前是非常有名的。

让我们看看当我们在这个数据集上使用朴素贝叶时会发生什么:

#利用提取的TFIDF特征来fitNaive Bayes
clf = MultinomialNB()
clf.fit(xtrain_tfv, ytrain)
predictions = clf.predict_proba(xvalid_tfv)print ("logloss: %0.3f " % multiclass_logloss(yvalid, predictions))#print(classification_report(predictions, yvalid))
logloss: 0.841

朴素贝叶斯模型的表现也不咋地!让我们在基于词汇计数的基础上使用朴素贝叶斯模型,看会发生什么?

#利用提取的word counts特征来fitNaive Bayes
clf = MultinomialNB()
clf.fit(xtrain_ctv, ytrain)
predictions = clf.predict_proba(xvalid_ctv)print ("logloss: %0.3f " % multiclass_logloss(yvalid, predictions))#print(classification_report(predictions, yvalid))
logloss: 3.780 

3.780,这次效果差到爆! 传统文本分类算法里还有一个名叫支持向量机(SVM)。 SVM曾是很多机器学习爱好者的“最爱”。 因此,我们必须在此数据集上尝试SVM。

由于SVM需要花费大量时间,因此在应用SVM之前,我们将使用奇异值分解(Singular Value Decomposition )来减少TF-IDF中的特征数量。

同时,在使用SVM之前,我们还需要将数据标准化(Standardize Data )

#使用SVD进行降维,components设为120,对于SVM来说,SVD的components的合适调整区间一般为120~200
svd = decomposition.TruncatedSVD(n_components=120)
svd.fit(xtrain_tfv)
xtrain_svd = svd.transform(xtrain_tfv)
xvalid_svd = svd.transform(xvalid_tfv)#对从SVD获得的数据进行缩放
scl = preprocessing.StandardScaler()
scl.fit(xtrain_svd)
xtrain_svd_scl = scl.transform(xtrain_svd)
xvalid_svd_scl = scl.transform(xvalid_svd)

现在是时候应用SVM模型进行文本分类了。 在运行以下单元格后,你可以去喝杯茶了---因为这将耗费大量的时间...

#调用下SVM模型
clf = SVC(C=1.0, probability=True) # since we need probabilities
clf.fit(xtrain_svd_scl, ytrain)
predictions = clf.predict_proba(xvalid_svd_scl)print ("logloss: %0.3f " % multiclass_logloss(yvalid, predictions))#print(classification_report(predictions, yvalid))
logloss: 0.347 

看起来,SVM在这些数据上表现还行。

在采用更高级的算法前,让我们再试试Kaggle上应用最流行的算法:xgboost!

# 基于tf-idf特征,使用xgboost
clf = xgb.XGBClassifier(max_depth=7, n_estimators=200, colsample_bytree=0.8, subsample=0.8, nthread=10, learning_rate=0.1)
clf.fit(xtrain_tfv.tocsc(), ytrain)
predictions = clf.predict_proba(xvalid_tfv.tocsc())print ("logloss: %0.3f " % multiclass_logloss(yvalid, predictions))#print(classification_report(predictions, yvalid))
logloss: 0.182

效果不错,比SVM还牛呢!

# 基于word counts特征,使用xgboost
clf = xgb.XGBClassifier(max_depth=7, n_estimators=200, colsample_bytree=0.8, subsample=0.8, nthread=10, learning_rate=0.1)
clf.fit(xtrain_ctv.tocsc(), ytrain)
predictions = clf.predict_proba(xvalid_ctv.tocsc())print ("logloss: %0.3f " % multiclass_logloss(yvalid, predictions))#print(classification_report(predictions, yvalid))
logloss: 0.154 
# 基于tf-idf的svd特征,使用xgboost
clf = xgb.XGBClassifier(max_depth=7, n_estimators=200, colsample_bytree=0.8, subsample=0.8, nthread=10, learning_rate=0.1)
clf.fit(xtrain_svd, ytrain)
predictions = clf.predict_proba(xvalid_svd)print ("logloss: %0.3f " % multiclass_logloss(yvalid, predictions))#print(classification_report(predictions, yvalid))
logloss: 0.394 
# 再对经过数据标准化(Scaling)的tf-idf-svd特征使用xgboost
clf = xgb.XGBClassifier(nthread=10)
clf.fit(xtrain_svd_scl, ytrain)
predictions = clf.predict_proba(xvalid_svd_scl)print ("logloss: %0.3f " % multiclass_logloss(yvalid, predictions))#print(classification_report(predictions, yvalid))
logloss: 0.373

XGBoost的效果似乎挺棒的! 但我觉得还可以进一步优化,因为我还没有做过任何超参数优化。 我很懒,所以我会告诉你该怎么做,你可以自己做!)。 这将在下一节中讨论:

网格搜索(Grid Search)

网格搜索是一种超参数优化的技巧。 如果知道这个技巧,你可以通过获取最优的参数组合来产生良好的文本分类效果。

在本节中,我将讨论使用基于逻辑回归模型的网格搜索。

在开始网格搜索之前,我们需要创建一个评分函数,这可以通过scikit-learn的make_scorer函数完成的。

mll_scorer = metrics.make_scorer(multiclass_logloss, greater_is_better=False, needs_proba=True)

接下来,我们需要一个pipeline。 为了演示,我将使用由SVD(进行特征缩放)和逻辑回归模型组成的pipeline。

#SVD初始化
svd = TruncatedSVD()# Standard Scaler初始化
scl = preprocessing.StandardScaler()# 再一次使用Logistic Regression
lr_model = LogisticRegression()# 创建pipeline
clf = pipeline.Pipeline([('svd', svd),('scl', scl),('lr', lr_model)])

接下来我们需要一个参数网格(A Grid of Parameters):

param_grid = {'svd__n_components' : [120, 180],'lr__C': [0.1, 1.0, 10], 'lr__penalty': ['l1', 'l2']}
# 网格搜索模型(Grid Search Model)初始化
model = GridSearchCV(estimator=clf, param_grid=param_grid, scoring=mll_scorer,verbose=10, n_jobs=-1, iid=True, refit=True, cv=2)#fit网格搜索模型
model.fit(xtrain_tfv, ytrain)  #为了减少计算量,这里我们仅使用xtrain
print("Best score: %0.3f" % model.best_score_)
print("Best parameters set:")
best_parameters = model.best_estimator_.get_params()
for param_name in sorted(param_grid.keys()):print("\t%s: %r" % (param_name, best_parameters[param_name]))

最终得分跟我们之前的SVM的结果相近。 这种技术可用于对xgboost甚至多项式朴素贝叶斯进行超参数调优。 我们将在这里使用tfidf数据,如下所示:

nb_model = MultinomialNB()# 创建pipeline
clf = pipeline.Pipeline([('nb', nb_model)])# 搜索参数设置
param_grid = {'nb__alpha': [0.001, 0.01, 0.1, 1, 10, 100]}# 网格搜索模型(Grid Search Model)初始化
model = GridSearchCV(estimator=clf, param_grid=param_grid, scoring=mll_scorer,verbose=10, n_jobs=-1, iid=True, refit=True, cv=2)# fit网格搜索模型
model.fit(xtrain_tfv, ytrain)  # 为了减少计算量,这里我们仅使用xtrain
print("Best score: %0.3f" % model.best_score_)
print("Best parameters set:")
best_parameters = model.best_estimator_.get_params()
for param_name in sorted(param_grid.keys()):print("\t%s: %r" % (param_name, best_parameters[param_name]))

相比于之前的朴素贝叶斯,本次得分提高了8%!

在NLP问题中,我们习惯于检索词向量(Word Vectors)。 Word向量提供了很多关于数据的见解。 接下来,让我们来深入研究一下。

词向量(Word Vectors)

在不深入细节的情况下,我将解释如何创建语句向量(Sentence Vectors),以及如何基于它们在其上创建机器学习模型。 鄙人是GloVe向量,word2vec和fasttext的粉丝。 在这篇文章中,笔者使用的文本分类模型都是基于Word2vec词向量模型(100维)。

X=data['文本分词']
X=[i.split() for i in X]
X[:2]
# 训练word2vec词向量:
import gensimmodel = gensim.models.Word2Vec(X,min_count =5,window =8,size=100)   # X是经分词后的文本构成的list,也就是tokens的列表的列表
embeddings_index = dict(zip(model.wv.index2word, model.wv.vectors))print('Found %s word vectors.' % len(embeddings_index))
model['汽车']
#该函数会将语句转化为一个标准化的向量(Normalized Vector)
#import nltk
#nltk.download('punkt')def sent2vec(s):import jiebajieba.enable_parallel() #并行分词开启words = str(s).lower()#words = word_tokenize(words)words = jieba.lcut(words)words = [w for w in words if not w in stwlist]#words = [w for w in words if w.isalpha()]M = []for w in words:try:#M.append(embeddings_index[w])M.append(model[w])except:continueM = np.array(M)v = M.sum(axis=0)if type(v) != np.ndarray:return np.zeros(300)return v / np.sqrt((v ** 2).sum())
# 对训练集和验证集使用上述函数,进行文本向量化处理
xtrain_w2v = [sent2vec(x) for x in tqdm(xtrain)]
xvalid_w2v = [sent2vec(x) for x in tqdm(xvalid)]
xtrain_w2v = np.array(xtrain_w2v)
xvalid_w2v = np.array(xvalid_w2v)

让我们看看xgboost在Word2vec词向量特征的表现如何:

# 基于word2vec特征在一个简单的Xgboost模型上进行拟合
clf = xgb.XGBClassifier(nthread=10, silent=False)
clf.fit(xtrain_w2v, ytrain)
predictions = clf.predict_proba(xvalid_w2v)print ("logloss: %0.3f " % multiclass_logloss(yvalid, predictions))
 基于word2vec特征在一个简单的Xgboost模型上进行拟合
clf = xgb.XGBClassifier(max_depth=7, n_estimators=200, colsample_bytree=0.8, subsample=0.8, nthread=10, learning_rate=0.1, silent=False)
clf.fit(xtrain_w2v, ytrain)
predictions = clf.predict_proba(xvalid_w2v)print ("logloss: %0.3f " % multiclass_logloss(yvalid, predictions))
print(classification_report(predictions, yvalid))

我们可以看到,简单的对参数进行微调,就提高基于GloVe词向量特征的xgboost得分! 相信我,你还可以从中继续“压榨”出更优秀的表现!

深度学习(Deep Learning)

这是一个深度学习大行其道的时代! 文本分类问题在它的指引下得到了突飞猛进的发展! 在这里,我们将在GloVe功能上训练LSTM和简单的全连接网络(Dense Network)。
让我们先从全连接网络开始:

# 在使用神经网络前,对数据进行缩放
scl = preprocessing.StandardScaler()
xtrain_w2v_scl = scl.fit_transform(xtrain_w2v)
xvalid_w2v_scl = scl.transform(xvalid_w2v)
# 对标签进行binarize处理
ytrain_enc = np_utils.to_categorical(ytrain)
yvalid_enc = np_utils.to_categorical(yvalid)
#创建1个3层的序列神经网络(Sequential Neural Net)
model = Sequential()model.add(Dense(300, input_dim=300, activation='relu'))
model.add(Dropout(0.2))
model.add(BatchNormalization())model.add(Dense(300, activation='relu'))
model.add(Dropout(0.3))
model.add(BatchNormalization())model.add(Dense(3))
model.add(Activation('softmax'))# 模型编译
model.compile(loss='categorical_crossentropy', optimizer='adam')
model.fit(xtrain_w2v_scl, y=ytrain_enc, batch_size=64, epochs=5, verbose=1, validation_data=(xvalid_w2v_scl, yvalid_enc))

你需要不断的对神经网络的参数进行调优,添加更多层,增加Dropout以获得更好的结果。 在这里,笔者只是简单的实现下,追求速度而不是最终效果,并且它比没有任何优化的xgboost取得了更好的结果:)

为了更进一步,笔者使用LSTM,我们需要对文本数据进行Tokenize:

# 使用 keras tokenizer
token = text.Tokenizer(num_words=None)
max_len = 70token.fit_on_texts(list(xtrain) + list(xvalid))
xtrain_seq = token.texts_to_sequences(xtrain)
xvalid_seq = token.texts_to_sequences(xvalid)#对文本序列进行zero填充
xtrain_pad = sequence.pad_sequences(xtrain_seq, maxlen=max_len)
xvalid_pad = sequence.pad_sequences(xvalid_seq, maxlen=max_len)word_index = token.word_index
#基于已有的数据集中的词汇创建一个词嵌入矩阵(Embedding Matrix)
embedding_matrix = np.zeros((len(word_index) + 1, 100))
for word, i in tqdm(word_index.items()):embedding_vector = embeddings_index.get(word)if embedding_vector is not None:embedding_matrix[i] = embedding_vector
# 基于前面训练的Word2vec词向量,使用1个两层的LSTM模型
model = Sequential()
model.add(Embedding(len(word_index) + 1,100,weights=[embedding_matrix],input_length=max_len,trainable=False))
model.add(SpatialDropout1D(0.3))
model.add(LSTM(100, dropout=0.3, recurrent_dropout=0.3))model.add(Dense(1024, activation='relu'))
model.add(Dropout(0.8))model.add(Dense(1024, activation='relu'))
model.add(Dropout(0.8))model.add(Dense(3))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam')
model.fit(xtrain_pad, y=ytrain_enc, batch_size=512, epochs=100, verbose=1, validation_data=(xvalid_pad, yvalid_enc))

现在,我们看到分数小于0.5。 我跑了很多个epochs都没有获得最优的结果,但我们可以使用early stopping来停止在最佳的迭代节点。

那我们该如何使用early stopping?

好吧,其实很简单的。 让我们再次compile模型:

# 基于前面训练的Word2vec词向量,使用1个两层的LSTM模型
model = Sequential()
model.add(Embedding(len(word_index) + 1,100,weights=[embedding_matrix],input_length=max_len,trainable=False))
model.add(SpatialDropout1D(0.3))
model.add(LSTM(100, dropout=0.3, recurrent_dropout=0.3))model.add(Dense(1024, activation='relu'))
model.add(Dropout(0.8))model.add(Dense(1024, activation='relu'))
model.add(Dropout(0.8))model.add(Dense(3))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam')#在模型拟合时,使用early stopping这个回调函数(Callback Function)
earlystop = EarlyStopping(monitor='val_loss', min_delta=0, patience=3, verbose=0, mode='auto')
model.fit(xtrain_pad, y=ytrain_enc, batch_size=512, epochs=100, verbose=1, validation_data=(xvalid_pad, yvalid_enc), callbacks=[earlystop])

一个可能的问题是:为什么我会使用这么多的dropout? 嗯,fit模型时,没有或很少的dropout,你会出现过拟合(Overfit):)

让我们看看双向长短时记忆(Bi-Directional LSTM)是否可以给我们带来更好的结果。 对于Keras来说,使用Bilstm小菜一碟:)

# 基于前面训练的Word2vec词向量,构建1个2层的Bidirectional LSTM
model = Sequential()
model.add(Embedding(len(word_index) + 1,100,weights=[embedding_matrix],input_length=max_len,trainable=False))
model.add(SpatialDropout1D(0.3))
model.add(Bidirectional(LSTM(100, dropout=0.3, recurrent_dropout=0.3)))model.add(Dense(1024, activation='relu'))
model.add(Dropout(0.8))model.add(Dense(1024, activation='relu'))
model.add(Dropout(0.8))model.add(Dense(3))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam')#在模型拟合时,使用early stopping这个回调函数(Callback Function)
earlystop = EarlyStopping(monitor='val_loss', min_delta=0, patience=3, verbose=0, mode='auto')
model.fit(xtrain_pad, y=ytrain_enc, batch_size=512, epochs=100, verbose=1, validation_data=(xvalid_pad, yvalid_enc), callbacks=[earlystop])

很接近最优结果了! 让我们尝试两层的GRU:

# 基于前面训练的Word2vec词向量,构建1个2层的GRU模型
model = Sequential()
model.add(Embedding(len(word_index) + 1,100,weights=[embedding_matrix],input_length=max_len,trainable=False))
model.add(SpatialDropout1D(0.3))
model.add(GRU(100, dropout=0.3, recurrent_dropout=0.3, return_sequences=True))
model.add(GRU(100, dropout=0.3, recurrent_dropout=0.3))model.add(Dense(1024, activation='relu'))
model.add(Dropout(0.8))model.add(Dense(1024, activation='relu'))
model.add(Dropout(0.8))model.add(Dense(3))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam')#在模型拟合时,使用early stopping这个回调函数(Callback Function)
earlystop = EarlyStopping(monitor='val_loss', min_delta=0, patience=3, verbose=0, mode='auto')
model.fit(xtrain_pad, y=ytrain_enc, batch_size=512, epochs=100, verbose=1, validation_data=(xvalid_pad, yvalid_enc), callbacks=[earlystop])

太好了! 比我们以前的模型好多了! 持续优化,模型的性能将不断提高。

在文本分类的比赛中,想要获得最高分,你应该拥有1个合成的模型。 让我们来看看吧!

模型集成(Model Ensembling)

集多个文本分类模型之长,合成一个很棒的分类融合模型。

#创建一个Ensembling主类,具体使用方法见下一个cell
import numpy as np
from sklearn.metrics import roc_auc_score
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import StratifiedKFold, KFold
import pandas as pd
import os
import sys
import logginglogging.basicConfig(level=logging.DEBUG,format="[%(asctime)s] %(levelname)s %(message)s",datefmt="%H:%M:%S", stream=sys.stdout)
logger = logging.getLogger(__name__)class Ensembler(object):def __init__(self, model_dict, num_folds=3, task_type='classification', optimize=roc_auc_score,lower_is_better=False, save_path=None):"""Ensembler init function:param model_dict: 模型字典 :param num_folds: ensembling所用的fold数量:param task_type: 分类(classification) 还是回归(regression):param optimize: 优化函数,比如 AUC, logloss, F1等,必须有2个函数,即y_test 和 y_pred:param lower_is_better: 优化函数(Optimization Function)的值越低越好还是越高越好:param save_path: 模型保存路径"""self.model_dict = model_dictself.levels = len(self.model_dict)self.num_folds = num_foldsself.task_type = task_typeself.optimize = optimizeself.lower_is_better = lower_is_betterself.save_path = save_pathself.training_data = Noneself.test_data = Noneself.y = Noneself.lbl_enc = Noneself.y_enc = Noneself.train_prediction_dict = Noneself.test_prediction_dict = Noneself.num_classes = Nonedef fit(self, training_data, y, lentrain):""":param training_data: 二维表格形式的训练数据:param y: 二进制的, 多分类或回归:return: 用于预测的模型链(Chain of Models)"""self.training_data = training_dataself.y = yif self.task_type == 'classification':self.num_classes = len(np.unique(self.y))logger.info("Found %d classes", self.num_classes)self.lbl_enc = LabelEncoder()self.y_enc = self.lbl_enc.fit_transform(self.y)kf = StratifiedKFold(n_splits=self.num_folds)train_prediction_shape = (lentrain, self.num_classes)else:self.num_classes = -1self.y_enc = self.ykf = KFold(n_splits=self.num_folds)train_prediction_shape = (lentrain, 1)self.train_prediction_dict = {}for level in range(self.levels):self.train_prediction_dict[level] = np.zeros((train_prediction_shape[0],train_prediction_shape[1] * len(self.model_dict[level])))for level in range(self.levels):if level == 0:temp_train = self.training_dataelse:temp_train = self.train_prediction_dict[level - 1]for model_num, model in enumerate(self.model_dict[level]):validation_scores = []foldnum = 1for train_index, valid_index in kf.split(self.train_prediction_dict[0], self.y_enc):logger.info("Training Level %d Fold # %d. Model # %d", level, foldnum, model_num)if level != 0:l_training_data = temp_train[train_index]l_validation_data = temp_train[valid_index]model.fit(l_training_data, self.y_enc[train_index])else:l0_training_data = temp_train[0][model_num]if type(l0_training_data) == list:l_training_data = [x[train_index] for x in l0_training_data]l_validation_data = [x[valid_index] for x in l0_training_data]else:l_training_data = l0_training_data[train_index]l_validation_data = l0_training_data[valid_index]model.fit(l_training_data, self.y_enc[train_index])logger.info("Predicting Level %d. Fold # %d. Model # %d", level, foldnum, model_num)if self.task_type == 'classification':temp_train_predictions = model.predict_proba(l_validation_data)self.train_prediction_dict[level][valid_index,(model_num * self.num_classes):(model_num * self.num_classes) +self.num_classes] = temp_train_predictionselse:temp_train_predictions = model.predict(l_validation_data)self.train_prediction_dict[level][valid_index, model_num] = temp_train_predictionsvalidation_score = self.optimize(self.y_enc[valid_index], temp_train_predictions)validation_scores.append(validation_score)logger.info("Level %d. Fold # %d. Model # %d. Validation Score = %f", level, foldnum, model_num,validation_score)foldnum += 1avg_score = np.mean(validation_scores)std_score = np.std(validation_scores)logger.info("Level %d. Model # %d. Mean Score = %f. Std Dev = %f", level, model_num,avg_score, std_score)logger.info("Saving predictions for level # %d", level)train_predictions_df = pd.DataFrame(self.train_prediction_dict[level])train_predictions_df.to_csv(os.path.join(self.save_path, "train_predictions_level_" + str(level) + ".csv"),index=False, header=None)return self.train_prediction_dictdef predict(self, test_data, lentest):self.test_data = test_dataif self.task_type == 'classification':test_prediction_shape = (lentest, self.num_classes)else:test_prediction_shape = (lentest, 1)self.test_prediction_dict = {}for level in range(self.levels):self.test_prediction_dict[level] = np.zeros((test_prediction_shape[0],test_prediction_shape[1] * len(self.model_dict[level])))self.test_data = test_datafor level in range(self.levels):if level == 0:temp_train = self.training_datatemp_test = self.test_dataelse:temp_train = self.train_prediction_dict[level - 1]temp_test = self.test_prediction_dict[level - 1]for model_num, model in enumerate(self.model_dict[level]):logger.info("Training Fulldata Level %d. Model # %d", level, model_num)if level == 0:model.fit(temp_train[0][model_num], self.y_enc)else:model.fit(temp_train, self.y_enc)logger.info("Predicting Test Level %d. Model # %d", level, model_num)if self.task_type == 'classification':if level == 0:temp_test_predictions = model.predict_proba(temp_test[0][model_num])else:temp_test_predictions = model.predict_proba(temp_test)self.test_prediction_dict[level][:, (model_num * self.num_classes): (model_num * self.num_classes) +self.num_classes] = temp_test_predictionselse:if level == 0:temp_test_predictions = model.predict(temp_test[0][model_num])else:temp_test_predictions = model.predict(temp_test)self.test_prediction_dict[level][:, model_num] = temp_test_predictionstest_predictions_df = pd.DataFrame(self.test_prediction_dict[level])test_predictions_df.to_csv(os.path.join(self.save_path, "test_predictions_level_" + str(level) + ".csv"),index=False, header=None)return self.test_prediction_dict
#为每个level的集成指定使用数据:
train_data_dict = {0: [xtrain_tfv, xtrain_ctv, xtrain_tfv, xtrain_ctv], 1: [xtrain_glove]}
test_data_dict = {0: [xvalid_tfv, xvalid_ctv, xvalid_tfv, xvalid_ctv], 1: [xvalid_glove]}model_dict = {0: [LogisticRegression(), LogisticRegression(), MultinomialNB(alpha=0.1), MultinomialNB()],1: [xgb.XGBClassifier(silent=True, n_estimators=120, max_depth=7)]}ens = Ensembler(model_dict=model_dict, num_folds=3, task_type='classification',optimize=multiclass_logloss, lower_is_better=True, save_path='')ens.fit(train_data_dict, ytrain, lentrain=xtrain_w2v.shape[0])
preds = ens.predict(test_data_dict, lentest=xvalid_w2v.shape[0])
# 检视损失率
multiclass_logloss(yvalid, preds[1])
print(classification_report(predictions, yvalid))

因此,我们看到集成模型在很大程度上提高了分数! 由于本文只是一个教程,更多的技术细节还没有深入下去,对此,你可以利用空余时间多多优化下。。

希望你能喜欢本文!

https://www.kesci.com/home/project/5be7e948954d6e0010632ef2/code

文本分类从入门到精通相关推荐

  1. 文本分类从入门到精通—代码展示

    文本分类从入门到精通 本文整理自笔者年前在知乎上的一个回答: 大数据舆情情感分析,如何提取情感并使用什么样的工具?(贴情感标签) 1.我将数据筛选预处理好,然后分好词. 2.是不是接下来应该与与情感词 ...

  2. 文本分类从入门到精通各种模型的学习——Jieba分词。

    结巴中文分词 Python中文分词组件 四种分词模式 精确模式:试图把句子最精确的切开,适合文本分析. 全模式:把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义: 搜索引擎模式: ...

  3. NLP之文本分类实战入门超详细教程

    目录 前言 一.数据加载 1.加载包 2.读取数据 二.文本处理 1.去除无用字符 2.文本分词 3.去除停用词 4.去除低频词 5.划分训练集和测试集 三.把文本转换成向量的形式 1.把文本转换成t ...

  4. 程序显示文本框_C++入门到精通(二),编写我们自己的Hello World程序

    上一章我们学习了如何创建一个"Hello World"示例程序,本章我们来自己手动写一个程序,参照我们上一篇文章的方法,我们创建一个控制台应用程序工程,在这里选择第一项" ...

  5. [026]文本分类之SVM

    1 基础知识 1. 1 样本整理 文本分类属于有监督的学习,所以需要整理样本.根据业务需求,确定样本标签与数目,其中样本标签多为整数.在svm中其中如果为二分类,样本标签一般会设定为-1和1,而在朴素 ...

  6. 利用SVM 实现文本分类的实例

    原文来自:http://blog.csdn.net/zhzhl202/article/details/8197109 之前做过一些文本挖掘的项目,比如网页分类.微博情感分析.用户评论挖掘,也曾经将li ...

  7. 文本分类入门(一)文本分类问题的定义

    原博客地址:http://www.blogjava.net/zhenandaci/category/31868.html?Show=All 文本分类入门(一)文本分类问题的定义 文本分类系列文章,从文 ...

  8. 《Linux C编程从入门到精通》——第 1 章 Linux基础 1.1Linux的起源、发展和分类...

    本节书摘来自异步社区<Linux C编程从入门到精通>一书中的第1章,第1.1节,作者:宋磊 , 程钢著,更多章节内容可以访问云栖社区"异步社区"公众号查看 第 1 章 ...

  9. 《Linux C编程从入门到精通》一第1章 Linux基础1.1 Linux的起源、发展和分类

    本节书摘来自异步社区<Linux C编程从入门到精通>一书中的第1章,第1.1节,作者 宋磊 , 程钢,更多章节内容可以访问云栖社区"异步社区"公众号查看 第1章 Li ...

最新文章

  1. python项目了解_神级程序员都是这样来开源 Python 项目!今天算是涨知识了!
  2. 快速深入一门语言的几个问题
  3. bootstrap学习(四)输入框、导航
  4. 《网络安全原理与实践》一第1章 网络安全介绍
  5. could not change product header data even after switching to change mode
  6. lisp方格网法计算土方量_CAD土方软件方格网法与三角网法相结合,准确计算土方量...
  7. Eclipse导入他人的Maven工程报错
  8. Node.js 模块之Nimble流程控制
  9. 想做数学建模?先看看这些MATLAB函数吧!
  10. c语言编写的键盘记录程序,C程序读取键盘码的方法
  11. oracle修改memory,修改memory——target的值
  12. JavaScript基础--DOM部分02--李南江
  13. 离散数学之矩阵关系运算
  14. 数据库实验三 存储过程与触发器
  15. 企业微信和钉钉的区别
  16. html返回顶部按钮图片,goToTop(回到顶部)按钮的制作
  17. 使用Quick BI 制作企业数据分析报表
  18. 解密AI芯片的加速原理
  19. python 数据处理时去除emoji表情
  20. 详解幂律分布,以及用于重尾分布的Python库powerlaw的使用

热门文章

  1. Python高阶函数用法
  2. python中单个和批量增加更新的mysql(没有则插入,有则更新)
  3. linux 内存清理 释放命令,Linux系统中的内存清理和释放命令总结
  4. mysql 多维度分表_亿级订单数据分库分表设计方案(满足多维度查询:订单号、用户、商家、渠道)...
  5. 河南派出所犯罪嫌疑计算机网络人,【出彩河南公安人】息县公安局冯振娇:平凡岗位献青春 恒心不改展风采...
  6. linux 进程间通信 dbus-glib【实例】详解四(上) C库 dbus-glib 使用(附代码)(编写接口描述文件.xml,dbus-binding-tool工具生成绑定文件)(列集散集函数)
  7. 《数字图像处理》 笔记
  8. DSP与FPGA和嵌入式是什么关系?
  9. python 报错 xxx is not callable 原因及解决办法
  10. python opencv cv2.resize()函数