相较于短文本,处理长文本时,需要更加精细的处理,这里针对不同长度的文本,分别做研究。

短文本分类

数据集介绍

在网上找了好久,最终决定使用DataFontain平台的互联网新闻情感分析竞赛数据集来作为这次短文本分类的训练用数据集。竞赛简介如下:
本赛题目标为在庞大的数据集中精准的区分文本的情感极性,情感分为正中负三类。面对浩如烟海的新闻信息,精确识别蕴藏在其中的情感倾向,对舆情有效监控、预警及疏导,对舆情生态系统的良性发展有着重要的意义。

数据集示例如下:

1
2
3
import pandas as pd
train_data = pd.read_csv("data/Train_DataSet.csv")
train_data.head(5)

在本章中,我们只使用新闻标题作为训练数据,来判断新闻的类别,在下章的长文本分类时在使用新闻的content,最终结合使用所有的数据得到最终的分数。
目前初赛快结束了,分数最高的是0.8231。在本文写完之后看下最终的分数能否达到近似的水平,由于我没有测试集的label,所以使用训练集划分的分数作为最终的分数,差别应该不会很大。
那我们现在就开始系统的文本分类探究吧!

数据预处理

数据预处理是做NLP必不可少的流程之一。由于是NLP的第一篇文章,所以这里对预处理做详细的说明,比较几种预处理的结果。常用的预处理流程如下:

  1. 根据数据集的情况,有时需要去掉数字或者链接或者符号之类的 ,或者对数字做归一化处理等
  2. 分词
  3. 去停用词
    一般而言,这3步就是对文本的预处理流程了、现在开始实战。
1
2
train_label = pd.read_csv("data/Train_DataSet_Label.csv", header=0)
train_label.head(5)

先把文本和label对应一下。

1
2
3
4
5
6
7
8
9
10
id_label = {i[0]:i[1] for i in train_label.values}
id_train = {i[0]:i[1] for i in train_data.values}
# print(id_label)
text_label = [(id_train[i], id_label[i]) for i in id_label if i in id_train]
print("\n".join([str(i) for i in text_label[:5]]))
print("the length of text_label",len(text_label))
print("the length of train_data",len(train_data.values))
#将text和label分别作为list
text_all = [text for text,_ in text_label]
label_all = [label for _,label in text_label]
1
2
3
4
5
6
7
('问责领导(上黄镇党委书记张涛,宣国才真能一手遮天吗?)', 2)
('江歌事件:教会孩子,善良的同时更要懂得保护自己!', 1)
('绝味鸭脖广告"开黄腔"引众怒 "双11"这么拼值吗?', 2)
('央视曝光!如东一医药企业将槽罐车改成垃圾车,夜间偷排高浓度废水', 2)
('恶劣至极,央视都曝光了!南通如东一医药企业将槽罐车改成洒水车,夜间偷排高浓度废水...丢大发了!', 2)
the length of text_label 7340
the length of train_data 7345

可以看出有5条是id没对应起来,这5条咱就不要了,对大局没影响。

接下来需要对文本做预处理。这里我们做个对比试验,一种是经过预处理的,另一种是未预处理,查看预处理对各种算法的影响。
这里需要特别指出的是:现在的深度模型基本不需要对文本做预处理了,很多情况下基于字的特征比词的特征具有更高的准确率,后续小道会针对这个问题作详细的对比实验与说明。但预处理作为NLP的重要基础之一,还是需要了解一下滴
本章预处理流程为:分词,去标点,去停用词(也可以将标点写在停用词表内一步到位,这样分开是因为有时候标点和停用词的去除处理不同)

分词(这里使用python最常用的分词包jieba)

1
2
3
import jieba
text_data = [jieba.lcut(str(sentence)) for sentence in text_all]
print(text_data[-10:])
1
[['帮', '女郎', '在', '行动'], ['稻城', '亚丁', '+', '318', '川藏线', '+', '拉萨', '+', '青藏线', ',', '你', '心动', '了', '吗', '?'], ['大良', '、', '龙江', '、', '陈村', '4', '所', '学校', '9', '月', '开学', '!', '顺德', '未来', '两年', '力', '...'], ['最', '容易', '受到', '甲醛', '侵害', '的', '五类', '人', ',', '有', '你', '吗', '?', '_', '污染'], ['2019', '年', '1', '月', '事故', '伤亡', '月', '报'], ['珊瑚', '裸尾鼠', ':', '首个', '因', '全球', '气候', '变暖', '灭绝', '的', '哺乳动物'], ['独居', '老人', '做饭', '忘关', '火', ' ', '南通', '志愿者', '及时发现', '转危为安'], ['被', '生意', '上', '的', '人', '给', '利用', '合同诈骗', ',', '诈骗', '三十万', '够判', '多少', '年', '-', '-', '在', '..._', '律师', '365'], ['奎山', '汽贸', '城', '去年', '那场', '火灾', ',', '调查', '情况', '报告', '出来', '了', '!'], ['曝光', '台', '•', '调查', '|', '市场', '消防通道', '被', '长期', '霸占', '?', '事情', '并非', '想象', '的', '那样']]

去除标点符号

可以使用string自带的punctuation获取英文标点和zhon包的punctuation获取中文标点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import string
import zhon.hanzi as han
print("English puctuation is :",string.punctuation)
print("Chinese puctuation is :",han.punctuation)
#需要查询操作,所以这里选用查找时间复杂度O(1)的集合
punc = set()
for i in string.punctuation:
punc.add(i)
for i in han.punctuation:
punc.add(i)
#这里举个例子
sentence = "fjdk sal fgjd,.,/,/,/.,.[]"
sentence_after = ''.join(word for word in sentence if word not in punc)
print("sentence remove punctuation is :",sentence_after)
text_remove_punc = []
for sentence in text_data:
tmp = []
for word in sentence:
if word not in punc:
tmp.append(word)
text_remove_punc.append(tmp)
print(text_remove_punc[:10])
1
2
3
4
English puctuation is : !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
Chinese puctuation is : "#$%&'()*+,-/:;<=>@[\]^_`{|}~⦅⦆「」、 、〃〈〉《》「」『』【】〔〕〖〗〘〙〚〛〜〝〞〟〰〾〿–—‘’‛“”„‟…‧﹏﹑﹔·!?。。
sentence remove punctuation is : fjdk sal fgjd
[['问责', '领导', '上', '黄镇', '党委书记', '张涛', '宣国', '才', '真能', '一手遮天', '吗'], ['江歌', '事件', '教会', '孩子', '善良', '的', '同时', '更要', '懂得', '保护', '自己'], ['绝味', '鸭', '脖', '广告', '开', '黄腔', '引', '众怒', ' ', '双', '11', '这么', '拼值', '吗'], ['央视', '曝光', '如东', '一', '医药企业', '将', '槽罐车', '改成', '垃圾车', '夜间', '偷排', '高浓度', '废水'], ['恶劣', '至极', '央视', '都', '曝光', '了', '南通', '如东', '一', '医药企业', '将', '槽罐车', '改成', '洒水车', '夜间', '偷排', '高浓度', '废水', '...', '丢', '大发', '了'], ['央视', '曝光', '南通', '一', '医药企业', '夜间', '偷排', '高浓度', '废水', '...', '丢脸'], ['粉丝', '爆料', '五洲', '国际', '无锡', '项目', '涉嫌', '诈骗', '非法', '集资'], ['年内', '约', '10', '起', '锂电', '重组', '失败', ' ', '资本', '对', '高', '估值', '收购', '说', '不'], ['男子', '梦想', '一夜', '暴富', '持', '水泥块', '砸机'], ['北京', '多家', '法院', '供暖', '纠纷', '案件', '主体', '为', '供暖费', '追缴', '山海', '网']]

去除停用词

这里提供我自己常用的停用词表,大家可以根据不同的需求自己设定停用词表

1
2
3
4
5
6
7
8
9
10
11
12
with open("data/stopwords", encoding='utf-8') as fin:
stopwords = set()
for i in fin:
stopwords.add(i.strip())
text_after = []
for sentence in text_remove_punc:
tmp = []
for word in sentence:
if word not in stopwords:
tmp.append(word)
text_after.append(tmp)
print(text_after[:10])
1
[['问责', '领导', '上', '黄镇', '党委书记', '张涛', '宣国', '才', '真能', '一手遮天', '吗'], ['江歌', '事件', '教会', '孩子', '善良', '的', '同时', '更要', '懂得', '保护', '自己'], ['绝味', '鸭', '脖', '广告', '开', '黄腔', '引', '众怒', ' ', '双', '11', '这么', '拼值', '吗'], ['央视', '曝光', '如东', '一', '医药企业', '将', '槽罐车', '改成', '垃圾车', '夜间', '偷排', '高浓度', '废水'], ['恶劣', '至极', '央视', '都', '曝光', '了', '南通', '如东', '一', '医药企业', '将', '槽罐车', '改成', '洒水车', '夜间', '偷排', '高浓度', '废水', '...', '丢', '大发', '了'], ['央视', '曝光', '南通', '一', '医药企业', '夜间', '偷排', '高浓度', '废水', '...', '丢脸'], ['粉丝', '爆料', '五洲', '国际', '无锡', '项目', '涉嫌', '诈骗', '非法', '集资'], ['年内', '约', '10', '起', '锂电', '重组', '失败', ' ', '资本', '对', '高', '估值', '收购', '说', '不'], ['男子', '梦想', '一夜', '暴富', '持', '水泥块', '砸机'], ['北京', '多家', '法院', '供暖', '纠纷', '案件', '主体', '为', '供暖费', '追缴', '山海', '网']]

Word Embedding

然后将其Embedding,就是用向量表示文本。
在word2vec以前,大都使用one-hot加tf-idf来向量化文本

1

1

1

1

1

1

1

然后划分训练集和验证集,这里按照9:1划分,由于没有测试集,这里小道用验证集当做测试集来计算得分。
这里可以直接调用sklearn的划分数据集的函数,或者自己写一个划分函数,既然写到这儿了,我就自己写一个,顺便测测和sklearn划分函数的时间差距。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#整体思路是先把索引random,然后按比例向下取整选取。
import numpy as np
import os
import math
import random
import time

def heng_split(filein, fileout_dir, train_persent):
is_exsist = os.path.exists(fileout_dir)
if not is_exsist:
os.mkdir(fileout_dir)
else:
print("%s目录已经存在"%(fileout_dir))

with open(fileout_dir+"/train.csv", 'w', encoding='utf-8') as fout_train:
with open(fileout_dir+"/test.csv", 'w', encoding='utf-8') as fout_test:
with open(filein, encoding='utf-8') as fin:
content = fin.readlines()
count_line = len(content)
before = [k for k in range(count_line)]
random.shuffle(before)
content =[content[i] for i in before]
train_count = int(math.floor(count_line * train_persent))
for index, i in enumerate(content):
if index <= train_count:
fout_train.write(i)
else:
fout_test.write(i)

def main():
filein = "train.tsv"
# for train_persent in np.arange(0.6,0.95,0.05):
for train_persent in [0.95]:
fileout_dir = "out" + str(train_persent)[:4]
heng_split(filein, fileout_dir, train_persent)

if __name__ == "__main__":
main()

#这里是sklearn的划分方法
from sklearn.model_selection import train_test_split

if name == “main”:
data = pd.read_csv(“G:/dataset/wine.csv”)
#将样本分为x表示特征,y表示类别
x,y = data.ix[:,1:],data.ix[:,0]
#测试集为30%,训练集为70%
x_train,x_test,y_train,y_test = train_test_split(x,y,test_size=0.3,random_state=0)
print(len(x_train))
#124
print(len(x_test))

1