小说文本分类任务

代码链接

https://github.com/a1097304791/fiction-classification

数据集

数据集有从起点中文网上爬取的13个分类,每个分类20本,每本10章,共260部小说,3600章。

所用算法

采用支持向量机(SVM)算法,考虑使用一对多法,训练时依次把某个类别的样本归为一类,其他剩余的样本归为另一类,这样k个类别的样本就构造出了k个SVM。分类时将未知样本分类为具有最大分类函数值的那类。

1. 加载数据集

从文件夹读取数据,按照种类名->对应小说list,小说名->对应内容关系建立多个字典。为了方便后续的 nlp 处理,将每本小说的10章内容合成一张。

import os# 系统中每个目录都有隐藏文件夹,为避免误读所以要删除
def delete_hidden_floder(files):for f in files:if f[0]== '.': files.remove(f)return files# 加载所有小说
genre_datas = os.listdir('起点小说')
# 删除隐藏文件夹
genre_datas =  delete_hidden_floder(genre_datas)# 依次打开所有文件夹,读取所有小说并存入字典
# 建立种类名->对应小说list,小说名->对应内容list
fiction_datas = {}
name_datas = {}
for i in range(0, len(genre_datas)):name_datas[genre_datas[i]] = os.listdir('起点小说/'+genre_datas[i])name_datas[genre_datas[i]] = delete_hidden_floder(name_datas[genre_datas[i]])# 为方便分析,每个小说的 10 章list合并为 1 章for j in range(0, len(name_datas[genre_datas[i]])):fiction_datas[name_datas[genre_datas[i]][j]] = []dic_file = os.listdir('起点小说/'+genre_datas[i]+'/'+name_datas[genre_datas[i]][j])dic_file = delete_hidden_floder(dic_file)merged_file = []for k in range(0, len(dic_file)):merged_file += open('起点小说/'+genre_datas[i]+'/'+name_datas[genre_datas[i]][j]+'/'+dic_file[k], encoding='utf-8').readlines()fiction_datas[name_datas[genre_datas[i]][j]]= merged_file

检验一下我们的加载结果:

genre_datas, name_datas[genre_datas[0]]
(['悬疑','轻小说','都市','历史','仙侠','玄幻','科幻','奇幻','现实','军事','游戏','武侠','体育'],['我有一座冒险屋','魔临','深夜书屋','万界疯人院','助灵为乐系统','熟睡之后','颤栗高空','诡神冢','老九门','鬼吹灯II','全球崩坏','我在黄泉有座房','捡了一片荒野','我不是真的想惹事啊','好想有个系统掩饰自己','我能回档不死','盗墓笔记','鬼吹灯(盗墓者的经历)','人间苦','黎明医生'])
fiction_datas['我有一座冒险屋'][0:5]
['手机上这个以恐怖屋大门为图标的应用软件,很像是市面上流行的模拟经营类手游,只不过其经营的不是饭店、水族馆、宠物乐园,而是鬼屋。\n','陈歌盯着屏幕,他怎么也想不通,为什么父母遗留下的手机里,会有这样一个奇怪的小游戏。\n','他仔细翻看应用界面,里面所有信息都和他的鬼屋相吻合,包括每日游览人数和馆内设施场景,这游戏让陈歌产生了一种奇怪的感觉,好像游戏里需要经营的鬼屋,就是他现实中的鬼屋一样。\n','同样糟糕的处境,同样是濒临倒闭,两者之间有太多的共同点。\n','“难道这个游戏就是以我的鬼屋为原型制作的吗?那如果在游戏里改变了鬼屋,现实中是不是也能受益?”\n']

2. 数据预处理

2.1 生成标签

我们加载的数据无法直接被算法模型处理,所以我们需要对数据进行一系列预处理准备工作。

前面我们按照文件层级结构建立了多个字典,为方便数据处理,还需要给小说生成对应标签,并统一转为 list 类型(长度为 260 的小说 list)。

import numpy as np# 生成供训练使用的 list 类型数据和标签
labels = np.ones(len(fiction_datas)).tolist()
datas = np.ones(len(fiction_datas)).tolist()
genre_id = 0
name_id = 0
for genre in genre_datas:for name in name_datas[genre]:datas[name_id] = fiction_datas[name]labels[name_id] = genre_idname_id += 1genre_id += + 1

2.2 划分训练集和测试集

使用 scikit-learn 工具里面的 train_test_split 类在 10001 个样本当中,随机划出 25% 个样本和标签来作为测试集,剩下的 75% 作为训练集来进行训练我们的分类器。

from sklearn.model_selection import train_test_splittrain_x, test_x, train_y, test_y = train_test_split(datas, labels, test_size=0.25, random_state=5)

简单查看一下样本和标签:

train_x[5][0:5]
['第十章苍茫大地\n','漆黑的青铜棺椁内部渐渐安静下来,没有人再说话,所有人皆充满惧意,望着前方装殓尸体的青铜棺,众人发出的粗重的呼声,每一个人内心都很紧张。\n','青铜棺绿锈斑驳,内部到底装殓了怎样的人物?\n','“这一切都应与泰山上的五色祭坛有关。”\n','过了很久,众人才低声议论起来,他们想知道这一切为何会发生。\n']
test_y[0:10]
[5, 11, 12, 4, 2, 4, 6, 9, 12, 6]

2.3 分词

nlp 工作往往是以词语作为基本特征进行分析,所以需要对文本进行分词。这里为了代码简洁方便理解,将分词设计成 tokenize_words 函数,供后续直接调用。我们使用 jieba 分词库。

import jiebadef tokenize_words(corpus):tokenized_words = jieba.cut(corpus) # 调用 jieba 分词tokenized_words = [token.strip() for token in tokenized_words] # 去掉回车符,转为list类型return tokenized_words
# 随便输入一句话调用函数验证一下
a = '青铜巨棺古朴无华,上面有一些模糊的古老图案\n'
b = tokenize_words(a)
b
['青铜', '巨棺', '古朴', '无华', ',', '上面', '有', '一些', '模糊', '的', '古老', '图案', '']

2.4 去除停用词

在自然语言中,很多字词是没有实际意义的,比如:【的】【了】【得】等,因此要将其剔除。首先加载我们刚刚下载好的停用词表。这里也可以自行在网上下载,编码格式为 utf-8,每行一个停用词。为了方便调用,我们将去除停用词的操作放到 remove_stopwords 函数当中。

!wget - nc "http://labfile.oss.aliyuncs.com/courses/1208/stop_word.txt"
--2021-09-30 16:57:12--  http://-/
正在解析主机 - (-)... 失败:nodename nor servname provided, or not known。
wget: 无法解析主机地址 “-”
--2021-09-30 16:57:12--  http://nc/
正在解析主机 nc (nc)... 失败:nodename nor servname provided, or not known。
wget: 无法解析主机地址 “nc”
--2021-09-30 16:57:14--  http://labfile.oss.aliyuncs.com/courses/1208/stop_word.txt
正在解析主机 labfile.oss.aliyuncs.com (labfile.oss.aliyuncs.com)... 47.110.177.159
正在连接 labfile.oss.aliyuncs.com (labfile.oss.aliyuncs.com)|47.110.177.159|:80... 已连接。
已发出 HTTP 请求,正在等待回应... 200 OK
长度:15185 (15K) [text/plain]
正在保存至: “stop_word.txt”stop_word.txt       100%[===================>]  14.83K  --.-KB/s  用时 0.04s   2021-09-30 16:57:15 (367 KB/s) - 已保存 “stop_word.txt” [15185/15185])下载完毕 --2021-09-30 16:57:15--
总用时:2.6s
下载了:1 个文件,0.04s (367 KB/s) 中的 15K
def remove_stopwords(corpus): # 函数输入为全部样本集(包括训练和测试)sw = open('stop_word.txt', encoding='utf-8') # 加载停用词表sw_list = [l.strip() for l in sw] # 去掉回车符存放至list中# 调用分词函数tokenized_data = tokenize_words(corpus)# 使用list生成式对停用词进行过滤filtered_data = [data for data in tokenized_data if data not in sw_list]# 用' '将 filtered_data 串起来赋值给 filtered_datas(不太好介绍,可以看一下下面处理前后的截图对比)filtered_datas = ' '.join(filtered_data)# 返回是去除停用词后的字符串return filtered_datas

不妨再用一句话来检验一下去除停用词和分词的结果:

a = '李凡从衣服内兜里掏出了一个很小的小本放在了桌子上。\n'
b = remove_stopwords(a)
b
'李凡 衣服 兜里 掏出 很小 小本 放在 桌子'

接下来,构建一个函数整合分词和剔除停用词的预处理工作,调用 tqdm 模块显示进度。

from tqdm.notebook import tqdmdef preprocessing_datas(datas):preprocessing_datas = []# 对 datas 当中的每一个 data 进行去停用词操作# 并添加到上面刚刚建立的 preprocessed_datas 当中for data in tqdm(datas):preprocessing_data = ''for sentence in data:sentence = remove_stopwords(sentence)preprocessing_data += sentencepreprocessing_datas.append(preprocessing_data)# 返回预处理后的样本集return preprocessing_datas

最后直接调用上面的与处理函数对训练集和测试集进行预处理,可能会稍微有些慢:

pred_train_x = preprocessing_datas(train_x)
pred_test_x = preprocessing_datas(test_x)
  0%|          | 0/195 [00:00<?, ?it/s]0%|          | 0/65 [00:00<?, ?it/s]

3. 特征提取

在进行分词及去停用词处理过后,得到的是一个分词后的文本。现在我们的分类器是 SVM,而 SVM 的输入要求是数值型的特征。这意味着我们要将前面所进行预处理的文本数据进一步处理,将其转换为数值型数据。

转换的方法有很多种,这里使用最经典的 TF-IDF 方法。

在 Python 当中,我们可以通过 scikit-learn 来实现 TF-IDF 模型。并且,使用 scikit-learn 库将会非常简单。这里主要用到了 TfidfVectorizer() 类。

接下来我们开始使用这个类将文本特征转换为相应的 TF-IDF 值。

首先加载 TfidfVectorizer 类,并定义 TF-IDF 模型训练器 vectorizer

from sklearn.feature_extraction.text import TfidfVectorizervectorizer = TfidfVectorizer(min_df=1, norm='l2', smooth_idf=True, use_idf=True, ngram_range=(1, 1))

对预处理过后的 pred_train_d 进行特征提取:

tfidf_train_features = vectorizer.fit_transform(pred_train_x)
tfidf_train_features
<195x211797 sparse matrix of type '<class 'numpy.float64'>'with 592801 stored elements in Compressed Sparse Row format>

通过这一步,我们得到了 195 个 211797 维数的向量作为我们的训练特征集。我们可以查看转换结果:

np.array(tfidf_train_features.toarray()).shape
(195, 211797)

用训练集训练好特征后的 vectorizer 来提取测试集的特征:

⚠️注意这里不能用 vectorizer.fit_transform() 要用 vectorizer.transform(),否则,将会对测试集单独训练 TF-IDF 模型,而不是在训练集的词数量基础上做训练。这样词总量跟训练集不一样多,排序也不一样,将会导致维数不同,最终无法完成测试。

tfidf_test_features = vectorizer.transform(pred_test_x)
tfidf_test_features
<65x211797 sparse matrix of type '<class 'numpy.float64'>'with 143305 stored elements in Compressed Sparse Row format>

完成之后,我们得到 65 个 28335 维数的向量作为我们的测试特征集。

3. 构建并训练分类器

在获得 TF-IDF 特征之后,我们才能真正执行分类任务。我们不需要自己手动构建算法模型,可以调用 sklearn 中的 svm.SVC 类来训练一对多 SVM 分类器。

from sklearn import svmclf = svm.SVC(kernel='linear', C=10, gamma='scale', decision_function_shape='ovo')
clf.fit(tfidf_train_features, train_y)
SVC(C=10, decision_function_shape='ovo', kernel='linear')

4. 查看预测结果

为了直观显示分类的结果,我们用 scikit-learn 库中的 accuracy_score 函数来计算一下分类器的准确率(准确率即 test_l 中与 prediction 相同的比例)。

predictions = clf.predict(tfidf_test_features)
predictions
array([ 7, 10, 12,  5, 10, 11,  0,  7, 12,  7,  0,  5, 11, 12,  3,  0, 11,1,  9, 10, 12,  7,  0,  9,  0, 12,  3,  3,  0,  0, 11, 11, 11,  3,0,  0,  0,  7,  0,  0,  6,  0, 11,  0,  0,  7,  5,  5,  3,  0,  5,0, 10,  7,  5,  1,  0,  5,  3,  0,  5,  0, 10, 10,  8])

可以看到我们的准确率达到了34%,虽然不是很高,但是考虑到有13个分类之多,而且仅仅只有200个训练集,这个数据还算比较乐观了,如果想要更多的数据集,考虑获取更多的数据,或者使用更高级的分类算法。

from sklearn import metricsaccuracy_score = np.round(metrics.accuracy_score(test_y, predictions), 2)
print('准确率为'+str(accuracy_score*100)+'%')
准确率为34.0%

参考文章

文本分类概述

起点中文网小说文本分类项目【简易上手的nlp实战项目】相关推荐

  1. python 爬虫抓取网页数据导出excel_Python爬虫|爬取起点中文网小说信息保存到Excel...

    前言: 爬取起点中文网全部小说基本信息,小说名.作者.类别.连载\完结情况.简介,并将爬取的数据存储与EXCEL表中 环境:Python3.7 PyCharm Chrome浏览器 主要模块:xlwt ...

  2. python爬虫之爬取起点中文网小说

    python爬虫之爬取起点中文网小说 hello大家好,这篇文章带大家来制作一个python爬虫爬取阅文集团旗下产品起点中文网的程序,这篇文章的灵感来源于本人制作的一个项目:电脑助手 启帆助手 ⬆是项 ...

  3. pythonttf字体反爬虫_利用Python采集起点中文网小说,并解决字体反爬的问题

    个人比较喜欢看小说,于是乎想利用Python爬取小说网站--起点中文网,在Python编程爬取定位过程中遇到了Python反爬虫,咨询了我旁边的前端大神,说下方法 当前页面接口返回的html源码 当前 ...

  4. 爬虫实战——起点中文网小说的爬取

    首先打开起点中文网,网址为:https://www.qidian.com/ 本次实战目标是爬取一本名叫<大千界域>的小说,本次实战仅供交流学习,支持作者,请上起点中文网订阅观看. 我们首先 ...

  5. 爬虫实战01--爬取起点中文网小说

    废话不多说,直接上代码??? """ 实战:起点中文网--万古最强宗师(限免)1802章爬取 url: https://book.qidian.com/info/1012 ...

  6. Python简单爬取起点中文网小说(仅学习)

    目录 前言 一.爬虫思路 二.使用步骤 1.引入库 2.读取页面 3.分析HTML 3.从标签中取出信息 4.爬取正文 总结 前言 实习期间自学了vba,现在开始捡回以前上课学过的python,在此记 ...

  7. python request 爬虫爬取起点中文网小说

    1.网页分析.进入https://www.qidian.com/,点击全部,进行翻页,你就会发现一个规律, url=https://www.qidian.com/all?orderId=&st ...

  8. 爬取起点中文网小说介绍信息

    字数的信息(word)没有得到缺失 import xlwt import requests from lxml import etree import timeall_info_list=[] hea ...

  9. java爬虫抓取起点小说_爬虫实践-爬取起点中文网小说信息

    qidian.py: import xlwt import requests from lxml import etree import time all_info_list = [] def get ...

最新文章

  1. Spring Boot + Vue 如此强大?
  2. 湖南科技大学c语言程序设计b,2017年湖南科技大学计算机科学与工程学院826C语言程序设计与数据结构综合之数据结构考研题库...
  3. python yield遍历目录
  4. 【USACO Mar08】 奶牛跑步 A-star k短路
  5. 一个偷偷修改工作目录的幕后黑手
  6. echarts setoption方法_在Vue和React中使用ECharts的多种方法
  7. NYOJ 370 波动序列
  8. python3精要(6)-string类的format()方法
  9. JAVA程序测试时用到的与内存测试有关的东西
  10. android sharedpreferences 工具类,android sharedpreferences工具类
  11. python3.5安装pip_win10上python3.5.2第三方库安装(运用pip)
  12. 程序员应对浏览器同源策略的姿势
  13. java版hive的UDF(临时函数与永久函数)
  14. 2018.06.30 BZOJ1857: [Scoi2010]传送带(三分套三分)
  15. rtx2060什么水平_《赛博朋克2077》持续火热 什么样的笔记本才能畅玩这款游戏
  16. oracle 12c实例,图解Oracle 12c 触发器实例
  17. 向量叉乘在永磁同步电机电磁转矩计算中的应用
  18. MATLAB图像检索系统GUI设计
  19. 人体的基本五行 - 心肝脾肺肾,金木水火土 对应关系与基础解读
  20. 明朝取代元朝鲜为人知的秘密

热门文章

  1. 博客登录实现权限拦截
  2. 【技法操作】PS制作相机图标,UI设计教程
  3. [P4V]Perforce常用命令总结
  4. JSP连接数据库教程(IDEA)
  5. atm取款机代码 java_ATM取款机java代码
  6. 银雀山汉简出土引发全球“孙子出版热”
  7. 诺基亚(NOKIA)手机 功能代码
  8. java序列化,从底层到序列化所隐藏的问题以及解决方案
  9. Second,Millisecond,Microsecond
  10. Linux下的时间详解【转】