NLP实战01:从简单做起手把手教你一步步Python实现中文文本(新闻文本)分类
文章目录
1.前言
NLP自然语言处理一直是人工智能,数据科学的热门分类,博主在去年参加的某学会的主题也是自然语言处理相关,自然语言处理由于是对一维上的文本,音频等方面进行处理,信息量更加的少,同时中文文本分类,相对英文,中文更加繁杂单词与单词之间不存在间隔,划分也是一段难事,本文的契机是作者最近参加的中国大学生软件杯需要对近几个月的中文互联网新闻文本进行分类处理,要求在5秒内快速得出新闻文本分类,于是在经过一段时间的折腾,暂时有了不错的进展,权且记录一下。
2.数据集介绍
该数据集收容了最近几个月中文互联网上的中文新闻,他们被提前划分为十个种类,如图,财经,房产,教育,科技,
军事,汽车,体育,娱乐,其他共十种以sheets子表的方式存储在一个xlsx中
同时该数据集的特点,就是新闻的文本的长度都非常的长这点 是毋庸置疑的,区别于一般的中文分类,博主曾经看过的文本分类数据集,长度也就大概十几个字,在这里长度甚至会达到上千。所以本次我们的新闻处理任务的难点,有一个很严重的问题就是数据过长,这点如何处理,博主在后面会介绍。
3.预处理数据
3.1 读取数据
对于这样一个拥有多个子表的数据,大家可能很容易就想到pandas库,但是如果我们直接采取pandas读取xlsx的方法,
read_excel只会默认将第一个子表的数据,然而显然我们需要的是全部十个子表的数据,这里我们就需要借助xlrd库的方法,获取excel表的sheet的信息。
# 获取workbook中所有的表格
excel_name = '文本.xlsx'
wb = xlrd.open_workbook(excel_name)
sheets = wb.sheet_names()
# 循环遍历所有sheet
data = DataFrame()
for i in range(len(sheets)):
df = pd.read_excel(excel_name, sheet_name=i)
data = data.append(df)
#这里我使用的xlrd版本是1.2.0如果使用较高版本,可能会出错。
值得提醒的是该部分代码是在windows上运行的,在后期博主为了加速训练将代码部署在linux服务器上运行时,报错xlrd不支持excel格式读取,根据博主查找资料获得的信息的话应该是在linux下不支持excel格式读取,所以,这里我们可以直接提前获取sheets的长度,然后直接运行后面这一段代码来读取数据,也是可以的
data = DataFrame()
for i in range(len(sheets)):
df = pd.read_excel(excel_name, sheet_name=i)
data = data.append(df)
3.2 处理数据
对于如何处理数据,这里我们需要明确一下,在处理数据,我们最终的目的是什么。计算机是完全不知道语言意思的,人工智能发展到现在,其实都是完全没有智能的(博主个人观点),一个个图像识别,人脸识别的模型其实是数据的转换(用这个观点来理解AI可能更好一点),比如对于二维图片,我们是将图片转换为数据上的一个个像素值,拼凑成一个整体然后让计算机记住拥有这样像素值组合的图片是某种特定的分类。那么对于,我们的文本,我们现在拥有了新闻的文本内容,我们要将新闻文本从计算机完全无法理解的字符转换成数符行,将该新闻对应的分类也转成数据型。这就是我们数据转换的步骤。
那么,我们先从最简单的开始,转换我们的标签:
label_transfer={'体育': 0, '体育焦点': 0, '军事': 2,
'娱乐': 3, '房产': 4, '教育': 5, '汽车': 6, '游戏': 7, '科技': 8, '财经': 9, '其他': 1}
#制作标签转换的字典
def apply_transfer(label):
return label_transfer.get(label)
new_data=data.channelName.apply(apply_transfer)
#pandas读取数据形成的数据结构dataFrame具有非常多的功能,这里我们使用apply功能相当于对指定列中的所有值都进行该操作
del data['channelName']
data['channelName']=new_data
data.head()#展示前五个,在数据过大的时候一般通过查看样本来查看数据总体
展示效果如下,可以看到标签数据全部被我们转换成了int整形,我们的数据处理完成了一部分
那么接下来,我们开始对文本进行处理,将每个中文单词变成数据形式(这是有一种专门研究的学科称为词嵌入),好像很困难?这里我一开始的想法是,
1.我们将每个句子切割为一个个的单词,那么句子被变成了一个个的单词序列,对于这样的序列我们制作一个字典比如
[‘我’,‘喜欢’,‘你’],我们将他转换成[0,1,2]看似逻辑很简单,但是有件事却是我们不得不思考的,在中文新闻文本类一篇新闻拥有上百甚至上千单词,将所有新闻文本的单词制作成一个大字典,最后的长度肯达到数十万以及百万(总共14137条数据),那么我们的词嵌入的数据差距就会很大(这对于模型的训练将是巨大的阻碍,同时我们的代码,要制作出一个这么长的字典,过于消耗内存),在硬件训练考虑方面(很现实,深度学习是要考虑硬件,吾日三省吾身,有钱买显卡吗,有原价显卡卖吗,想要的显卡显存够吗),该种方案的代价我们不能接受
但经过后面查阅资料发现,一直在做NLP的前辈已经预见到了这种问题,提供了一种已经提前训练好的word2vec–用于进行词嵌入的库,他可以非常简单的将文本的字符数据转换成高维向量,我们再将该数据直接输入RNN即可完成分类任务,那么接下来我们就主要介绍这种方法
3.2.1 如何处理超多文字的新闻文本
这里我在上篇博客(如何使用python处理中文文本–近几个月的新闻数据分析社会热点并创造词云图_theworld666的博客-CSDN博客)也有提到过,我们可以提前使用别人早已训练好的中文文本切割模型jieba库,但是由于新闻文本内部仍然存在很多根本无意义的字符,那么对于这样的数据我们肯定是要先提前清洗数据,所以我们这里编写文字处理函数如下:
import re
import jieba.posseg as pseg
def word_slice(lines):
corpus = []
corpus.append(lines.strip())
stripcorpus = corpus.copy()
for i in range(len(corpus)):
stripcorpus[i] = re.sub("@([\s\S]*?):", "", corpus[i]) # 去除@ ...:
stripcorpus[i] = re.sub("\[([\S\s]*?)\]", "", stripcorpus[i]) # [...]:
stripcorpus[i] = re.sub("@([\s\S]*?)", "", stripcorpus[i]) # 去除@...
stripcorpus[i] = re.sub(
"[\s+\.\!\/_,$%^*(+\"\']+|[+——!,。?、~@#¥%……&*()]+", "", stripcorpus[i])
# 去除标点及特殊符号
stripcorpus[i] = re.sub("[^\u4e00-\u9fa5]", "",
stripcorpus[i]) # 去除所有非汉字内容(英文数字)
stripcorpus[i] = re.sub("原标题", "", stripcorpus[i])
stripcorpus[i] = re.sub("回复", "", stripcorpus[i])
stripcorpus[i] = re.sub("(完)", "", stripcorpus[i]) # 相当于replace
#这里先使用re库清洗数据,将不影响原文意思和特殊字符全部去除
onlycorpus = []
for string in stripcorpus:
if(string == ''):
continue
else:
if(len(string) < 5):
continue
else:
onlycorpus.append(string)
cutcorpusiter = onlycorpus.copy()
cutcorpus = onlycorpus.copy()
wordtocixing = [] # 储存分词后的词语
for i in range(len(onlycorpus)):
cutcorpusiter[i] = pseg.cut(onlycorpus[i])#切割单词
cutcorpus[i] = ""
for every in cutcorpusiter[i]:
cutcorpus[i] = (cutcorpus[i] + " " + str(every.word)).strip()
wordtocixing.append(every.word)
return wordtocixing
fp = open('text2.txt', "w+", encoding='utf-8')
lenx=[]
for string in content:
if not isinstance(string,str):
continue
lines=word_slice(string)
lenx.append(len(lines))
for line in lines:
fp.write(line)
fp.write(' ')
fp.write('\n')
fp.close()
#这里我的方法是将切割后的文本全部存储到一个text2.txt,方便以后使用,每一句的所有单词在同一行吗,同时以换行符来区分每个句子
这里最终形成的text2.txt样式如下:
那么我们接下来使用word2CVec库:
from gensim.models import word2vec
from gensim.models import Word2Vec
sentences=word2vec.Text8Corpus('text2.txt')
model = Word2Vec(sentences, min_count=1)
model.save('words_models')
这里我们需要提前保存模型,这点是非常需要注意的,即因为该库提供的word2vec形成的模型参数是随机初始化的,我们为了保证每次训练模型的一致性,同时为了节省时间,导入提前保存好的数据,在多次断点训练中,也是最好的选择。
接下来我们正式开始将字符数据转换成高维向量:
model1=Word2Vec.load('words_models')#导入之前保存好的数据
word_vectors=model1.wv
#获取词向量是我们转换数据的关键
#将所有数据强制规定成一定长度,方便模型训练,对于不满足该长度的进行填充处理,即后面全填写0
def pad(x, maxlength=4500):
x1 = np.zeros((maxlength,100))
if len(x)==0:
return x1
x1[:len(x)] = x
x1=np.array(x1).reshape(4500,100)
return x1
def apply_text_trainsfer(text,data):
text=word_slice(text)
vec=[word_vectors[w] for w in text if w in word_vectors]
vec=pad(vec)
return vec,data
这里我们之所以编写成函数是因为博主的思想是想利用tensorflow提供的dataset数据结构来完成边加载边训练的方式,这样有个好处就是如果不采取这个方式,直接将所有文本转化成向量,那么内存占用是巨大的,不利于训练(博主在深度学习中由于使用GPU训练从而学到的硬件知识,深切的感觉到了计算机组成原理的有用。。。。),所以我们编写函数如下:
new_text=data.content.values
new_data=new_data.values
data_text=[]
for text in new_text:
data_text.append(text)
data_text=np.array(data_text)#将数据转换成固定的ndarray数组形式,方便变成tensor
print(data_text.shape)
# data_train=tf.data.Dataset.from_tensor_slices((data_text,new_data))
data_train=tf.data.Dataset.from_tensor_slices((data_text,new_data))
data_train=data_train.shuffle(14632)
#由于读取数据是顺序的所以前面的数据都是相同类型,对于数据训练很不方便,所以我们提前将数据打乱全部顺序
data_train=data_train.map(apply_text_trainsfer)
data_train=data_train.batch(32).shuffle(100)#指定batchsize和每次进行shuffle的个数增强数据
4.构建模型并训练数据
在前面,我们完成了数据的处理,最终我们的数据被全部压入进一个dataset的数据结构。有了数据之后,我们便开始搭建模型,这里我个人喜欢用tensorflow配合keras搭建模型,这里我们使用RNN网络中的lstm变体GRU从而来进行训练关于这边的介绍,博主在这篇博客中介绍过(RNN学习:利用LSTM,GRU层解决航空公司评论数据预测问题_theworld666的博客-CSDN博客
model=keras.Sequential()
model.add(layers.GRU(64))
model.add(layers.Dense(64,activation='relu'))
model.add(layers.BatchNormalization())
model.add(layers.Dense(32,activation='relu'))
model.add(layers.BatchNormalization())
model.add(layers.Dense(16,activation='relu'))
model.add(layers.BatchNormalization())#抑制过拟合
model.add(layers.Dense(10,activation= 'softmax'))
model.compile(optimizer=keras.optimizers.Adam(0.001),
loss='sparse_categorical_crossentropy',
metrics=['acc']
)
model.fit(data_train,batch_size=32,epochs=10)
#使用tensorflow搭配keras训练模型只需要简单的着几行代码就够了,如果不是博主为了抑制过拟合也不会添加这么多行
5.结果分析
然而我们的结果却并不尽人意
准确率却一直卡在60左右,这实在是有点差。。。后面在咨询大佬中,偶然得知,是博主学识浅薄了,LSTM可能对于这种非常大的数据适应性不是很好,无法记忆这么长的数据,所以博主在网络的选择上面出了问题,于是第一种方案从十几天前一直到现在,算是失败了。。但如果选取的是短篇的新闻效果还是非常可观的。对于本篇博客有任何疑问或者见解欢迎评论区指出。