手把手教你用 TensorFlow 实现文本分类(上)

由于需要学习语音识别,期间接触了深度学习的算法。利用空闲时间,想用神经网络做一个文本分类的应用, 目的是从头到尾完成一次机器学习的应用,学习模型的优化方法,同时学会使用主流的深度学习框架(这里选择tensorflow)。

文章分为两部分,本文仅实现流程,用简单的softmax回归对文本进行分类,后面一篇文章再从流程的各个方面对模型进行优化,达到比较好的效果。

  收集数据

该部分不是这里的重点,数据从各大新闻网站爬取新闻文本,分十类保存到本地,包括科技、生活、体育、娱乐等。文本分别保存到training_set和testing_set目录下,如:

$ tree -L 1 training_set/
training_set/
├── 10_hel
├── 1_ent
├── 2_fin
├── 3_spo
├── 4_tec
├── 5_mil
├── 6_soc
├── 7_lif
├── 8_cul
└── 9_car

文本以text_id.txt的格式保存在不同类的目录下(如text_1234.txt)。本例保存了共113673个训练文本和等数量的测试文本(暂时按1:1的比例)。

  预处理文本

step0

为方便后面处理,预处理文本首先要分别针对训练文本和测试文本生成唯一的文本ID, 这里用 {class_id}{text_type}{text_id}.txt 来标示唯一文本,class_id为类的id,这里为1-10;text_type为数据类型包括train和test;text_id为类文件夹下的文本id,实现函数:

def get_unique_id(self, data_dir):
        """
            get flie unique id famate as {class_id}_type_{text_id}.txt.
            data_dir is the full path of file
              e.g ./training_set/4_tec/4_tec_text/text_2001.txt
            where "training" is type, "4" is file class, and "2001" is text id.
            modify this function to adapt your data dir fomate
        """

        dir_list = data_dir.split("/")
        class_id = dir_list[2].split("_")[0]
        text_id = dir_list[4].split(".")[0]
        type_id = dir_list[1].split("_")[0]
        return class_id + "_" + type_id + "_" + text_id

step1: 分词

通俗来讲,文本分类的主要思想,是构建各类文本的汉语词典,通过对文本进行分析,观察文本中哪类词汇比较多,由此判断文本所属类别。因此,文本分类需要对文本进行分词操作,可以选择的分词工具很多,这里选择Python编写的jieba开源库对文本进行分词,并以行为单位,将文本保存到输出文件,该部分实现比较简单:

def splitwords(self, data_dir, data_type):

        if os.path.exists(data_type+".txt"):
            os.remove(data_type+".txt")

        list_dirs = os.walk(data_dir)
        for root, _, files in list_dirs:
            print root
            # get all files under data_dir
            for fp in files:
                file_path = os.path.join(root, fp)
                file_id = self.get_unique_id(file_path)
                #split words for f, save in file ./data_type.txt
                with nested(open(file_path), open(data_type+".txt", "a+")) as (f1, f2):
                    data = f1.read()
                    #print data
                    seg_list = jieba.cut(data, cut_all=False)
                    f2.write(file_id + " " + " ".join(seg_list).replace("\n", " ")+"\n")

        print "split word for %s file end." % data_type
        return

函数传入参数为数据集目录路径,以及数据集类型(train or test)。结果文件保存形如train.txt,后续的操作在该输出文件基础之上。输出文件格式为:<class_{data_type}_id> < words >

step2: 去除停用词

这部分主要删去文本中的停用词,停用词包括一些对于文本分类无用,而且出经常出现的词汇或符号,如“因此”、“关于”、“嘿嘿”、标点符号等。去除停用词需根据停用词典,去除上面经过分词操作的文本中的停用词。停用词典可以根据自己需要生成或在网络上获得,这里后面源码链接中会给出使用的停用词词典。

def rm_stopwords(self, file_path, word_dict):

        #read stop word dict and save in stop_dict
        stop_dict = {}
        with open(word_dict) as d:
            for word in d:
                stop_dict[word.strip("\n")] = 1

        # remove tmp file if exists
        if os.path.exists(file_path+".tmp"):
            os.remove(file_path+".tmp")

        print "now remove stop words in %s." % file_path
        # read source file and rm stop word for each line.
        with nested(open(file_path), open(file_path+".tmp", "a+"))  as (f1, f2):
            for line in f1:
                tmp_list = [] # save words not in stop dict
                words = line.split()
                for word in words[1:]:
                    if word not in stop_dict:
                        tmp_list.append(word)
                words_without_stop =  " ".join(tmp_list)
                f2.write(words[0] + " " + words_without_stop + "\n")

        # overwrite origin file with file been removed stop words
        shutil.move(file_path+".tmp", file_path)
        print "stop words in %s has been removed." % file_path
        return

代码中经过简单的按行读文本,然后搜索停用词典,如果文本中的词汇在词典中,则跳过,否则保存。这里每行对应数据集中的一个文本。

  step3: 生成词典

上面提到文本分类需要得到能表征各类文本的汉语词典,这部分的主要思路是实现tf_idf算法自动提取关键词,根据词频(TF)和逆文档频率(IDF)来衡量词汇在文章中的重要程度。这里词频的计算采用公式:

由于是衡量某类文本的关键词,公式中的“文章”为某类所有文本的总和。逆文档频率计算采用公式:

上面的文档总数为train数据集所有文本的数目。tf-idf为两个指标的乘积,计算各类文本中所有词汇的tf-idf,由小到大排序,默认取前500个词汇作为该类的关键词保存到词典。最终生成大小为5000的词典。简洁考虑,该部分的关键代码(gen_dict方法中):

      

  for k, text_info in class_dict.items():
            #print "class %s has %d words" % (k, text_info.file_num)
            # get tf in words of class k
            for w in text_info.wordmap:
                text_info.tf_idf(w, word_in_files[w], text_num)

            main_words = []
            with open(save_path, "a+") as f:
                main_words = text_info.get_mainwords()
                print "class %s : main words num: %d" % (k, len(main_words))
                f.write("\n".join(main_words) + "\n")

class_dict是类id到该类文本信息(text_info)的字典,text_info.wordmap保存了该类文本的所有不重复的词汇,text_info.tf_idf方法计算该类文本某词的tf-idf,输入参数为词汇,词汇在整个语料库出现的文本数和语料库的文本数。text_info.get_mainwords方法得到该类本前500个关键词。完整的定义与实现参考源码

step4: 生成词袋

该部分实现向量化文本,利用生成的词典,以行为单位将去停用词后的文本转换为向量,这里向量为5000维。如果文本出现词典中的某词汇,则文本向量对应词典中该词汇的位置的计数累加。最终生成文件,行数为文本数,列为5000。此外生成对应的label文件,行数为文本数,对应于文本向量文件行,列为1,对应某文本的类别(1-10)。该部分代码比较简单,实现在gen_wordbag方法中。

到此完成了文本的预处理,接下来针对不同分类算法,将有不同的处理,这里参考tensotflow处理MNIST数据集,读取预处理后的文本到系统,进行线性回归。

  读取训练数据

该部分主要包括两部分,一是从磁盘读取向量化后的文本保存到numpy数组,将数据和类别分别存储,数据保存为二维(text_line_num, 5000)的数组,text_line_num为数据集的文本数,5000为词典的维度,也是后面模型输入参数的个数。类别保存为标签向量(label_line_num, 1),label_line_num,同样为数据集的大小。

为方便处理,将类别10的标签保存为0,并对label进行“one_hot”处理,这部分解释可参考上个tensotflow链接。该部分在datasets类中实现。需要注意的是这里train部分数据最为cv(cross validation)数据,这里暂时不会用到。此外,由于数据较多,为节省内存,提高整体运算速度,分别读取train数据集和test数据集。dataset类中保存不同类型的数据集,并实现next_batch方法,获取指定数目的数据。

  训练数据

该部分利用softmax回归对数据进行训练,对于tensorflow的使用这里不作介绍。完整代码如下:

#!/usr/bin/python
#-*-coding:utf-8-*-

import tensorflow as tf
from datasets import datasets

data_sets = datasets()
data_sets.read_train_data(".", True)

sess = tf.InteractiveSession()

x = tf.placeholder(tf.float32, [None, 5000])
W = tf.Variable(tf.zeros([5000, 10]))
b = tf.Variable(tf.zeros([10]))
y = tf.nn.softmax(tf.matmul(x, W) + b)

y_ = tf.placeholder(tf.float32, [None, 10])
cross_entropy = -tf.reduce_sum(y_ * tf.log(y + 1e-10))
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

#training
tf.global_variables_initializer().run()

saver = tf.train.Saver()
for i in range(1000):
    batch_xs, batch_ys = data_sets.train.next_batch(100)
    train_step.run({x: batch_xs, y_: batch_ys})

print W.eval()
print b.eval()

path = saver.save(sess, "./model2/model.md")

代码中:

● x : 对于输入数据,None占位符标示输入样本的数量,5000为单个样本的输入维度,对应字典维度。

● W :权重矩阵,行为输入维度,列为输出维度,这里为类别的数目10。

● b : 偏重为10对应输出的维度

● y : 定义训练输出结果,使用softmax作为激励函数,tf.matmul(x, W) + b为输入参数,tf.matmul为矩阵乘。

● y_ : 真实样本的类别,从数据集读入,None占位符标示输入样本的数量,10为输出的维度。

● cross_entropy: 交叉熵,衡量真实值与预测值的偏差程度,训练过程中目的是最小化该值。

训练对cross_entropy进行梯度下降算法更新参数,学习率为0.01。迭代1000次,每次使用100个训练集。最后保存训练的模型到指定目录。

  测试模型

这部分主要读取上面保存的模型参数,对测试数据集进行预测,并打印准确率。

!/usr/bin/python
#-*-coding:utf-8-*-

import tensorflow as tf
from datasets import datasets

data_sets = datasets()
data_sets.read_test_data(".", True)

sess = tf.InteractiveSession()

x = tf.placeholder(tf.float32, [None, 5000])
W = tf.Variable(tf.zeros([5000, 10]))
b = tf.Variable(tf.zeros([10]))
y = tf.nn.softmax(tf.matmul(x, W) + b)
y_ = tf.placeholder(tf.float32, [None, 10])

saver = tf.train.Saver()
saver.restore(sess, "./model2/model.md")

# test
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
acc = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print(acc.eval({x: data_sets.test.text, y_: data_sets.test.label}))

  小结

直接通过上面过程训练模型,得到的准确率大概为65%,虽然比10%高出许多,仍然属于比较低的准确率。在后面一篇文章重点对上面的过程进行改进,提高预测的准确性。

此外,值得一提的是,一开始,直接参考tensorflow官网给的例子进行训练会出现准确率为0的现象,观察TensorBord,发现权重和偏重一直不更新,打印W和b发现值为Nan,最后找到问题所在:

使用交叉熵作为cost function,由于文本矩阵为严重稀疏矩阵,导致出现y_ tf.log(y)结果为0log0的现象。导致训练参数为Nan,给预测值加一个极小的值,防止与测试为0。

====================================分割线================================

本文作者:AI研习社

本文转自雷锋网禁止二次转载,原文链接

时间: 2024-08-29 10:43:36

手把手教你用 TensorFlow 实现文本分类(上)的相关文章

手把手教你用 TensorFlow 实现文本分类(下)

本篇文章主要记录对之前用神经网络做文本识别的初步优化,进一步将准确率由原来的65%提高到80%,这里优化的几个方面包括: ● 随机打乱训练数据 ● 增加隐层,和验证集 ● 正则化 ● 对原数据进行PCA预处理 ● 调节训练参数(迭代次数,batch大小等)   随机化训练数据 观察训练数据集,发现训练集是按类别存储,读进内存后在仍然是按类别顺序存放.这样顺序取一部分作为验证集,很大程度上会减少一个类别的训练样本数,对该类别的预测准确率会有所下降.所以首先考虑打乱训练数据. 在已经向量化的训练数据

机器学习零基础?手把手教你用TensorFlow搭建图像识别系统(三)| 干货

雷锋网按:本文是介绍用TensorFlow构建图像识别系统的第三部分. 在前两部分中,我们构建了一个softmax分类器来标记来自CIFAR-10数据集的图像,实现了约25-30%的精度. 因为有10个不同可能性的类别,所以我们预期的随机标记图像的精度为10%.25-30%的结果已经比随机标记的结果好多了,但仍有很大的改进空间.在这篇文章中,作者Wolfgang Beyer将介绍如何构建一个执行相同任务的神经网络.看看可以提高预测精度到多少!雷锋网(公众号:雷锋网)对全文进行编译,未经许可不得转

手把手教你使用TensorFlow生成对抗样本 | 附源码

更多深度文章,请关注:https://yq.aliyun.com/cloud 如果说卷积神经网络是昔日影帝的话,那么生成对抗已然成为深度学习研究领域中一颗新晋的耀眼新星,它将彻底地改变我们认知世界的方式.对抗学习训练为指导人工智能完成复杂任务提供了一个全新的思路,生成对抗图片能够非常轻松的愚弄之前训练好的分类器,因此如何利用生成对抗图片提高系统的鲁棒性是一个很有研究的热点问题. 神经网络合成的对抗样本很容易让人大吃一惊,这是因为对输入进行小巧精心制作的扰动就可能导致神经网络以任意选择的方式对输入

手把手教你:如何让Windows恋上Linux bash

4月7日,微软开始向用户推送Windows 10 biuld 14316预览版,该版本不仅在Cortana跨平台支持.Edge浏览器支持和虚拟桌面方面得到了优化,还能够原生支持Linux bash. 对现在的开发者来说,Linux已经是很多人必须的开发平台,在windows上不必借助虚拟机就能运行Bash,无疑有极大的吸引力. 但是微软并没有在最新版本Windows 10里直接内置Bash,需要开发者进行一些安装和设置工作.下面,笔者将会手把手教你,如何实现Ubuntu on Windows.

机器学习零基础?手把手教你用TensorFlow搭建图像识别系统(二)

这是Wolfgang Beyer一篇博文.详细介绍了如何使用TensorFlow搭建一个简单的图像识别系统.本篇将手把手地讲解搭建图像识别系统的全过程. 此系列文章主要介绍了不具备机器学习基础的用户如何尝试从零开始在TensorFlow上搭建一个图像识别系统.在文章的第一部分中,作者Woflgang Beyer向读者们介绍了一些简单的概念.本文为系列的第二部分,主要介绍了如何实现简单的图像识别功能.雷锋网编译,未经许可不得转载. 现在,我们可以开始建立我们的模型啦.实际上数值计算都是由Tenso

手把手教你由TensorFlow上手PyTorch(附代码)

当我第一次尝试学习 PyTorch 时,没几天就放弃了.和 TensorFlow 相比,我很难弄清 PyTorch 的核心要领.但是随后不久,PyTorch 发布了一个新版本,我决定重新来过.在第二次的学习中,我开始了解这个框架的易用性.在本文中,我会简要解释 PyTorch 的核心概念,为你转入这个框架提供一些必要的动力.其中包含了一些基础概念,以及先进的功能如学习速率调整.自定义层等等.   PyTorch 的易用性如何?Andrej Karpathy 是这样评价的 资源 首先要知道的是:P

手把手教你用 TensorFlow 实现卷积神经网络(附代码)

在知乎上看到一段介绍卷积神经网络的文章,感觉讲的特别直观明了,我整理了一下.首先介绍原理部分. 通过一个图像分类问题介绍卷积神经网络是如何工作的.下面是卷积神经网络判断一个图片是否包含"儿童"的过程,包括四个步骤: ● 图像输入(InputImage) ● 卷积(Convolution) ● 最大池化(MaxPooling) ● 全连接神经网络(Fully-ConnectedNeural Network)计算. 首先将图片分割成如下图的重叠的独立小块:下图中,这张照片被分割成了77张大

手把手教你如何在阿里云服务器上搭建PHP环境?

购买阿里云服务器前,请先到阿里云官网领取幸运券,然后再购买,除了价格上有很多优惠外,还可以参与抽奖.详见:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=2a7uv47d&utm_source=2a7uv47d 首先你需要买一个阿里云服务器,买的时候可以选择操作系统(买完之后在控制台也可以更改操作系统) 我使用的是CentOS,买完服务器之后需要添加一个多语言环境,这个需要到云市场搜索"

手把手教你在友善之臂tiny4412上用uboot启动Linux内核

要想用uboot启动内核,我推荐一种方法,用dnw下载内核到开发板上,然后用uboot命令启动: 首先我在网上随便下了一个dnw工具,经过移植修改后,代码如下: /* YYX--->for tiny4412 dnw version:20170423 v1 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <malloc.h> #include <errno