2019新型冠状病毒(2019-nCoV)感染的肺炎疫情发生对人们生活生产的方方面面产生了重要影响,并引发国内舆论的广泛关注,众多网民参与疫情相关话题的讨论。为了帮助政府掌握真实社会舆论情况,科学高效地做好防控宣传和舆情引导工作,本赛题针对疫情相关话题开展网民情绪识别的任务。给定微博ID和微博内容,设计算法对微博内容进行情绪识别,判断微博内容是积极的、消极的还是中性的。数据集依据与“新冠肺炎”相关的230个主题关键词进行数据采集,抓取了2020年1月1日—2020年2月20日期间共计100万条微博数据,并对其中10万条数据进行人工标注,标注分为三类,分别为:1(积极),0(中性)和-1(消极)。
本次数据集获取方式:
关注:YOLO的学习进阶日常
回复:DF
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
plt.style.use('seaborn')
sns.set(font_scale=2)
train_df = pd.read_csv('D:\\AA\\C\\deepmind\\NLP\\DF\\data\\nCoV_100k_train.labled.csv',engine ='python')
test_df = pd.read_csv('D:\\AA\\C\\deepmind\\NLP\\DF\\data\\nCov_10k_test.csv',engine ='python')
这一步十分的重要,提前对数据有一些了解,更熟悉业务,好的数据有利于模型的建立,可能会有很多异常处理
train_df['情感倾向'].value_counts().plot.bar()
plt.title('sentiment(target)')
从上面可以看出来,原本应该只有0,-1,1三种值,但是这里却有很多其他值,因此我们需要把这些对我们有用的数据进行提取,从图中还可以看出来大部分人都是保持中性的态度。
train_df = train_df[train_df['情感倾向'].isin(['0','1','-1'])]
有的ID很有可能会耍水贴,所以这里要对重复值也进行处理
drop_duplicates()去除重复值,只保留一条
train_df['微博id'].value_counts().head(10)
train_df=train_df.drop_duplicates(['微博id'])
train_df['微博id'].value_counts().head(5)
4458020735796410 1
4467956547167580 1
4462158534903640 1
4463231437677040 1
4465820153259820 1
Name: 微博id, dtype: int64
train_df['time'] = pd.to_datetime('2020年' + train_df['微博发布时间'], format='%Y年%m月%d日 %H:%M', errors='ignore')
train_df['month'] = train_df['time'].dt.month
train_df['day'] = train_df['time'].dt.day
train_df['dayfromzero'] = (train_df['month']-1)*31 + train_df['day']
fig, ax = plt.subplots(1, 2, figsize=(16, 8))
sns.kdeplot(train_df.loc[train_df['情感倾向'] == '0', 'dayfromzero'], ax=ax[0], label='sent(0)')
sns.kdeplot(train_df.loc[train_df['情感倾向'] == '1', 'dayfromzero'], ax=ax[0], label='sent(1)')
sns.kdeplot(train_df.loc[train_df['情感倾向'] == '-1', 'dayfromzero'], ax=ax[0], label='sent(-1)')
train_df.loc[train_df['情感倾向'] == '0', 'dayfromzero'].hist(ax=ax[1])
train_df.loc[train_df['情感倾向'] == '1', 'dayfromzero'].hist(ax=ax[1])
train_df.loc[train_df['情感倾向'] == '-1', 'dayfromzero'].hist(ax=ax[1])
ax[1].legend(['sent(0)', 'sent(1)','sent(-1)'])
plt.show()
因为一般过于长或者过于短的文本对我们来说误差率会有点大。微博长度可以帮助我们在建模深度学习的时候决定如何采用多大的最大长度来截断
train_df['weibo_len'] = train_df['微博中文内容'].astype(str).apply(len)
sns.kdeplot(train_df['weibo_len'])
plt.title('weibo_len')
从图中可以看出来我们使用150作为文本的分析就是最好的,所以等一下深度学习的时候长度我会设成150!
观察微博图片数量和情感的分布关系
train_df['pic_len'] = train_df['微博图片'].apply(lambda x: len(eval(x)))
train_df['pic_len'].value_counts().plot.bar()
plt.title('pic_len(target)')
sns.countplot(x='pic_len', hue='情感倾向',data=train_df)
plt.show()
从图中可以看出,大部分的人都还是文字居多,图片有一张或者没有,在数据预处理和初探之后就要进行分词词向量处理了
词向量经历的过程:
在前面的英文分词我也说过了其实大部分分类模型逻辑回归的效果都很好,所以这里就用最简单的词袋模型和逻辑回归开始第一步。
import jieba
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.linear_model import LogisticRegression
train_df['text_cut'] = train_df['微博中文内容'].apply(lambda x:" ".join(jieba.cut(str(x))))
count_vect = CountVectorizer(analyzer='word', token_pattern=r'\w{1,}')
count_vect.fit(train_df['text_cut'])
xtrain_count = count_vect.transform(train_df['text_cut'])
lr = LogisticRegression()
lr.fit(xtrain_count, train_df['情感倾向'] )
test_df['text_cut'] = test_df['微博中文内容'].apply(lambda x:" ".join(jieba.cut(str(x))))
xtest_count = count_vect.transform(test_df['text_cut'])
test_sub = lr.predict(xtest_count)
print(test_sub)
['-1' '0' '0' ... '0' '0' '0']
from keras.preprocessing.text import Tokenizer :这个库主要是作为分词器,允许通过将每个文本转换为整数序列或转换为矢量
他和基于统计的分词还有字符串匹配的分词方式有所不同,这是基于神经网络的分词
keras.preprocessing.text.Tokenizer(num_words=None,filters=’!"#$%&()*+,-./:;<=>?@[\]^_`{|}~\t\n’, lower=True, split=’ ',char_level=False, oov_token=None, document_count=0)
num_words:保留的最大单词数,基于单词频率。仅num_words-1保留最常用的词 一般使用5000
filters:是否过滤掉一些不需要的符号,默认情况下,将删除所有标点符号,从而将文本转换为以空格分隔的单词序列,然后将它们编入索引或向量化
因此使用神经网络的分词方式不需要进行正则表达式的数据预处理,传统模型baseline如果有兴趣可以给我评论
lower:因为会涉及一些英文字母,为了担心转换成适量的0,1时将单词错义这里需要全部转换成小写
from keras.preprocessing.sequence import pad_sequences:这个库的目的是将序列填充到相同的长度
为了实现的简便,keras只能接受长度相同的序列输入。如果序列长度参差不齐,需要将序列转化为经过填充以后的一个长度相同的新序列。
keras.preprocessing.sequence.pad_sequences(sequences, maxlen=None, dtype=‘int32’, padding=‘pre’, truncating=‘pre’, value=0.0)
maxlen设置最大的序列长度,长于该长度的序列将会截短,短于该长度的序列将会填充,一般的长度都是30 看我的第一个自然语言处理的博客我就说过一般的语句长度不会超过30
from keras.utils import to_categorical:将类向量(整数)转换为二进制类矩阵,神经网络只能fit(训练)矩阵,用来将整数型标签转化为one_hot数据。
例如,使用数组将得到的label表示标签[0,1,2,3,4],只有将其转化为one_hot数据后,才能由神经网络进行训练。
keras.utils.to_categorical(y, num_classes=None, dtype=‘float32’)
y是待转换的标签数组。
num_class:标签中共有多少种类。num_class的默认值是标签中最大数+1,是从0开始索引的,因此index在这里应该是3个 如果不加1就只有2个就会报错
dtype:转化的目标数据类型。
from keras.models import Sequential:这里就开始构建神经网络了,这是一个很基础Sequential模型,首先需要定义一个序列模型,然后往里面添加layers(层次)from keras import layers
keras.layers.Embedding(input_dim, output_dim, embeddings_initializer=‘uniform’, embeddings_regularizer=None, activity_regularizer=None, embeddings_constraint=None, mask_zero=False, input_length=None)
该层将正整数(索引)转换为固定大小的密集向量,只能作为构建的第一层,第一层必须由输入层
input_dim:int>0。词汇表的大小,即最大整数索引+ 1
output_dim:int> =0。密集嵌入的尺寸
input_length:表示这个最大的长度是30我前面说过了
layers.Dense:全连接层。在整个卷积神经网络中起到“分类器”的作用。 如果说卷积层、池化层和激活函数层等操作是将原始数据映射到隐层特征空间的话,全连接层则起到将学到的“分布式特征表示”映射到样本标记空间的作用。
activation:激励函数
在神经网络中,隐层和输出层节点(就是上一层和下一层之间的函数关系必须需要一个激励函数)的输入和输出之间具有函数关系,这个函数称为激励函数。 常见的激励函数有:线性激励函数、阈值或阶跃激励函数、S形激励函数、双曲正切激励函数和高斯激励函数等
model.compile():将优化器传递给之前实例化优化器
summary:相当于就是形成一个报告的意思,一个总结概括,你能看到当中的参数变化
再进行分类之后就是预测了,先将已经分好类的数据转换成二进制矩阵,在用序列模型去进行训练得到值。
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.utils import to_categorical
from keras.models import Sequential
from keras import layers
tokenizer = Tokenizer()
tokenizer.fit_on_texts(list(train_df['text_cut']) + list(test_df['text_cut']))
train_x = tokenizer.texts_to_sequences(train_df['text_cut'])
test_x = tokenizer.texts_to_sequences(test_df['text_cut'])
maxlen = 30
train_x = pad_sequences(train_x, padding='post', maxlen=maxlen)
test_x = pad_sequences(test_x, padding='post', maxlen=maxlen)
embedding_dim = 50
vocab_size = len(tokenizer.word_index) +1
model = Sequential()
model.add(layers.Embedding(input_dim=vocab_size,
output_dim=embedding_dim,
input_length=maxlen))
model.add(layers.Flatten())
model.add(layers.Dense(10, activation='relu'))
model.add(layers.Dense(3, activation='softmax'))
model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy'])
model.summary()
model.fit(train_x[:300], to_categorical(train_df['情感倾向'].astype(int) + 1)[:300],
epochs=1,
batch_size=10)
test_sub = model.predict(test_x)
Model: "sequential_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
embedding_1 (Embedding) (None, 30, 50) 7923850
_________________________________________________________________
flatten_1 (Flatten) (None, 1500) 0
_________________________________________________________________
dense_1 (Dense) (None, 10) 15010
_________________________________________________________________
dense_2 (Dense) (None, 3) 33
=================================================================
Total params: 7,938,893
Trainable params: 7,938,893
Non-trainable params: 0
_________________________________________________________________
D:\ANACONDA\lib\site-packages\tensorflow_core\python\framework\indexed_slices.py:433: UserWarning: Converting sparse IndexedSlices to a dense Tensor of unknown shape. This may consume a large amount of memory.
"Converting sparse IndexedSlices to a dense Tensor of unknown shape. "
Epoch 1/1
300/300 [==============================] - 6s 20ms/step - loss: 0.6189 - accuracy: 0.6667
print(test_sub)
[[0.3406474 0.35368806 0.30566448]
[0.33278218 0.3436801 0.32353768]
[0.36407104 0.42468563 0.21124329]
...
[0.33073366 0.34590626 0.3233601 ]
[0.35391447 0.48806122 0.15802427]
[0.3604995 0.40158147 0.23791896]]