文章目录

  • 0x00 前言
  • 0x01 整体概述
  • 0x02 细节展开
    • 2.1 数据预处理
      • 2.1.1 获取数据
      • 2.1.2 处理数据
    • 2.2 神经网络模型搭建
      • 2.2.1 模型架构
      • 2.2.2 层
        • 全连接层
        • 激活函数
      • 2.2.3 优化器和损失函数
        • 优化器
        • 损失函数
    • 2.3 训练验证
    • 2.4 优化改进
  • 0x03 小结

0x00 前言

  最近开始学习深度学习,所以打算以写博客的形式来记录自己的学习过程。
  本篇博客的目的是梳理熟悉深度学习的整体框架神经网络模型及代码的构建以及加深深度学习整个过程的理解。基于目的,选择了较为简单的电影评论分类问题为例子。参考的书籍是 [美]Francois Chollet 著的《Python深度学习》,结合书本内容的同时也会提出我自己对部分细节的理解和总结。
  运行环境:

Name Version
python 3.6
keras 2.2.4
tensorflow-gpu 1.14.0
Numpy 1.16.2
cuda 10.1
cudnn 7.6.5


0x01 整体概述


图1-模型训练过程图


  【问题描述】:电影评论二分类想实现的工作其实就是将评论分为两个阵营——正面和负面。
  【实现过程】:评论数据输入训练好的神经网络模型,得出一个预测值(此处预测值为正面或者负面),期待该预测值就为真实目标值或者为真的概率尽可能高。那么重点就是如何训练神经网络模型,据流程图,模型由若干个层组成,而每一层都对输入的数据做某种变换,最后得出一个预测值(呵呵很神奇),当然一开始这个预测值很可能不准确,那么到底有多不准确,由损失值来衡量,损失值可以理解为预测值到真实值之间的距离(通常距离越小越好),而损失函数就是计算这个距离用的。我们要训练的对象具体来说就是各层中的权重参数w,权重参与数据变换的过程,所以理论上取一组合适的权重,可以使得预测值最准确。那么如何得到合适的权重就是优化器的工作,优化器会依据损失值来更新权重。总结一下就是以下几个步骤:

  (1)将数据输入神经网络模型,输出预测值
  (2)损失函数依据真实目标值计算损失值
  (3)优化器依据损失值更新权重

于是不断地输入数据,反复迭代,重复以上步骤就会得到一组较为合适的权重。当然会存在过拟合现象,先按下不表。
  至此,就是模型训练的大致流程。不过依旧存在很多疑问,例如全连接层是干什么的?二元交叉熵是什么?优化器具体是怎么工作的?等等。下面展开来讲。

0x02 细节展开

2.1 数据预处理

  前面的整体概述中没有提到数据处理的有关内容,在实际应用中,数据处理是一块重要的部分。因为输入模型的数据需要符合统一格式和特定的数据结构,而不做处理的原始数据往往不符合模型输入的条件,所以要进行数据预处理。

2.1.1 获取数据

  本例数据直接来自IMDB数据库

from keras.datasets import imdb# 加载数据
# 所加载的数据集其实是二维列表
# 每条评论是由其单词的整数索引构成的向量
(train_data, train_lables), (test_data, test_lables) = imdb.load_data(num_words=10000)  # 10000意思是只保留最常出现的前10000单词

书本引用:
  参数num_words=10000的意思是仅保留训练数据中前10000个最常出现的单词。低频单词将被舍弃。这样得到的向量数据不会太大,便于处理。

  trian_data、test_data: 由评论组成的二维列表(samples,features),第一维是样本批量维度,第二维是评论。每一条评论都是由其单词的整数索引构成的一维向量(一个单词对应一个整数,这个整数不会超过9999)

  train_lables、test_lables: 0、1组成的一维列表,0代表负面评论,1代表正面评论。数据标签,所谓的真实目标值,与每条评论呈一一对应关系。

2.1.2 处理数据

import numpy as np# 准备数据(合适的格式)
# 因为神经网络需要传入张量的数据结构,所以要将数据向量化,将所有评论都向量化到一样的维度此处化到10000维,因为根据最常用的前10000的单词,单词所对应的序号最大为9999
# 对列表进行one-hot编码,将其转化为0和1组成的向量def vectorize_sequences(sequences, dimension=10000):  # 这里的 sequence 其实是传入的(训练集/测试集)results = np.zeros((len(sequences), dimension))   # 创建零矩阵(二维张量)for i, sequence in enumerate(sequences):# 这里其实有点桶排的味道,将下标索引为该值的元素置1,表示有该值,其余为0results[i, sequence] = 1.                       # [1.]表示此处数据类型为浮点return resultsx_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)# 将标签也向量化,因为标签本来就是一维的向量了,所以直接转化成张量的数据结构就可了
y_train = np.asarray(train_lables).astype('float32')
y_test = np.asarray(test_lables).astype('float32')

  代码解释请参考注释☝

  此处之所以将数据类型转换为浮点型,给出我自己的理解:拓展学习空间
  将数据集类型转化为浮点型以后,在训练过程中的权重、损失值等也都从整数向浮点转变。而权重就是神经网络学习到的东西,转为浮点以后,它能够表示的范围就变大了,也就拓展了学习空间。这里的权重其实是张量矩阵,下文再展开来讲。

2.2 神经网络模型搭建

  在讲解以下内容的时候,可能会有这样的疑问:为什么要这样那样搭建模型就可以了?
  事实上根据不同的实际问题,要搭建不同的神经网络,而为什么要这样搭建,更多的是经验之谈。虽然存在一些原则性的指导,可以指导你采取某种模型架构更加合适,但是很难严谨科学逻辑地推出具体哪种模型是最好的。所以深度学习更偏向是一门工程性科学,需要不断实践。
  本篇博客的目的也不是帮助你建立搭建模型的直觉,所以你要知道,接下来针对电影评论分类问题所搭建的模型,是已经已知且比较合适的。当然也可以采取其他不同的尝试。

2.2.1 模型架构

  这里采用Sequential类来做层的线性堆叠架构,这是目前最常见的网络架构。所谓线性堆叠,从前文的流程图可以看出,是由单一输入映射到单一输出,再依次顺序连接各层。“深度学习”的深度也就体现在层的堆叠上。

from keras import modelsmodel = models.Sequential()

  这样子我们建立了一个模型的框架,之后就可以往这个框架中添加层。当然,除了线性堆叠的架构,还有其他各种各样的有向无环的拓扑架构可以采用。

2.2.2 层

  对于电影评论二分类问题,此处使用全连接层(fully connected layer,也叫密集层 dense layer),激活函数采用relu、sigmoid。

from keras import layers# Dense的第一个参数是该层的输出维度,也可以认为是该层的学习空间
model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))  # 这里的10000指的是每个输入样本数据的维度,不包含样本数量的维度
model.add(layers.Dense(16, activation='relu'))  # 从第二层开始,就不用写输入样本数据的维度,因为自动根据上一层进行调节
model.add(layers.Dense(1, activation='sigmoid'))

  代码解释请参考注释☝

全连接层

  不同的张量格式与不同的数据处理类型需要用到不同的层。每条电影评论是简单的向量数据,保存在形状为(samples, features)的2D张量中,适合采用全连接层 [对应于keras的Dense类]。

  【全连接层做了什么工作?】
  每个全连接层都做了以下张量矩阵的线性运算:
o u t p u t = d o t ( i n p u t , w ) + b . output = dot(input, w) + b. output=dot(input,w)+b.
  【权重w】
  讲到这里了就来说说权重。输入的数据是张量的数据结构,那么权重也是张量。
  以第一层为例,它的输出是16维的。输入的评论数据是一维向量(此一维与前16维非同一概念,前16维可以认为是一维向量中有16个数据),所以经过数据变换,10000维的输入变成16维的输出,那么权重矩阵的形状就为(input_dimension, 16),input_dimension = 10000。也由线性代数的知识可知,input的形状是[1x10000],那么w的形状必须是[10000x16]才可做矩阵乘法。
  这一过程可以通俗的理解为把信息做了一个加权和(矩阵乘法的计算过程其实也就做了这么一个事)。源数据的所有信息都参与了运算,所以也可以看作是一个信息浓缩的过程。

  【全连接层的作用?】
  刚刚讲了全连接层其实就做了矩阵的乘法和加法运算,本质是一个加权,有点浓缩的味道
  我参考了其他人的博客,觉得这句话说的挺好,引用:

在 CNN 中,全连接常出现在最后几层,用于对前面设计的特征做加权和。比如 mnist,前面的卷积和池化相当于做特征工程,后面的全连接相当于做特征加权。

激活函数

  激活函数也叫做非线性函数。
  本例用到了relu(rectified linear unit, 整流线性单元)和sigmoid。当然还有其他名字稀奇古怪的激活函数。

  【为什么要用激活函数?】
  激活函数是非线性的,正是因为非线性的存在才使得层的堆叠有了意义。不难想象,如果没有非线性函数,只有线性函数的堆叠,那么最终的输出也是线性的。

   o u t p u t 1 = i n p u t 1 ∗ w 1 + b 1. — — ( 1 ) output1= input1*w1 + b1. ——(1) output1=input1∗w1+b1.——(1)

   o u t p u t 2 = o u t p u t 1 ∗ w 2 + b 2. — — ( 2 ) output2= output1*w2 + b2. ——(2) output2=output1∗w2+b2.——(2)

   ( 1 ) ( 2 ) 联 立 : (1)(2)联立: (1)(2)联立:
o u t p u t 2 = i n p u t 1 ∗ w 1 ∗ w 2 + b 1 ∗ w 2 + b 2. output2 = input1*w1*w2 + b1*w2 + b2. output2=input1∗w1∗w2+b1∗w2+b2.
  我可以令 w 3 = w 1 ∗ w 2 , b 3 = b 1 ∗ w 2 + b 2. 使 得 : o u t p u t 2 = i n p u t 1 ∗ w 3 + b 3 w3 = w1 * w2,b3 = b1*w2 + b2.使得:output2= input1*w3 + b3 w3=w1∗w2,b3=b1∗w2+b2.使得:output2=input1∗w3+b3
  所以最终都可以用一层来表示,于是深度就失去了意义,因为不管叠几层都只是在线性变换中打转,网络能学到的东西也只是输入数据的线性变化的集合,这十分有限。

  引用书本原话

这样Dense层就只能学习输入数据的线性变换(仿射变换):该层的假设空间是从输入数据到16位空间所有可能的线性变换集合。这种假设空间非常有限,无法利用多个表示层的优势,因为多个线性层堆叠实现的仍是线性运算,添加层数并不会扩展假设空间。


  relu

图2-整流线性单元函数

  由图可知,relu函数就是把负数的部分全部置为0,其他不变。

每个带有relu激活的Dense层都实现了下列张量运算:
o u t p u t = r e l u ( d o t ( w , i n p u t ) + b ) output = relu(dot(w, input) + b) output=relu(dot(w,input)+b)


  sigmoid

图3-sigmoid函数

  由图可知,sigmoid函数把所有值都映射到[0,1]的范围内,其输出值可以看作概率。

最后一层使用sigmoid激活以输出一个0~1范围内的概率值(表示样本的目标值等于1的可能性,即评论为正面的可能性)。


2.2.3 优化器和损失函数

  优化器使用rmsprop,不过对于初学者来说接触最早的应该是SGD(stochastic gradient descent, 随机梯度下降)。
  损失函数使用二元交叉熵。

# 模型编译
# 配置优化器、损失函数和指标
model.compile(optimizer='rmsprop',loss='binary_crossentropy',  # 二元交叉熵metrics=['accuracy'])

  rmspropbinary_crossentropy都是keras库内置的,可以直接传入。如果想修改其中的部分参数或者自定义,那么就需要传入优化器对象或者损失函数对象。

优化器

  对于优化器rmsprop,说来惭愧,由于数学素养的问题,在查阅了相关资料以后仍旧是不怎么懂(以后可能会去弄懂吧呵呵)。所以为了讲明白优化器的大致原理,接下来我以SGD为例,在之后的优化改进中,我也会更改优化器为SGD并比较两者实现效果。

  【SGD】
  这里我就简单讲解一下我所消化理解的SGD的工作原理。
  梯度是什么?梯度就是导数向多维的推广,从理解层面,可以简单认为是导数。

  数据输入神经网络模型,经过一系列的数据变换后得到输出,输出再经过损失函数得到损失值。这么一个过程就完成了从输入数据到损失值的映射,可以看作是一种函数关系: l o s s = f ( i n p u t ) loss = f(input) loss=f(input) 。根据实际计算过程我们知道这个函数是可微的。最终我们期待该映射能使loss最小。

  优化器的工作目的是找到一组合适的权重,所以这里我们把权重当作参数,输入的数据当作这个函数的常数。那么函数就变成 l o s s = f ( w ) loss = f(w) loss=f(w) ,也是可微的。求这个函数相对于损失值的梯度,就可以知道当前点也就是当前权重下,loss的增长趋势。然后我们就只需要将权重往梯度的反方向移动一小步,就可以使得loss减小一点点,重复这个过程就可以使得loss处在最低点(理论上)。那么更新权重的过程就可以用这样的式子简单表示:
w − = s t e p ∗ g r a d i e n t w -= step * gradient w−=step∗gradient
  以一维函数为例,导数大于0,该点呈增长趋势,那么就让x减小一点点,可以使得y减小。导数小于0,该点呈下降趋势,那么就让x增大一点点,可以使得y减小。图示如下:



  在解释的过程中我也省去了部分概念,现在补上。
  计算损失相对于网络参数的梯度叫做反向传播。求梯度的过程运用了链式求导法则,因为层的堆叠,上一层的输出是下一层的输入,可以认为是复合函数的嵌套,复合函数的求导就用到了链式求导法则。

  可能会有一些疑问,既然可导为什么不直接计算导数等于0的点,事实上,在参数(也就是权重)少的时候可以这样做,但是在实际应用中,参数可达几百甚至上千,这样做就不现实了。

  还有一个容易想到的问题,单纯的SGD可能会陷入局部低谷而再也出不来,基于这个现象,SGD衍生出其他的变体,例如动量SGD,这里只是提一下,不作介绍。

  最后提醒一下,原文:

不可能将神经网络的实际训练过程可视化,因为你无法用人类可以理解的方式来可视化1000000维空间。因此最好记住,在这些低维表示中形成的直觉在实践中不一定总是准确的。这在历史上一直是深度学习研究的问题来源。

损失函数

  所谓的损失函数,就是用来衡量预测值与真实值之间的距离用的。这里采用二元交叉熵,还有其他很多损失函数,但是在面临全新的问题时,往往需要根据实际情况自定义损失函数。

  【二元交叉熵】
  引用书本内容:

由于你面对的是一个二分类问题,网络输出是一个概率值(网络最后一层使用sigmoid激活函数,仅包含一个单元),那么最好使用binary_crossentropy(二元交叉熵)损失。这并不是唯一可行的选择,比如你还可以使用mean_squared_error(均方误差)。但对于输出概率值的模型,交叉熵(crossentropy)往往是最好的选择。交叉熵是来自于信息论领域的概念,用于衡量概率分布之间的距离,在这个例子中就是真实分布与预测值之间的距离。


2.3 训练验证

  因为要在训练过程中同时观察在陌生数据上的精度,所以需要在训练集中分出部分验证集。

# 将训练集的前10000个取出来做验证集
x_val = x_train[:10000]
partial_x_train = x_train[10000:]y_val = y_train[:10000]
partial_y_train = y_train[10000:]

  【接下来开始训练】

# 开始训练
history = model.fit(partial_x_train,  # 训练集partial_y_train,  # 训练集标签epochs=20,        # 轮次batch_size=512,   # 批量validation_data=(x_val, y_val)  # 验证集)

  轮次: 轮次20意味着所有数据迭代20遍。也就是一个数据集重复输入20遍。
  批量: 考虑到容量问题,把数据集的数据拆成小批次的大小来依次输入模型。往往是一个小批量的数据输入完后,计算损失值的平均值,再更新权重,也就是一个批量更新一次权重。合适的批量很重要。


  【绘制训练损失和验证损失】

  调用model.fit()返回了一个History对象。这个对象有一个成员history它是一个字典,包含训练过程中的所有数据[val_acc,acc,val_loss,loss]。

import matplotlib.pyplot as plthistory_dict = history.history
epochs = range(1, len(loss_values)+1)# 绘制训练损失和验证损失
loss_values = history_dict['loss']  # 训练损失
val_loss_value = history_dict['val_loss']  # 验证损失plt.plot(epochs, loss_values, 'bo', label='Training loss')  # 'bo'表示蓝色圆点
plt.plot(epochs, val_loss_value, 'b', label='Validation loss')  # 'b'表示蓝色实线
plt.title('Training and Validation Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()# 绘制训练精度和验证精度
plt.clf()  # 清空图像
acc = history_dict['acc']
val_acc = history_dict['val_acc']plt.plot(epochs, acc, 'bo', label='Training Acc')  # 'bo'表示蓝色圆点
plt.plot(epochs, val_acc, 'b', label='Validation Acc')  # 'b'表示蓝色实线
plt.title('Training and Validation Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
plt.show()

         图4-rmsprop损失图                图5-rmsprop精度图


  由图可知,随着训练轮次增加,在训练集上的损失越来越小,精度越来越大,而在验证集上的损失先减小后增大,验证精度先增大后减小。很明显发生了过拟合,也就是在训练集上的效果越来越好,最后使得该神经网络模型变成了只针对该训练集的效果好,在陌生的数据集上效果欠佳。


2.4 优化改进

  通过观察发现,验证集的损失在轮次3、4左右达到最低点,所以只要训练4个轮次就可以停止了。
  但是在改进之前,我们试着更改一下优化器为SGD

from keras import optimizerssgd = optimizers.SGD(lr=0.001, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(optimizer=sgd,  # 传入优化器对象loss='binary_crossentropy',  # 二元交叉熵metrics=['accuracy'])

          图6-SGD损失图                  图7-SGD精度图

  由图对比可以发现,SGD的收敛速度没有rmsprop快,所以针对电影评论二分类问题,优化器rmsprop更加适合。

  【改进轮次】
  改轮次为4次,并且直接在所有训练集上训练,没有验证集,并且直接在测试集上评估正确率。

history = model.fit(x_train,  # 训练集y_train,  # 训练集标签epochs=4,        # 轮次batch_size=512,   # 批量)# 在测试集上评估
result = model.evaluate(x_test, y_test)
print(result)

  【评估结果】

[0.3081159823989868, 0.87684]

  【在测试集上预测】

# 在测试集上预测
print(model.predict(x_test))

  【预测结果】

[[0.14623103][0.9995303 ][0.6543776 ]...[0.08932507][0.06080121][0.5334659 ]]

如你所见,网络对某些样本的结果非常确信(大于等于0.99,或者小于等于0.01),但对其他结果却不那么确信(0.6或0.4)。


0x03 小结

  至此,你可能仍旧对深度学习过程的部分细节有点不理解,但是应当对整体的框架构建以及代码构成有了更加清晰的认识,本篇博客的目的也就是这个。然后在此基础上可以自行进行DIY的尝试,例如尝试新的激活函数或者模型的架构等。

源代码参考:Github

  有错误或者疑问欢迎在评论区提出!

【深度学习框架简单梳理】电影评论二分类问题相关推荐

  1. 深度学习实战案例:电影评论二分类

    第一个深度学习实战案例:电影评论分类 公众号:机器学习杂货店 作者:Peter 编辑:Peter 大家好,我是Peter~ 这里是机器学习杂货店 Machine Learning Grocery~ 本 ...

  2. 【深度学习】Keras实现回归和二分类问题讲解

    [深度学习]Keras实现回归和二分类问题讲解 文章目录 [深度学习]Keras实现回归和二分类问题讲解 1 回归问题1.1 波士顿房价预测数据集1.2 构建基准模型1.3 数据预处理1.4 超参数 ...

  3. AI:神经网络IMDB电影评论二分类模型训练和评估

    AI:Keras神经网络IMDB电影评论二分类模型训练和评估,python import keras from keras.layers import Dense from keras import ...

  4. 【深度学习kears+tensorflow】电影评论分类:二分类问题

    目录 Classifying movie reviews: a binary classification example 电影评论分类:二分类问题 The IMDB dataset IMDB 数据集 ...

  5. 深度学习CNN模型预测电影评论中的情感问题

    情感分析是一种自然语言处理问题,其中理解文本并预测潜在意图. 在这篇文章中,您将了解如何使用Keras深度学习库在Python中预测电影评论的积极或消极情绪. 阅读这篇文章后你会知道: 关于自然语言处 ...

  6. 《Python深度学习》3.4电影评论分类:二分类问题

    完整的敲了一遍,感觉还不错.但是还是有诸多问题. 在powershell里面输入conda activate tensowflow激活tensowflow IMDB中文章长短不一致,0,1,2这三个数 ...

  7. windows10使用cuda11搭建pytorch深度学习框架——运行Dlinknet提取道路(二)——代码运行问题解决

    运行程序 去github上下载Dlinknet的代码 https://github.com/zlckanata/DeepGlobe-Road-Extraction-Challenge 把数据集放进da ...

  8. 深度学习框架【MxNet】的安装

    文章目录 前言 一.MXNet的前世今生 二.mxnet的安装 1.CPU安装 2.GPU安装,以cuda10.1为例 三.报错提醒 前言 本文简介安装mxnet深度学习框架,下边开始对深度学习框架m ...

  9. pytorch 图像分割的交并比_PyTorch深度学习框架——初识

    PyTorch深度学习框架 简单介绍 PyTorch 是一个针对深度学习, 并且使用GPU 和CPU来优化的tensor library(张量库). 学过Tensorflow的人或许有话说,这些事情T ...

最新文章

  1. OpenCV中导向滤波介绍与应用
  2. 创新方法系列 如何找联系 符号化就是找数学中的等于==关系,遇到等号请留意
  3. 掌握XML系列(四)---创建格式良好的XML文档
  4. typescript 类型映射 (ReadOnly、Partial)
  5. Halcon算子学习:sample_object_model_3d
  6. IFIX 需要权限打开某个画面
  7. mug网络用语_各种游戏术语
  8. 百度竞价教程 借助百度热力图让你的效果翻10倍
  9. Java EE eclipse 优化配置(2020)
  10. Au 音频效果参考:振幅和压限
  11. 材料成型计算机仿真技术,材料成型计算机模拟分析(各种仿真软件介绍).ppt
  12. 广域网宽带接入技术一
  13. 解决安装Chrome翻译插件总是报CRX_HEADER_INVALID
  14. 计算机系统要素 - 布尔逻辑/布尔运算构建ALU
  15. WEB前端面试2014阿里旺旺
  16. python zxing 识别条码_详解利用python识别图片中的条码(pyzbar)及条码图片矫正和增强...
  17. 深入理解计算机系统(2.8)---浮点数的舍入,Java中的舍入例子以及浮点数运算(重要)
  18. js美化系统默认Prompt仿APP移动端弹出,可以自行修改
  19. 32位计算机处理64位数据,win764位和32位区别
  20. 河南新乡:牧野区王村镇手绘文明墙巩固文明果

热门文章

  1. python 协程 (概念+示例代码)
  2. 2021年G2电站锅炉司炉新版试题及G2电站锅炉司炉考试试卷
  3. 一条mysql查询语句的执行过程
  4. 马化腾、马云、李彦宏,谁更具狼性?
  5. “幻核”跑路,数字藏品路在何方?
  6. linux 常用快捷键及命令
  7. 20180529-C · Comic book characters · ggplot2 geom_bar 柱状图 条形图 facet_wrap 分面 · R 语言数据可视化 案例 源码
  8. Unity UnityWebRequest Get与Post应用
  9. java中List和Array相互转换
  10. 11.5 Vue day06 父子组件通信、自定义事件