一、数据集介绍
本项目的数据集来自于DataFountain——疫情期间网民情绪识别。即给定微博ID和微博内容,设计算法对微博内容进行情绪识别,判断微博内容是积极的、消极的还是中性的。
链接:https://www.datafountain.cn/competitions/423/datasets
二、TextCNN模型介绍
将卷积神经网络CNN应用到文本分类任务,利用多个不同size的kernel来提取句子中的关键信息(类似 n-gram 的关键信息),从而能够更好地捕捉局部相关性。
三、实战
(一)环境
Python 3.7
Tensorflow 2.1.0
(二)代码
1.导包
from tensorflow.compat.v1 import ConfigProto
from tensorflow.compat.v1 import InteractiveSession
config = ConfigProto()
config.gpu_options.allow_growth = True
session = InteractiveSession(config=config)
import pandas as pd
import numpy as np
import tensorflow as tf
import os
import tensorflow.keras.backend as K
from sklearn.model_selection import StratifiedKFold
import matplotlib as mpl
import matplotlib.pyplot as plt
%matplotlib inline
print(tf.__version__)
print(tf.test.is_gpu_available())
2.读入数据
TRAIN_PATH = '../data/train_dataset/'
TEST_PATH = '../data/test_dataset/'
max_features = 5000
maxlen = 100
batch_size = 32
embedding_dims = 50
epochs = 2
input_categories = '微博中文内容'
output_categories = '情感倾向'
df_train = pd.read_csv(TRAIN_PATH+'nCoV_100k_train.labled.csv',engine ='python',encoding='utf-8')
df_train = df_train[df_train[output_categories].isin(['-1','0','1'])]
df_test = pd.read_csv(TEST_PATH+'nCov_10k_test.csv',engine ='python',encoding='utf-8')
df_sub = pd.read_csv(TEST_PATH+'submit_example.csv',encoding='utf-8')
print('train shape =', df_train.shape)
print('test shape =', df_test.shape)
3.Text to Sequence
# 将缺失值填充为-1
all_train_data = df_train[[input_categories, output_categories]].fillna('-1')
all_test_data = df_test[[input_categories]].fillna('-1')
# 将文本做分字处理
all_train_data[input_categories] = all_train_data[input_categories].map(lambda x: " ".join(x))
all_test_data[input_categories] = all_test_data[input_categories].map(lambda x: " ".join(x))
# 将文本转换成序列
tokenizer = tf.keras.preprocessing.text.Tokenizer(num_words=max_features, lower=False, filters="")
tokenizer.fit_on_texts(all_train_data[input_categories].tolist()+all_test_data[input_categories].tolist())
train_ = tokenizer.texts_to_sequences(all_train_data[input_categories].values)
test_ = tokenizer.texts_to_sequences(all_test_data[input_categories].values)
4.数据截断、补全
pad_sequences(),该函数是将序列转化为经过填充以后的一个长度相同的新序列新序列。
padding:'pre'或'post',确定当需要补0时,在序列的起始还是结尾补。
truncating:'pre'或'post',确定当需要截断序列时,从起始还是结尾截断。
train_ = tf.keras.preprocessing.sequence.pad_sequences(train_, maxlen=maxlen,
padding='pre',truncating='pre',value=0.0)
test_ = tf.keras.preprocessing.sequence.pad_sequences(test_, maxlen=maxlen,
padding='pre',truncating='pre',value=0.0)
5.label处理
LabelEncoder(),将离散型的数据转换成 0 到 n−1 之间的数。
from sklearn.preprocessing import LabelEncoder
from tensorflow.keras.utils import to_categorical
lb = LabelEncoder()
train_label = lb.fit_transform(all_train_data[output_categories].values)
6.模型构建
(1)输入层
[batch_size, maxlen] -> [batch_size, maxlen],每个句子固定maxlen个词,上述取100。
(2)Embedding层(Embedding)
[batch_size, maxlen] -> [batch_size, maxlen, embedding_dims],将字进行embedding_dims长度的编码,embedding_dims上述取50。
(3)卷积层(Conv1D)
卷积具有局部特征提取的功能,用 CNN 来提取句子中类似 n-gram 的关键信息。
每个卷积核的大小为[filter_size, embedding_dims],filter_size相当于n-gram中的n的大小,一般为[3,4,5],表示相邻几个词之间有词序关系。每个filter计算完成之后得到一个列向量,表示该filter从句子中提取的特征,有多少卷积核就能提取多少种特征,上述有3个filter。
filter_size=3的卷积操作后,生成一个100-3+1=98长度的向量,共有128个这样的卷积核,因此[batch_size, maxlen, embedding_dims] -> [batch_size, 98, 128]。filter_size=4的卷积操作后,生成一个100-4+1=97长度的向量,共有128个这样的卷积核,因此[batch_size, maxlen, embedding_dims] -> [batch_size, 97, 128]。filter_size=5的卷积操作后,生成一个100-5+1=96长度的向量,共有128个这样的卷积核,因此[batch_size, maxlen, embedding_dims] -> [batch_size, 96, 128]。
(4)池化层(GlobalMaxPooling1D)、拼接(Concatenate)
因为在卷积层过程中我们使用了不同高度的卷积核,使得我们通过卷积层后得到的向量维度会不一致,所以在池化层中,我们使用GlobalMaxPooling1D全局最大池化,对每个特征向量池化成一个值,即抽取每个特征向量的最大值表示该特征,而且认为这个最大值表示的是最重要的特征。因此3个卷积结果池化后都变成了[batch_size, :, 128] -> [batch_size, 128]。取最后一维拼接起来,得到[batch_size, 128] -> [batch_size, 384]。在池化层到全连接层之前可以加上dropout防止过拟合。
(5)全连接层(Dense)
[batch_size, 384] -> [batch_size, 3],使用softmax作为激活函数进行输出。可以使用L2正则化防止过拟合。
class TextCNN(tf.keras.Model):
def __init__(self,
maxlen, max_features, embedding_dims):
super(TextCNN, self).__init__()
self.maxlen = maxlen
self.max_features = max_features
self.embedding_dims = embedding_dims
self.kernel_sizes = [3, 4, 5]
self.embedding = tf.keras.layers.Embedding(self.max_features, self.embedding_dims, input_length=self.maxlen)
self.convs = []
self.max_poolings = []
for kernel_size in self.kernel_sizes:
self.convs.append(tf.keras.layers.Conv1D(128, kernel_size, activation='relu'))
self.max_poolings.append(tf.keras.layers.GlobalMaxPooling1D())
self.concat = tf.keras.layers.Concatenate()
self.classifier = tf.keras.layers.Dense(3, activation='softmax')
def call(self, inputs):
embedding = self.embedding(inputs)
convs = []
for i in range(len(self.kernel_sizes)):
c = self.convs[i](embedding)
c = self.max_poolings[i](c)
convs.append(c)
x = self.concat(convs)
output = self.classifier(x)
return output
7.模型训练
StratifiedKFold用法类似Kfold,但是他是分层采样,确保训练集,测试集中各类别样本的比例与原始数据集中相同。
gkf = StratifiedKFold(n_splits=5).split(X=all_train_data[input_categories].fillna('-1'), y=all_train_data[output_categories].fillna('-1'))
valid_preds = []
test_preds = []
for fold, (train_idx, valid_idx) in enumerate(gkf):
train_inputs = train_[train_idx]
train_outputs = to_categorical(train_label[train_idx])
valid_inputs = train_[valid_idx]
valid_outputs = to_categorical(train_label[valid_idx])
model = TextCNN(maxlen, max_features, embedding_dims)
optimizer = tf.keras.optimizers.Adam(learning_rate=1e-5)
train_dataset = tf.data.Dataset.from_tensor_slices((train_inputs,train_outputs)).shuffle(buffer_size=1000).batch(1)
valid_dataset = tf.data.Dataset.from_tensor_slices((valid_inputs,valid_outputs)).batch(1)
model.compile(loss='categorical_crossentropy', optimizer=optimizer, metrics=['acc'])
model.fit(train_dataset, validation_data= valid_dataset, epochs=epochs)
test_preds.append(model.predict(test_))
K.clear_session()