TensorFlow实现cnn踩坑点


title: TensorFlow实现cnn踩坑点
copyright: true
date: 2019-12-25 20:20:00
categories:

  • nlp自然语言处理
  • CNN语言模型
    tags:
  • nlp
    mathjax: true

因为是新手,所以跟着网上的教程用tensorflow框架实现了CNN模型,并且开始了训练。在实现过程中经历了很多的坑,今天记录一下在实现过程中遇到过的坑。

教程博客

数据预处理

我一直觉得数据处理只是简单的对数据进行数据加载,然后清洗(去掉停用词、标点符号、转义字符–比如\n以及空格等冗余数据)

其实,要做的处理并不止这些。

标签处理

在预处理中需要把不同文本对应的标签转换为one-hot类型:

def dense_to_one_hot(self, labels_dense, num_classes):    
    """Convert class labels from scalars to one-hot vectors.""" 
    num_labels = labels_dense.shape[0]    
    index_offset = np.arange(num_labels) * num_classes    
    labels_one_hot = np.zeros((num_labels, num_classes))    
    temp = index_offset + labels_dense.ravel()    
    labels_one_hot.flat[temp] = 1    
    return labels_one_hot

在后面隐藏层输出结果与对应正确标签计算时候是需要的。

因为后面:

with tf.name_scope("output"):    
    W = tf.Variable(tf.truncated_normal([self.num_filters_total, self.num_classes]), name="W")    
    b = tf.Variable(tf.constant(0.1, shape=[self.num_classes]), name="b")       if l2_reg_lambda:        
        W_l2_loss = tf.contrib.layers.l2_regularizer(l2_reg_lambda)(W)    
        tf.add_to_collection("losses", W_l2_loss)    
        self.scores = tf.nn.xw_plus_b(self.h_drop, W, b, name="scores")             # (self.h_drop * W + b)    
        self.predictions = tf.argmax(self.scores, 1, name="predictions")           # 找出分数中的最大值,就是预测值

全连接输出时候,里面会通过tf.nn.xw_plus_b()计算得到的不同种类对应得到的score和以one-hot表示的标签下标值进行softmax计算误差,也就是loss值。

losses = tf.nn.softmax_cross_entropy_with_logits_v2(self.input_y, self.scores)
# softmax_cross_entropy_with_logits_v2第一个是对应标签的labels值,第二个logits值是预测分数,会自动转为对应的标签值进行计算

文本处理

文本处理除掉上面说过的去停用词去标点符号去转义字符等,还需要把文本处理成机器可读取的模式,就是把文本变成数字。

我刚开始对这个的理解就是把文本转换为对应的词向量,再喂进模型,结果发现,并不是这样。

如果直接把词向量做词嵌入处理,这样会导致工程浩大(因为会有很多重复的词出现在文档里,在喂进模型前要把对应的词向量矩阵捣鼓出来,这样的话,就会消耗计算机大量的内存,降低速率。)所以我们会先把清洗后的文本转换成对应的文本下标的ID

vocab_processor =learn.preprocessing.VocabularyProcessor(normal_param.max_document_length)
x = np.array(list(vocab_processor.fit_transform(x_texts)))

【注:x_texts是已经经过清洗的数据,x就是x_texts对应的下标矩阵】

为了防止之后加载模型时候,测试文件不能转换为和训练集一样对应的下标ID,所以在训练集转换完对应的ID后,用

然后传入cnn模型中word embeding部分,找到对应的词向量,进行后续操作。

with tf.device('/cpu:0'), tf.name_scope("embedding"):    
    self.W = tf.Variable(tf.random_uniform([self.vocab_size, self.embedding_size], -1.0, 1.0))   
    # 随机化W,得到维度是self.vocab_size个self.embedding_size大小的矩阵,随机值在-1.0-1.0之间 
    self.embedding_chars = tf.nn.embedding_lookup(self.W, self.input_x)    
    # 从id(索引)找到对应的One-hot encoding    
    self.embedding_chars_expanded = tf.expand_dims(self.embedding_chars, -1)    # 增加维度

W相当于词袋模型,每个词都是个独立的个体,所以通过tf.variable设置随机变量,然后再根据前面处理好的下标过来找到不同的词对应的embeding。

因为是词袋模型,所以不好的地方在于,会丢失上下文的关系,造成准确度偏低的情况。

所以会有词预处理模型,比如Word2vec、最近特别热门的Bert这些模型,都会想办法获取词的上下文关系,得到对应的词向量,增加预测结果的准确性。

模型搭建

在搭建CNN模型的过程中也碰到过一些问题,因为是新手,看着论文,也不知道如何下手去搭建这个模型,所以跟着教程才写了出来,在讨论碰到的问题之前,先总结一下经验。

先不提python语法问题,python是个很好的东西,对新手来说,有很多库,上手很快,是个很优美的语言。特别是在你了解到它的面向对象的特性的时候,它的优美实现会让你瞬间爱上它,反正我还没学会python。它是面向对象,我是面向百度谷歌。

接下来要说的是tensorflow框架了,TensorFlow框架是个坑,它升级到2.0版本,和1.0版本有很多不兼容的地方,但因为是新出的2.0,普及性还不是很广,出了问题翻遍它官方的issue都不一定能找到答案,所以我配合我的cuda版本装了TensorFlow1.13.1。其实这个因果关系不是特别紧密,我只是想吐槽它升级版本顺便改了改语法,让我初学者翻了一下午的资料,才发现为啥教程上是这样写没问题,我这样写就有问题了。

回到正文。

在用代码搭建CNN模型中,作者用了tf.name_scope("")把这个实现分成了词嵌入、卷积-池化、dropout、输出、loss值计算、准确值计算这六个部分

tf.name_scope(""):它给我感觉就是把复杂的网络分开来,变成几个小的模块,不会因为网络复杂了,而数据混乱没有条理,导致出错。实际上和我想的差不多吧。

loss绝对值增大

当时loss值的绝对值越来越大,并没有收敛的趋势,我找了很久的问题,在学长的帮助下才发现出在这里:

losses = tf.nn.softmax_cross_entropy_with_logits_v2(self.input_y, self.scores)

这是第一个坑, softmax_cross_entropy_with_logits_v2第一个是对应标签的labels值,第二个logits值是预测分数,会自动转为对应的标签值进行计算出相应的loss值。

讲到这里,不得不提及一下softmax是什么了,参考博客博客里面给出了相关公式,是将每个类别给出的分数通过softmax进行计算,把分值均转换为正数,通过 S ∗ i = e i ∑ ∗ j e j S*{i} = \frac{e^{i}}{\sum*{j}e^{j}} Si=jejei这个公式,把分值最高的值凸显出来,同时将输出结果映射到(0,1)之间,转换成概率。

loss值是由损失函数算出来的,这里使用交叉熵函数作为我们的损失函数。这里有交叉熵函数的公式。参考博客

对tf运行的理解

        self.input_x = tf.placeholder(tf.int32, [None, sequence_length], name="input_x")
        self.input_y = tf.placeholder(tf.float32, [None, num_classes], name="input_y")

tf和平常代码不一样,日常代码都是先赋值,再进行运算,而tf是先定义运算,再进行初始化赋值,就像上面这两行代码一样,这两行代码是预定义了input_xinput_y的值,然后再在后面

step, summaries, loss, accuracy = sess.run([global_step, dev_summary_op, cnn_init.loss, cnn_init.accuracy], feed_dic)

把定义好feed_dic的值,喂进初始化后的cnn模型中,再返回[global_step, dev_summary_op, cnn_init.loss, cnn_init.accuracy]里面的值(cnn_init就是初始化后的CNN模型)

global_step, dev_summary_op和后面的值不一样,不是从模型里面输出的值,

模型存取

在程序的运行中,会因为各种问题导致程序突然中止,所以我想要运行到一定时间就自动保存一个正在学习的数据模型,再次运行时候,如果已经有保存了的模型,就加载出来。

checkpoint_dir = os.path.abspath(os.path.join(out_dir, "checkpoint"))
checkpoint_prefix = os.path.join(checkpoint_dir, "model")
if not os.path.exists(checkpoint_dir):    
    os.makedirs(checkpoint_dir)
saver = tf.train.Saver(tf.global_variables())ckpt = tf.train.latest_checkpoint(checkpoint_dir)
if ckpt:    
    saver.restore(sess, ckpt)    
    print("CNN restore from the checkpoint {0}".format(ckpt))

继续训练后,运行了好几遍,发现并没有办法加载已有模型继续训练,究其原因,发现,问题出在session初始化上面。

sess.run(tf.global_variables_initializer())

参考博文

以此初始化全局变量

电脑性能限制

因为实验室不是专门做深度学习这块,所以没有较好的设备去完成实验,目前实验设备有:

  1. CPU

     Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz
     
     基准速度:	3.60 GHz
     插槽:	1
     内核:	4
     逻辑处理器:	8
    
  2. GPU 1块

    NVIDIA GeForce RTX 2060 SUPER

     专用 GPU 内存	8.0 GB
     共享 GPU 内存	8.0 GB
     GPU 内存	16.0 GB
    
  3. 内存

    16.0 GB

  4. 磁盘 1 (D: F: G: E:)

    WDC WD10EZEX-21M2NA0

     容量:	932 GB
     已格式化:	932 GB
     系统磁盘:	否
     页面文件:	是
     
     读取速度	199 KB/秒
     写入速度	41.0 KB/秒
     活动时间	56%
     平均响应时间	6.2 毫秒
    

目前硬件上面临的有几个困难:

  1. GPU利用率过低,不知道如何优化
  2. 读取文档的速度过慢,优化方法下面介绍

其实总结一下就是,硬件设备性能低,没法加快程序运行速度。

优化运行方案

因为内存的不足,所以在学长的建议下,我把我需要输入的数据进行分段处理,这样一来能快速看到效果,二来被打断的话,不需要重新加载那么庞大的数据继续跑,三来我电脑性能限制,我没法一次性跑那么多数据。其实第三点才是最重要的。

这就要来说说pythonyield的语法了。

yield相当于return,不同的是,它不会结束程序的运行,而是让程序在经过yield的处理后,回到开始的循环,继续向下执行。可能这样干说不太能很好的表达,所以我写一段。

def batch_iter(a, b, num_foreach):
    for i in range(num_foreach):
        c = a + b
        yield c

if __name__ == "__main__":
    batch = batch_iter(1,23)
    for b in batch:
        t = b
        print(t)

输出:

3
3
3

就是迭代器,里面batch_iter这个方法就是迭代时候会使用的方法,然后迭代完c就是输出的值。

根据这个语法的特性,我改进了一下代码:

batches = process_data_init.batch_iter(normal_param.batch_size, normal_param.num_epochs)
for batch, dev_data, dev_label, is_save in batches:    
    x_batch, y_batch = zip(*batch)    # print("y_batch", y_batch) 
    train_step(x_batch, y_batch, train_summary_write)    
    current_step = tf.train.global_step(sess, global_step)    
    if current_step % normal_param.evaluate_every == 0:        
        print("\nEvaluation:")        
        dev_step(dev_data, dev_label, writer=dev_summary_writer)

把数据集的读取改到迭代时候读取。

def batch_iter(self, batch_size, num_epochs, shuffle=True):    
    '''迭代器'''    
    # num = 1    # data = np.array(data)    # data_size = len(data)    # num_batches_per_epoch = int((data_size - 1) / batch_size) + 1    
    echo_part_num = len(self.all_text_path) // normal_param.num_database    
    for epoch in range(num_epochs):        
        print("epoch:", epoch, "/", num_epochs)        
        for part_n in range(normal_param.num_database):            
            is_save = False            
            train_data, train_label, dev_data, dev_label, vocal_size_train = self.deal_data(part=echo_part_num,n_part=part_n)            
            data = list(zip(train_data, train_label))            
            data = np.array(data)            
            data_size = len(data)            
            num_batches_per_epoch = int((data_size - 1) / batch_size) + 1            
            if shuffle:                
                shuffle_indices = np.random.permutation(np.arange(data_size))         
                shuffle_data = data[shuffle_indices]            
             else:                
                shuffle_data = data            
             for batch_num in range(num_batches_per_epoch):                
                 start_idx = batch_num * batch_size                
                 end_idx = min((batch_num + 1) * batch_size, data_size)                
                 if batch_num + 1 == num_batches_per_epoch:                    
                    is_save = True                
                    yield shuffle_data[start_idx:end_idx], dev_data,dev_label,is_save

调参

根据数据集大小以及过拟合情况,适当调整了batch_size的大小和学习率的大小、dropout的大小以及迭代的次数:

batch_size = 64
num_epochs = 50
#(视情况定,验证集loss值上升结束后开始下降说明学习已经结束)
dropout_keep_prob = 0.5
#学习率设置:1e-3
optimizer = tf.train.AdamOptimizer(1e-3)

结果

数据集我用的是THUCNew1,里面有14个class,将前三个class抽取出来,并且每个类别中的数据以10:1的比例抽取,所以实验数据集有6,472个文件数据,实验中使用TensorBoard可视化数据,结果如下:

神经网络流程图可视化

在这里插入图片描述

验证集的loss曲线图:

在这里插入图片描述

验证集的acc的曲线:

在这里插入图片描述

训练集的acc曲线:

在这里插入图片描述
训练集的loss曲线:

在这里插入图片描述
实现代码

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用TensorFlow实现CNN语音识别是一种有效的方法。在使用Python语言编写代码时,TensorFlow可以帮助我们构建和训练深度学习模型,特别是卷积神经网络(CNN)模型。 首先,我们需要导入TensorFlow库,并准备训练和测试数据集。这些数据集通常是采样率为16KHz的音频文件,每个文件对应着一个标签,表示音频中所包含的语音识别内容。 接下来,为了能够输入音频数据到CNN模型中,我们需要对音频进行预处理。这通常包括将音频文件转换为Mel频谱图的形式,以及对频谱图进行标准化和归一化。 然后,我们可以开始构建CNN模型。通常,一个基本的CNN语音识别模型由多个卷积层和池化层组成,用于提取特征。最后,我们将得到的特征输入到全连接层中,进行分类和预测。 在构建模型之后,我们需要定义损失函数和优化器来对模型进行训练。对于语音识别问题,常用的损失函数是交叉熵损失函数,优化器可以选择Adam算法。 接下来,我们将训练数据集输入到CNN模型中,并进行训练。为了更好地利用训练数据,可以使用批量梯度下降法(mini-batch gradient descent),并设置合适的批量大小和训练轮数。 在训练结束后,我们可以使用测试数据集对模型进行评估。通过计算模型在测试数据集上的准确率和损失值,可以评估模型的性能。 最后,我们可以使用经过训练的CNN模型对新的语音数据进行预测。将新的音频数据进行相同的预处理步骤,然后输入到模型中,获取模型对应的预测结果。 总结来说,使用TensorFlow实现CNN语音识别需要准备数据集、构建模型、定义损失函数和优化器、进行训练和评估,最后进行预测。通过以上步骤,我们可以较为准确地实现语音识别任务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值