tensorflow cnn文本分类

给定文本数据training.txt。
每一行格式为:{"label": "label_name", "content": "content_n"}
类别标签有四个。

通过大量其他的新闻文本训练一个word2vec模型,將赛题数据的word2vec向量作为cnn的输入。

前期数据处理:
训练word2vec的新闻数据使用的是搜狐实验室下的新闻数据(四个文件:”news_sohusite_xml.txt”,”news_tensite_xml.txt”, “sougou2008.txt”, “sougoucs2008.txt”)。读取数据后使用HanLP分词,得到Hanlp_cut_data.txt文件。

安装下载word2vec,进入文件所在目录,使用命令:

./word2vec -train Hanlp_cut_data.txt -output Hanlp_cut_news.bin -cbow 0 -size 200 -window 5 -negative 0 -hs 1 -sample 1e-3 -threads 12 -binary 1

cbow: 1表示使用cbow。默认为skip-Gram。
size:每个词的向量维度,这里我取200,在本文后面的大部分地方我写为embedding_size,一个意思。
window:训练窗口,考虑一个词前后window个词语。
sample:采样阈值,如果一个词语在训练样本中出现的频率越大,越会被采样。
negative 0 -hs 1:表示不使用NEG方法,使用HS方法。
-binary:1表示二进制存储,0表示普通存储,普通存储打开是可以看到词语和向量的

由此得到word2vec的词向量模型:Hanlp_cut_news.bin。

接下来就是如何把训练数据 training.txt 转变成输入向量。

首先我们把训练集中所有词语映射到一个表vocab中,这个表包含词语与下标。

vocab: {'UNKNOW': 0, 'w1': 1, 'w2': 2, 'w3': 3, ....'wn': n}

其中默认第一个映射为{‘UNKNOW’: 0},并且在这个过程中去掉了部分低频词。

再构造一个embedding_matrix矩阵,row_i处的向量为vocab[i]词语的word2vec向量。使用随机数初始化矩阵,如果词语在word2vec词典中存在向量则覆盖。因此,对于训练数据而言,其中低频词和word2vec词典中的未收录词其对应向量都是随机向量。

embedding_matrix (n * embedding_size):
[w1_vector
 w2_vector
 ... 
 wn_vector]

在我们输入cnn之前、处理句子的时候都是使用词语下标而不是直接使用词语向量,在cnn中的时候会使用 lookup 的方法:tf.nn.embedding_lookup(self.word_embedding, self.input_x) 將其转为向量,这样可以避免数据量过大。

同时,统计计算得到训练数据中最长的篇幅为一万多个字,平均字长为四百多,所以我划定每一条长度(max_length)为1000,不足补零,有多舍弃。由此,我们將每一行的content处理成了max_length个vocab[word],如果是第四类标签向量则为:[0,0,0,1].

使用十折交叉检验方法,把数据集划分为十份,每次取其中一份为测试数据。
embedding_matrix和所有的数据我都用pickle.load()的方法存储为二进制文件。

模型训练:
先简要介绍下text_cnn的分类过程
(该算法由YoonKim在04年论文 “Convolutional Neural Networks for Sentence Classification”中提出)。
tensorflow cnn文本分类_第1张图片
图片来源于网络

在这个图片中,输入层是max_length个词语×embedding_size个维度,不同过滤器尺寸(filter_size)有三个:2,3,4,每个尺寸的过滤器有两个(num_filters),池化部分采用的方法是最大池化,即取最大数值。合并这些输出并进行softmax处理(softmax就是將输出转为不同类别上的概率分布)

下面是对部分代码的解析:

# 加载embedding_matrix,记得以'rb'二进制文件的形式打开,否则报错
word_embedding_vector = pickle.load(open(path_to_embedding_matrix, 'rb'))
self.input_x = tf.placeholder(tf.int32, [None, max_length], name="input_x")
self.input_y = tf.placeholder(tf.float32, [None, num_classes], name="input_y")
# 设置droupout
self.dropout_keep_prob = tf.placeholder(tf.float32, name="dropout_keep_prob")  
# L2正则 
l2_loss = tf.constant(0.0) 
with tf.name_scope("embedding"):
    self.word_embedding = tf.Variable(tf.to_float(word_embedding_vector))
    # lookup转为词语对应向量
    self.embedded_chars = tf.nn.embedding_lookup(self.word_embedding, self.input_x)
    # 增加一个维度,以匹配输入input_x
    self.embedded_chars_expanded = tf.expand_dims(self.embedded_chars, -1)

在训练过程中使用droupout,随机地让某些节点不工作,可以提高模型的可靠性和避免过拟合。

        pooled_outputs = []
        for i, filter_size in enumerate(filter_sizes):
            with tf.name_scope("conv_maxpool-%s" % filter_size):
                filter_shape = [filter_size, embedding_size, 1, num_filters]
                # 卷积层权重w
                w = tf.Variable(tf.truncated_normal(filter_shape, stddev=0.1), name="conv-W")
                # 偏置b
                b = tf.Variable(tf.constant(0.1, shape=[num_filters]), name="conv-b")
                # 卷积操作 convolutional
                conv = tf.nn.conv2d(
                    self.embedded_chars_expanded, # 当前层节点矩阵
                    w, # W 是卷积矩阵权重
                    strides= [1, 1, 1, 1], # 不同维度上的步长
                    padding="VALID", # 表示卷积核不在边缘做填补,也就是“窄卷积”
                    name="conv"
                )
                # 池化操作 pool
                #  h 是经过非线性激活函数的输出
                h = tf.nn.relu(tf.nn.bias_add(conv, b), name="relu")
                pooled = tf.nn.max_pool(
                    h,
                    ksize= [1, max_length - filter_size + 1, 1, 1], # 过滤器尺寸
                    strides=[1, 1, 1, 1], # 步长信息
                    padding='VALID', # 是否零填充
                    name="pool"
                )
                # pooled_outputs保存每次的卷积结果
                pooled_outputs.append(pooled)
    num_filters_total = num_filters * len(filter_sizes)  
    self.h_pool = tf.concat(3, pooled_outputs)  
    self.h_pool_flat = tf.reshape(self.h_pool, [-1, num_filters_total])      

卷积操作和池化操作都有一个步长信息:strides[batch, height, width, channcels]
batch:batch_size样本数目的多少
height:单个样本的行数
width:列数
channcels:通道数目
二维操作只作用在height和width上,因此,一定有:strides[0] = strides[3]=1
padding = ‘SAME’表示全零填充,’VALID’表示不填充

tf.concat()在第三维上合并数据,reshape列表中的-1表示这一维度不用我们自己来指定,最后的h_pool_flat將会是 len(input_x) * num_filters_total。
下面是我自己抽象的理解……
tensorflow cnn文本分类_第2张图片

        with tf.name_scope("dropout"):
            self.h_drop = tf.nn.dropout(self.h_pool_flat, self.dropout_keep_prob)

        with tf.name_scope("output"):
            w = tf.get_variable(
                "output-w",
                shape = [num_filters_total, num_classes],
                initializer= tf.contrib.layers.xavier_initializer()
            )
            b = tf.Variable(tf.constant(0., shape=[num_classes]), name= "output-b")
            l2_loss += tf.nn.l2_loss(w)
            l2_loss += tf.nn.l2_loss(b)
            # 各个类别的概率分布
            self.scores = tf.nn.softmax(tf.nn.xw_plus_b(self.h_drop, w, b, name="scores"))
            # 取概率最大的为输出类别
            self.predictions = tf.argmax(self.scores, 1, name="predictions")

        # 计算损失函数
        with tf.name_scope("loss"):
            self.prob = tf.nn.softmax(self.scores)
            # softmax_cross_entropy_with_logits 带softmax的交叉熵计算
            losses = tf.nn.softmax_cross_entropy_with_logits(logits=self.prob, labels=self.input_y)
            # reduce_mean取均方差,后部分为L2正则,lambda是正则化的权重,l2_loss是需要正则的参数
            self.loss = tf.reduce_mean(losses) + l2_reg_lambda * l2_loss

        # 计算准确率
        with tf.name_scope("accuracy"):
            correct_predictions = tf.equal(self.predictions, tf.argmax(self.input_y, 1))
            self.accuracy = tf.reduce_mean(tf.cast(correct_predictions, "float"), name="accuracy")

模型的保存用saver.save (train_cnn.py)
加载使用训练好的模型:

checkpoint_file = tf.train.latest_checkpoint(checkpoint_dir)
graph = tf.Graph()
With graph.as_default():
    session_conf = tf.ConfigProto(
        allow_soft_placement=FLAGS.allow_soft_placement,
            log_device_placement=FLAGS.log_device_placement
    )
    sess = tf.Session(config=session_conf)
    with sess.as_default():
        saver = tf.train.import_meta_graph()

你可能感兴趣的:(算法学习,cnn,文本分类)