从零开始使用深度学习训练一个新闻分类器(干货)

https://mp.weixin.qq.com/s/qR-d9Zay-7NJZgmYYlwn0A?utm_source=tuicool&utm_medium=referral

我们在浏览新闻的时候,通常会看到新闻网站对每个新闻都进行了分类:



新闻分类的应用相当广泛。对于网站来说,可以根据你看得较多的新闻类别给你推荐新闻;对于用户来说,则是可以忽略掉不感兴趣的分类,提高了浏览体验。

比如我抓取了近一个月,网易新闻 APP 向我推荐的 13.7 万条新闻,以下的新闻类别图彻底地暴露了本人是个喜欢看体育和花边娱乐新闻的俗人:


那么各大新闻网站的新闻分类是如何对新闻进行分类的呢?据了解,有可能是网站编辑人工进行分类,但目前更有可能是通过各种高级的算法和 AI 来进行自动分类。

本文使用 Python 和 Keras,展示了如何从收集数据开始,到数据分析、预处理,再到使用 深度学习/神经网络 创建一个准确率达到人类水准的新闻分类器。

虽然这是个比较啰里吧嗦的教程,但是这里的 “从零开始”,是假设你已熟悉了 Python 基础语法的基础上的。

选择要爬取的数据源

我们知道,在监督学习中,数据的预处理往往花费的时间要比真正训练模型的时候还要多。所以找到一个规整易于爬取的数据源是相当重要的。

来看下我们的需求,即“给出一个新闻标题,返回该新闻的分类”,那么我们收集的每一条数据中都必须有 新闻标题 和 分类。

在各个提供了历史新闻的新闻网站中,中国新闻网 ( http://www.chinanews.com ) 的滚动新闻页面应该是最容易爬取的了:


网页链接,如:http://www.chinanews.com/scroll-news/2017/1224/news.shtml 可以直接指定某天的新闻,并且该页面直接包含了当天所有的新闻标题和对应的分类。

编写数据爬虫

还是以上面的链接 http://www.chinanews.com/scroll-news/2017/1224/news.shtml 为例,在页面上右键点击“显示网页源代码”,很容易可以找到我们所需要爬取的新闻标题和分类:

<div class="content_list"><ul><li><div class="dd_lm">[<a href=http://www.chinanews.com/world.shtml>国际</a>]</div> <div class="dd_bt"><a href="/gj/2017/12-24/8408109.shtml">天津交响乐团为尼泊尔带来新年音乐会</a></div><div class="dd_time">12-24 23:53</div></li><li><div class="dd_lm">[<a href=http://www.chinanews.com/wenhua.shtml>文化</a>]</div> <div class="dd_bt"><a href="/cul/2017/12-24/8408110.shtml">漫画“吾皇”系列作者白茶:走了心的作品才能走红</a></div><div class="dd_time">12-24 23:50</div></li>省略一大波输出...</ul>
</div>

获取网页内容

我这里使用 requests :

base_url = "http://www.chinanews.com/scroll-news/%s/%s/news.shtml" % (year, month_day)
resp = requests.get(base_url, timeout=10)
resp.encoding = "gbk"

需要注意的是该网页的编码方式是 gbk,需要用 resp.encoding 显式说明,否则会给接下来的解析带来乱码的麻烦。

解析网页

这里我们就要用到大名鼎鼎的“美丽汤” BeautifulSoup 了。当然这里也可以用正则解析出来,但是比起处心积累地构造过几天都不知道怎么来的正则,还是用 bs 比较省心:

soup = BeautifulSoup(resp.text, "html.parser")

首先找到包含了我们要抓的数据的 div 段 <div class="content_list"> ... </div>

content = soup.find('div', class_='content_list')

再从 div 里找到所有的 li ,即每条包含 新闻标题和分类 的列表,如 

<li><div class="dd_lm">[<a href=http://www.chinanews.com/world.shtml>国际</a>]</div> <div class="dd_bt"><a href="/gj/2017/12-24/8408109.shtml">天津交响乐团为尼泊尔带来新年音乐会</a></div><div class="dd_time">12-24 23:53</div></li>
li = content.find_all("li")

每个 li 中又包含了三个 div ,分别是 分类、新闻标题、发布时间。这里我们只需要前两个部分,但是还需要抓取下链接,作为去重的 key :

category = item.find('div', class_='dd_lm').text.replace(r'[', '').replace(r']', '')
title = item.find('div', class_='dd_bt').text
href = item.find('div', class_='dd_bt').a.attrs['href']

数据存储

我比较喜欢将数据凑成字典存到 Redis,方便之后使用 Pandas 处理。

di = {'category':category, 'title':title}
if not cli.hget('chinanews', href):cli.hset('chinanews', href, str(di))

时间处理

上面提到页面的 URL 里需要用到年月日信息,我这里使用了 timedelta 来从当前时间往后计算,比如下面就是抓取 1500 天的新闻标题及分类:

today = datetime.today()
for i in range(1, 1500):whichday = (today - timedelta(days=i)).strftime("%Y-%m%d")fetch_oneday(whichday)time.sleep(2)   # 做个有风度的爬虫

(爬虫的详细代码见文章最后的链接)

数据分析

大概一小时的收集之后,我们就有了 2299879 将近 230 万条数据:1500 天的新闻标题和对应的分类。

在对数据进行预处理之前,我们必须对数据进行一个初略的分析,去掉一些噪点之类的数据。

从 Redis 里读入数据

cli = redis.Redis()
data = cli.hgetall('chinanews')
df = pd.DataFrame([ast.literal_eval(data[k]) for k in data])

去除无用类别

categories = df.groupby('category').size()
pie = Pie("分类")
pie.add("", categories.index.tolist(), categories.values.tolist(), is_label_show=True, is_legend_show=False)
pie



一共有 30 个分类,并可以看到数据的分布并不均匀,我们先去掉数据量少于 20000 条的类别,因为数据量少对模型的准确性还是有影响的;另外对于 “图片”、“视频”、“报摘” 这三种分类,很明显没有区分度,只能去掉了:

categories = df.groupby('category').size()
categories = categories[categories > 20000].index.tolist()
# 设置 map 方法
filted = df['category'].map(lambda x: x in categories)
# 然后应用到 dataframe 上
df_filted = df[filted]df_filted = df_filted[ (df_filted['category'] != u'图片') & (df_filted['category'] != u'视频') & (df_filted['category'] != u'报摘')]

现在我们还有 2178902 条数据,以及 22 种分类:


数据预处理

现在我们的数据是长这样子的:


神经网络只能处理数字,因此我们需要将 category 和 title 分别映射为数字。

category

分类的转换比较容易,可以将所有分类从 1 开始映射,首先生成一个分类的列表,然后生成两个字典,分别是 分类名称:数字 以及 数字:分类名称 的字典,方便映射和查找:

catagories = df_filted.groupby('category').size().index.tolist()
catagory_dict = {}
int_catagory = {}
for i, k in enumerate(catagories):catagory_dict.update({k:i})int_catagory.update({i:k})

dataframe 加上一个映射的 column,使用 apply 方法:

df_filted['c2id'] = df_filted['category'].apply(lambda x: catagory_dict[x])

这样就完成了 category 到 数字的映射,重新取 title,c2id 两列来准备接下来的工作:

prepared_data = df_filted[ ['title', 'c2id'] ]

title

新闻标题其实是个句子,我们要把句子映射为数字列表,有两种方式:

第一种是分词后再映射,另一种是直接单个字进行转换映射。

前者的优点是准确率高 ( 假设当新闻标题里出现“勒布朗”这个词时,99% 可能这是条体育新闻,而如果是单个“勒”字很显然比较难进行分类 );缺点是在预测新标题时,如果出现了词库里没有的词,则无法进行预测 ( 人名之类的最常出现这种问题,无法进行映射转换,且分词效果也不好,如果是按单个字来映射则没有这个问题 )。

后者的优缺点刚好相反。

因为我们最终的目的是用作新数据的分类,所以用单字进行转换映射较好。

prepared_data['words'] = prepared_data['title'].apply(lambda x: re.findall('[\x80-\xff]{3}|[\w\W]', x))

正则 [\x80-\xff]{3}|[\w\W] 中,[\x80-\xff]{3} 配置中文字符,[\w\W] 配置标点符号空格等其他所有字符。

转换后的结果:


字库

生成字的映射字典:

all_words = []
for w in prepared_data['words']:all_words.extend(w)
word_dict = pd.DataFrame(pd.Series(all_words).value_counts())
word_dict['id'] = list(range(1, len(word_dict)+1))

我们得到了一个 6790 个 “字” ( 包括标点符号和空格等 ) 的字典,基本上包括了所有常用字,对应的 id 则为不同的数字。

字映射为数字

新加一个 w2v 的列存放转换后的数字队列 ( 执行比较久 ):

prepared_data['w2v'] = prepared_data['words'].apply(lambda x: list(word_dict['id'][x]))

然后补全或者截断为固定长度为 25 的队列 ( 新闻标题一般不会超过 25 个字 ):

maxlen = 25
prepared_data['w2v'] = list(sequence.pad_sequences(prepared_data['w2v'], maxlen=maxlen))

最终准备好的 dataframe:

现在,我们的 X 数据是 w2v 列,而 标签 (target) Y 则是 c2id 列。

生成训练数据和测试数据

简单地使用 sklearn.model_selection 的 train_test_split 随机将所有数据以 3:1 的比例分隔为训练数据和测试数据:

seed = 7
X = np.array(list(prepared_data['w2v']))
Y = np.array(list(prepared_data['c2id']))
x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=0.25, random_state=seed)

这里,比较重要的是要对 Y 进行 to_categorical 处理,把 Y 变成 one-hot 的形式。

为什么要转成 one-hot 呢?因为分类器往往默认数据数据是连续的,并且是有序的。但是按照我们上述的表示,数字并不是有序的,而是随机分配的。使用 one-hot,使得这些特征互斥,每次只有一个激活。因此数据就变成稀疏的了。

y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

LSTM 模型

建立模型

文本模型,通常都是使用 RNN 模型,我们直接参考 Keras 的 LSTM 官方例子即可:

https://github.com/keras-team/keras/blob/master/examples/imdb_lstm.py

model = Sequential()
model.add(Embedding(len(word_dict)+1, 256))
model.add(LSTM(256))
model.add(Dropout(0.5))
model.add(Dense(y_train.shape[1]))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

与例子中的模型不同的是,我在中间加了层 dropout 避免过拟合;另外我们是多分类,所以最后的全连接层,输出应该是类别的个数,激活函数改为 softmax,损失函数改为 categorical_crossentropy。

训练

model.fit(x_train, y_train, batch_size=128, epochs=20)


因为数据较大,所以训练起来速度较慢,在使用了一块 GTX1080 GPU 的情况下,一个轮次还要差不多 10 分钟左右。

10 个轮次的训练之后,准确率下降变慢,停留在 74% 左右。

测试数据验证

model.evaluate(x=x_test, y=y_test)

结果:

测试集的准确率在 69% 左右。

使用新数据来测试模型

结果有点差强人意,我们用新数据测试下吧。

新数据需要用预处理中的方式处理成数字列表:

def predict_(title):words = re.findall('[\x80-\xff]{3}|[\w\W]', title)w2v = [word_dict[word_dict['0']==x]['id'].values[0] for x in words]xn = sequence.pad_sequences([w2v], maxlen=maxlen)predicted = model.predict_classes(xn, verbose=0)[0]return int_catagory[predicted]

再拉取新的新闻测试下效果:

以下是部分输出:

从上面的结果可以看出,其实部分新闻标题的分类,感觉模型判断出来的结果更准确,比如:

[社会] prediction:[I  T] 华为否认提前发年终奖 网传消息实为销售激励计划
[社会] prediction:[房产] 中国这个地方如同仙境 豪华别墅每平米仅1300元
[汽车] prediction:[I  T] 阿里巴巴高管调整 蒋凡任淘宝总裁 靖捷任天猫总裁
[社会] prediction:[港澳] 香港青年学子“冬聚吉林”:多层面了解国家变化

如果是将预测的前三种分类都当成正确的话:

def predict_3(title):words = re.findall('[\x80-\xff]{3}|[\w\W]', title)w2v = [word_dict[word_dict['0']==x]['id'].values[0] for x in words]xn = sequence.pad_sequences([w2v], maxlen=maxlen)predicted = model.predict(xn, verbose=0)[0]predicted_sort = predicted.argsort() li = [(int_catagory[p], predicted[p]*100) for p in predicted_sort[-3:]]return li[::-1]

可以看到成功率接近 90%

从这个结果来看,我们的模型还是可行的。

其他模型

GRU

model = Sequential()
model.add(Embedding(len(word_dict)+1, 256))
model.add(GRU(256))
model.add(Dense(y_train.shape[1]))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

精确度略低于 LSTM ( 训练集 73.52% / 10 epochs;测试集 67.83% );

但训练用时较少,每轮次 480s 左右。

BiLSTM + CNN

embedding_size=128
hidden_size=256model = Sequential()
model.add(Embedding(input_dim=len(word_dict)+1, output_dim=128, input_length=25))
model.add(Bidirectional(LSTM(256, return_sequences=True)))
model.add(TimeDistributed(Dense(64)))
model.add(Activation('softplus'))
model.add(MaxPooling1D(5))
model.add(Flatten())
model.add(Dense(y_train.shape[1]))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

训练集精确度较 LSTM 略高 ( 76.55% / 10 epochs),但测试集的精确度略低 ( 68.29% ),且训练用时更长,需要 1080s / 1 epochs。

保存模型和数据

最终还是使用了 LSTM 模型。将数据和模型保存下来,方便下一次调用:

model.save('model.hdf5')import pickle
def save_obj(obj, name ):with open(name + '.pkl', 'wb') as f:pickle.dump(obj, f, pickle.HIGHEST_PROTOCOL)save_obj(int_catagory, 'int_catagory')
save_obj(catagory_dict, 'catagory_dict')
word_dict.to_csv('word_dict.csv', encoding='utf8')
prepared_data.to_csv('prepared_data.csv', encoding='utf8')

应用

比如类似某云平台的这种 API 服务:

使用 Flask 搭建一个 API 服务,用户 Post 一个新闻标题过来,返回预测的前三种可能分类:

➜  ~ curl -H "Content-Type: application/json" -X POST -d '{"title":"彭文生:房价下降才能促进宏观杠杆率的可持续下"}' http://192.168.15.24:5000/classify
{"result": {"1.category": "房产","1.possibility": "0.650669","2.category": "财经","2.possibility": "0.285769","3.category": "金融","3.possibility": "0.0191255"}
}
➜  ~ curl -H "Content-Type: application/json" -X POST -d '{"title":"潘粤明后台自拍 扮相温文尔雅表情搞怪反差萌"}' http://192.168.15.24:5000/classify
{"result": {"1.category": "娱乐","1.possibility": "0.845201","2.category": "台湾","2.possibility": "0.108812","3.category": "港澳","3.possibility": "0.0148304"}
}

还不赖吧,现在,你可以炒掉那些只会给新闻分类的网站编辑了。


代码及 Jupyter 笔记:https://github.com/jackhuntcn/news_category_classify


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/351505.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

跑实验_word2vector词向量实现_基于搜狗新闻预料+维基百科

这篇博客只是记录一下如何解决 跑别人的代码没通的过程。 文章目录 1 运行代码0设备环境1.获取语料库2.语料库预处理3.训练4.开动&#xff01;使用词向量近义词获取某个词语的词向量计算句子相似度词向量加减运算选出集合中不同类的词语 2总结一下经验3补充 1 运行代码 最经在…

web编程项目--新闻网站搭建

文章目录 一、新闻爬取1.爬虫原理2.分析网站链接格式和网页信息格式cheerio爬取新浪新闻爬取人民网新闻央视新闻爬取网易新闻 3.数据库设计4.爬虫具体代码实现5.存储结果展示 二、 网站搭建1.用express构建网站访问mysql2.显示查询结果后端实现前端实现 3.对查询结果进行分页4.…

彻底解决网络爬虫遇到的中文乱码问题

你是否遇到过下面的情况&#xff1a; 作为爬虫新手好不容易写了一个爬虫结果爬出来的数据中文数据乱码导致不能使用 如图&#xff1a; 其实很好解决&#xff1a; 如果你是使用的request模块得到的相应对象则可以如下设置&#xff1a; 主要由两种情况&#xff0c;这是根据网页所…

解析网页出现中文乱码问题的解决方案

最近在解析淘宝中商品的信息&#xff0c;结果出现乱码&#xff0c;如&#xff1a; 原因就是中文字符格式出现冲突&#xff0c;ASP.NET MVC 默认采用utf-8,但是淘宝网页采用gbk。 在网上找了一下&#xff0c;最常用的解决方法就是修改web.config: < system.web> ......…

java并发编程:重排序与happens-before介绍

文章目录 什么是重排序&#xff1f;顺序一致性模型与JMM的保证数据竞争与顺序一致性顺序一致性模型JMM中同步程序的顺序一致性效果JMM中未同步程序的顺序一致性效果 happens-before什么是happens-before?天然的happens-before关系 什么是重排序&#xff1f; 计算机在执行程序…

burpsuite工具的使用(详细讲解)

一&#xff09;前言 我已经在之前详细的说明了burpsuite的安装过程&#xff0c;如果不了解的可以看 burpsuite安装教程 &#xff1a;http://t.csdn.cn/uVx9X 在这了补充说明一下&#xff0c;在安装完burpsuite并设置完代理后&#xff0c;会出现如果访问的url是使用http协议的…

Boilsoft Video Splitter(无损视频分割器)官方正式版V8.2.0 | 无损视频分割软件下载 | 视频分割软件哪个好用?

Boilsoft Video Splitter 是一款优秀且快速的专业无损视频分割软件&#xff0c;支持大于2GB以上的视频分割和直接拖放功能&#xff0c;无需重新编码就能帮助大家直接将完整的AVI、MPEG、RM、ASF、WMV、3GP、MKV、FLV 或 MP4等主流视频文件按时间、大小或关键帧等条件拆分、剪切…

视频怎么分割片段?快速分割视频小技巧

如何快速分割视频&#xff0c;处理视频时&#xff0c;一些视频时长可能会比较长&#xff0c;需要进行分割处理&#xff0c;如何快速将多个视频进行分割&#xff0c;分割后自动每个视频分类保存。下面来试试批量剪辑分割的技巧&#xff0c;一起来试试。 准备工具&#xff1a; 媒…

分割视频的方法有哪些?

现在网络上的剪辑方法有很多&#xff0c;比如视频合并、视频分割等等&#xff0c;而其中的视频分割大概就是指将一段很长的视频分割成一段一段的&#xff0c;有些人为了取视频中重要的一个片段会采取视频分割的方法&#xff0c;下面给大家介绍一下操作方法。 教程之前&#xf…

一款简单实用的视频分割软件,快速将一段视频分割成两段

视频太多、太长&#xff0c;怎么统一分割&#xff0c;比如按段分割呢&#xff1f;今天小编给大家分享一个新的剪辑技巧&#xff0c;下面一起来试试。 所需工具 视频素材若干 操作步骤 运行【好简单批量智剪】&#xff0c;“分割视频”中导入视频素材&#xff0c;支持多种导入…

怎么截取精彩视频片段 视频分割软件哪个好

现在我们生活圈&#xff0c;每天都被各大火热上映的视频电影、电视剧和各种五花八门的综艺节目给包围了。好看的电视及电影一大波的袭来让我们应接不暇&#xff0c;我们不可能总是能跟我们喜欢的人一起去看&#xff0c;所以电影节目里面那些比较精彩的片段总是想保存下来&#…

视频分割软件,将一个视频分割成多段小的视频

如今已是深秋&#xff0c;正所谓一叶知秋&#xff0c;看着这满地落叶&#xff0c;大家期待的中秋佳节和十一小长假也就快要到来了&#xff0c;不过对于小编这样的剪辑爱好者当然是要在家中剪辑视频了。不知道有没有和小编一样的呢&#xff1f;今天小编要给大家分享一个分割视频…

视频剪辑,分割软件怎么用?简单高效分割视频

平常大家总会遇到视频剪辑与视频格式转换以及是视频压缩和合并的问题&#xff0c;下面为大家推荐几款window桌面电脑实用的比较实用的视频编辑软件&#xff0c;如下图。有了这几款软件&#xff0c;以后我们在手机或者电脑上剪辑视频就轻松多了&#xff0c;下面就让小编我来为大…

如何截取视频的一个小片段 视频分割软件哪个好

截视频的软件有什么&#xff0c; 怎么截取视频一部分&#xff0c; 今天小编就来带大家了解视频剪切分割那些事儿吧&#xff01; 看看如何截取视频的一个小片段&#xff0c; 视频分割软件哪个好吧&#xff01; 1、先打开迅捷视频合并分割软件&#xff0c;主要的功能就是视频…

如何将视频分割成几部分 视频剪切软件哪个好

视频已经成为继文字&#xff0c;图片后的又一个交流方式&#xff0c;在这个快节奏的发展时代&#xff0c;很多人看到文字就会头疼&#xff0c;转而通过视频来获取外界传递的信息&#xff0c;尤其是短视频以及影视的发展&#xff0c;对于很多女生来说&#xff0c;大概在追剧的过…

视频分割软件有什么,怎么分割视频

我们平时在看视频的时候回将一些精彩或者自己喜欢的内容录制下来&#xff0c;但是录制的过程中可能会有一些不是很喜欢的片段&#xff0c;这时候是不是很想将其剪切掉&#xff0c;虽然手机中也有相关工具&#xff0c;但是我们都知道&#xff0c;手机操作没有电脑方便&#xff0…

无损分割视频的软件哪个好

随着科技的迅速发展&#xff0c;好看的影视剧一大波的袭来让我们应接不暇&#xff0c;在我们不可能总是能跟我们喜欢的人一起去看&#xff0c;所以电影节目里面那些比较精彩的片段总是想保存下来和喜欢的人一起分享&#xff0c;但是由于各种原因会出现很多的问题&#xff0c;比…

怎么把视频的多个片段分割出来?快速分割视频的方法

在拍摄视频、保存视频或者录屏时多拍摄了一段、有很多不需要的片段或者多录了一段。那么是否重新拍摄、保存或录屏呢&#xff1f;显然是不需要的&#xff0c;因为你不一定能达到你原本想要的结果。但是可以用迅捷视频转换器把需要的片段从完整的视频分割出来&#xff0c;或者直…

Python获取当当平台商品数据信息可视化效果展示

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 环境使用: 版 本&#xff1a; python 3.8 编辑器&#xff1a;pycharm 2021.2 jupyter notebook 模块使用: 采集 requests >>> pip install requests 数据请求 csv <表格文件> 内置模块 保存数据 …

视频前景分割

MobileNetV3 bneck层 配套MobileNetV3视频讲解 super详解 在单继承中 super主要是用来调用父类的方法的 了解torch torch.nn nn是Neural Network的简称&#xff0c;帮助程序员方便执行如下的与神经网络相关的行为&#xff1a; &#xff08;1&#xff09;创建神经网络…