编辑 | 安可
出品 | 磐创AI技术团队
【磐创AI导读】:本系列文章介绍了与tensorflow的相关知识,包括其介绍、安装及使用等。本篇文章将接着上篇文章继续介绍它的安装及部分使用。查看上篇:文末福利|一文上手TensorFlow2.0(一)。想要获取更多的机器学习、深度学习资源,欢迎大家点击上方蓝字关注我们的公众号:磐创AI。
系列文章目录:
- Tensorflow2.0 介绍
- Tensorflow 常见基本概念
- 从1.x 到2.0 的变化
- Tensorflow2.0 的架构
- Tensorflow2.0 的安装(CPU和GPU)
- Tensorflow2.0 使用
- “tf.data” API
- “tf.keras”API
- 使用GPU加速
- 安装配置GPU环境
- 使用Tensorflow-GPU
2. TensorFlow2.0安装
Tensorflow兼容性最好的是Unix内核的系统,如Linux,MacOS等。另外TensorFlow的GPU版本仅支持Linux环境,不支持Windows和Mac环境,因此本节的安装部分仅针对Linux系统环境。我们会统一使用Anaconda,在Mac和Windows下安装的过程也较为简单,读者可以自行参考其官方文档。Anaconda官网有各个平台详细的安装使用教程:https://docs.anaconda.com/anaconda/install/。
作者使用的开发环境如下:
1. 安装Anaconda
我们打算使用python3.6,因此我们下载Anaconda5.2.0版本,该版本对应的python版本是3.6.5,为了下载的更快一点,我们从清华大学的镜像站下载:https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/?C=N&O=D,下载“Anaconda3-5.2.0-Linux-x86_64.sh”文件。
(1)安装Anaconda
1. 执行“bashAnaconda3-5.2.0-Linux-x86_64.sh”,提示需要阅读licenses,按下回车继续,如图1所示。
图1 安装Anaconda
2. 出现提示是否接受licenses,输入“yes”回车。提示Anaconda将要安装的位置,回车确认。
图2
3.提示是否要写入配置文件,输入“yes”回车。
图3
4.提示是否安装“VSCode”,输入“no”回车。安装完成。
图4
如图5所示,安装好后我们键入“python3”,此时运行的还是系统自带的python版本,我们执行“source ~/.bashrc”让配置生效,此时在执行“python3”,运行的就是Anaconda。
图5
(2)创建虚拟环境
接下来我们在Anaconda中创建一个虚拟Python环境,终端中执行如下命令:
代码语言:javascript复制
conda create --name apython python=3.6
接着出现提示是否继续,输入“y”回车,稍等片刻一个Python3.6的虚拟环境就创建好了。此时系统中有多个版本的Python,为了方便使用,我们配置一下环境变量,为每个版本的Python设置一个别名。另外为了后面方便使用“pip”来管理虚拟环境的包,我们为虚拟环境的“pip”命令也创建一个别名。 编辑“~/.bashrc”文件,在文件末尾增加如下内容:
代码语言:javascript复制
alias python="/usr/bin/python2"
alias python3="/usr/bin/python3"
alias apython="/home/lqhou/anaconda3/envs/apython/bin/python3"
alias apip="/home/lqhou/anaconda3/envs/apython/bin/pip"
注意Anaconda的路径要根据实际情况来填写,“/home/lqhou/anaconda3”为作者系统上Anaconda的安装路径。配置完成后键入“source ~/.bashrc”让配置生效,之后我们分别执行“python”、“python3”和“apython”命令,如图6所示:
图6 测试别名
这里python和python3命名指向的都是系统自带的python版本,apython命令指向的是我们刚刚创建的python虚拟环境。这里需要注意,当我们要使用“pip”命令为我们创建的python虚拟环境安装包时,需要使用这里我们配置的“apip”命令,直接使用pip或pip3命令,会把包安装到系统自带的python环境中。
2. 安装TensorFlow
GPU版的TensorFlow包含了CPU版本,如果读者手上有GPU资源的话,可以直接参考后文会提到的安装GPU版的TensorFlow。
我们可以直接使用“pip installtensorflow==2.0.0-alpha0”命令来进行安装。由于作者使用的pip源还没有加入“TensorFlow2.0.0-alpha0”版本,所以这里我们直接到“PyPi”网站下载TensorFlow2.0 Alpha版的安装包。进入网址:https://pypi.org/project/tensorflow/2.0.0a0/#files,部分安装包如图7所示:
图7 TensorFlow2.0 Alpha版安装包列表
这里我们需要根据实际的Python版本和操作系统环境来下载相应的安装包,这里作者的python版本是python3.6.8,操作系统是64位的Ubuntu16.04。因此作者下载的是“tensorflow-2.0.0a0-cp36-cp36m-manylinux1_x86_64.whl”。
下载完成后,我们执行如下命令进行安装。
代码语言:javascript复制
apip install tensorflow-2.0.0a0-cp36-cp36m-manylinux1_x86_64.whl
安装完成后,我们进入python的交互式解释器环境验证安装是否成功,如图8所示:
图8 测试TensorFlow2.0 Alpha是否安装成功
3. 使用Jupyter Notebook
JupyterNotebook是一个开源的Web应用程序,常被用于交互式的开发和展示一些数据科学项目(例如数据清洗和转换、数据可视化以及机器学习等等)。在本章中为了方便大家学习,我们会使用Jupyter NoteBook作为我们的编程环境(读者也可以使用Google的Colab:https://colab.research.google.com
),后面章节的项目和代码,读者可以任意选择自己喜欢的编程工具。
在我们安装好Anaconda后,Anaconda集成了Jupyter NoteBook,因此我们可以直接使用,如图9所示。
图9 Anaconda界面
我们直接点击Jupyter下方的“运行”即可启动Jupyter(也可以在终端中输入“jupyter-notebook”来启动),启动之后会自动打开一个WEB页面,如图10所示。这里列出了默认路径下的所有目录和文件,我们可以打开自己存放代码的目录。
图10 Jupyter Notebook启动之后打开的WEB界面
如图11所示,点击页面右上角的“new”菜单,再点击“python[conda env:apython3]”菜单之后就会创建一个新的后缀名为“ipynb”的notebook文件。读者的“new”菜单中可能只有一个“Python”kernel,而没有另外两个Anaconda的python环境的kenel。这里读者可以在命令行下执行命令“source activate apython3”进入我们之前创建的“apython3”虚拟环境,然后再执行命令“jupyter-notebook”命令启动Jupyter,这时我们在“new”菜单下就可以看到我们需要使用的kernel了。
图11 创建一个新的notebook文件
新创建的notebook文件会自动的在新的标签页打开,如图12所示,新创建的是一个空的notebook文件。
图12 打开后的notebook文件
如图13所示,我们在notebook的单元格内输入代码,点击“Run”之后会在单元格的下方显示代码运行的结果。
图13 在notebook文件中编写代码
3 TensorFlow2.0使用
3.1 “tf.data”API
除了GPU和TPU等硬件加速设备以外,一个高效的数据输入管道也可以很大程度的提升模型性能,减少模型训练所需要的时间。数据输入管道本质是一个ELT(Extract、Transform和Load)过程:
- Extract:从硬盘中读取数据(可以是本地的也可以是云端的)。
- Transform:数据的预处理(例如数据清洗、格式转换等)。
- Load:将处理好的数据加载到计算设备(例如CPU、GPU以及TPU等)。
数据输入管道一般使用CPU来执行ELT过程,GPU等其他硬件加速设备则负责模型的训练,ELT过程和模型的训练并行执行,从而提高模型训练的效率。另外ELT过程的各个步骤也都可以进行相应的优化,例如并行的读取数据以及并行的处理数据等。在TensorFlow中我们可以使用“tf.data”API来构建这样的数据输入管道。
我们首先下载接下来的实验中需要用的图像数据集(数据集的下载地址为“https://storage.googleapis.com/download.tensorflow.org/example_images/”,由于该地址需要访问外国网站后才能访问,本书作者已经将下载好的数据集上传到百度云网盘,下载地址为“https://pan.baidu.com/s/16fvNOBvKyGVa8yCB5mDUOQ”。)
该数据集是一个花朵图片的数据集,将下载下来的数据解压后如图2-15所示,除了一个License文件以外主要是五个分别存放着对应类别花朵图片的文件夹。其中“daisy(雏菊)”文件夹中有633张图片,“dandelion(蒲公英)”文件夹中有898张图片,“roses(玫瑰)”文件夹中有641张图片,“sunflowers(向日葵)”文件夹中有699张图片,“tulips(郁金香)”文件夹中有799张图片。
图14 解压后的数据集
接下来我们开始实现代码,首先我们导入需要使用的包:
代码语言:javascript复制
import tensorflow as tf
import pathlib
pathlib提供了一组用于处理文件系统路径的类。导入需要的包后,可以先检查一下TensorFlow的版本:
代码语言:javascript复制
print(tf.__version__)
首先获取所有图片样本文件的路径:
代码语言:javascript复制
# 获取当前路径
data_root = pathlib.Path.cwd()
# 获取指定目录下的文件路径(返回是一个列表,每一个元素是一个PosixPath对象)
all_image_paths = list(data_root.glob('*/*/*'))
print(type(all_image_paths[0]))
# 将PosixPath对象转为字符串
all_image_paths = [str(path) for path in all_image_paths]
print(all_image_paths[0])
print(data_root)
输出结果如图15所示:
图15 文件路径输出结果
接下来我们需要统计图片的类别,并给每一个类别分配一个类标:
代码语言:javascript复制
# 获取图片类别的名称,即存放样本图片的五个文件夹的名称
label_names = sorted(item.name for item in data_root.glob('*/*/') if item.is_dir())
# 将类别名称转为数值型的类标
label_to_index = dict((name, index) for index, name in enumerate(label_names))
# 获取所有图片的类标
all_image_labels = [label_to_index[pathlib.Path(path).parent.name]
print(label_to_index)
print("First 10 labels indices: ", all_image_labels[:2])
print("First 10 labels indices: ", all_image_paths[:2])
输出结果如图16所示,daisy(雏菊)、dandelion(蒲公英)、roses(玫瑰)、sunflowers(向日葵)、tulips(郁金香)的类标分别为0、1、2、3、4、5。
图16 图片类标的输出结果
处理完类标之后,我们接下来需要对图片本身做一些处理,这里我们定义一个函数用来加载和预处理图片数据:
代码语言:javascript复制
def load_and_preprocess_image(path):
# 读取图片
image = tf.io.read_file(path)
# 将jpeg格式的图片解码,得到一个张量(三维的矩阵)
image = tf.image.decode_jpeg(image, channels=3)
# 由于数据集中每张图片的大小不一样,统一调整为192*192
image = tf.image.resize(image, [192, 192])
# 对每个像素点的RGB值做归一化处理
image /= 255.0
return image
完成对类标和图像数据的预处理之后,我们使用“tf.data.Dataset”来构建和管理数据集:
代码语言:javascript复制
# 构建图片路径的“dataset”
path_ds = tf.data.Dataset.from_tensor_slices(all_image_paths)
# 使用AUTOTUNE自动调节管道参数
AUTOTUNE = tf.data.experimental.AUTOTUNE
# 构建图片数据的“dataset”
image_ds = path_ds.map(load_and_preprocess_image,
num_parallel_calls=AUTOTUNE)
# 构建类标数据的“dataset”
label_ds = tf.data.Dataset.from_tensor_slices(tf.cast(all_image_labels, tf.int64))
# 将图片和类标压缩为(图片,类标)对
image_label_ds = tf.data.Dataset.zip((image_ds, label_ds))
print(image_ds)
print(label_ds)
print(image_label_ds)
输出结果如图17所示:
图17 构建的“dataset”
在代码中,我们使用了“from_tensor_slices”方法使用张量的切片元素构建“dataset”,“tf.data.Dataset”类还提供了“from_tensor”直接使用单个张量来构建“dataset”,以及可以使用生成器生成的元素来构建“dataset”的“from_generator”方法。
我们使用了“tf.data.Dataset”的“map”方法,该方法允许我们自己定义一个函数,将原数据集中的元素依次经过该函数处理,并将处理后的数据作为新的数据集,处理前和处理后的数据顺序不变。例如这里我们自己定义了一个“load_and_preprocess_image”函数,将“path_ds”中的图片路径转换成了经过预处理的图像数据,并保存在了“image_ds”中。
最后我们使用“tf.data.Dataset”的“zip”方法将图片数据和类标数据压缩成“(图片,类标)”对,其结构如图17所示。我们可视化一下数据集中的部分数据:
代码语言:javascript复制
import matplotlib.pyplot as plt
plt.figure(figsize=(8,8))
for n,image_label in enumerate(image_label_ds.take(4)):
plt.subplot(2,2,n 1)
plt.imshow(image_label[0])
plt.grid(False)
plt.xticks([])
plt.yticks([])
plt.xlabel(image_label[1])
结果如图18所示:
图18 数据集中部分数据的可视化
接下来我们用创建的dataset训练一个分类模型,这个例子的目的是让读者了解如何使用我们创建的dataset,为了简单,我们直接使用“tf.keras.applications”包中训练好的模型,并将其迁移到我们的花朵分类任务上来。这里我们使用“MobileNetV2”模型。
代码语言:javascript复制
# 下载的模型在用户根目录下,具体位置:“~/.keras/models/
mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_192_no_top.h5”
mobile_net = tf.keras.applications.MobileNetV2(input_shape=(192, 192, 3),
include_top=False)
# 禁止训练更新“MobileNetV2”模型的参数
mobile_net.trainable = False
当我们执行代码后,训练好的“MobileNetV2”模型会被下载到本地,该模型是在ImageNet数据集上训练的。因为我们是想把该训练好的模型迁移到我们的花朵分类问题中来,所以代码我们设置该模型的参数不可训练和更新。
接下来我们打乱一下数据集,以及定义好训练过程中每个“batch”的大小。
代码语言:javascript复制
# 使用Dataset类的shuffle方法打乱数据集
image_count = len(all_image_paths)
ds = image_label_ds.shuffle(buffer_size=image_count)
# 让数据集重复多次
ds = ds.repeat()
# 设置每个batch的大小
BATCH_SIZE = 32
ds = ds.batch(BATCH_SIZE)
# 通过“prefetch”方法让模型的训练和每个batch数据集的加载并行
ds = ds.prefetch(buffer_size=AUTOTUNE)
在代码中,我们使用“tf.data.Dataset”类的“shuffle”方法将数据集进行打乱。代码使用“repeat”方法让数据集可以重复获取,通常情况下如果我们一个“epoch”只对完整的数据集训练一遍的话,可以不需要设置“repeat”。“repeat”方法可以设置参数,例如“ds.repeat(2)”是让数据集可以重复获取两遍,即一个训练回合(epoch)中我们可以使用两遍数据集,不加参数的话,则默认可以无限次重复获取数据集。
代码里我们设置了训练过程中一个“batch”的大小。我们使用“tf.data.Dataset.prefetch”方法让ELT过程的 “数据准备(EL)”和“数据消耗(T)”过程并行。
由于“MobileNetV2”模型接收的输入数据是归一化后范围在[-1,1]之间的数据,我们在第31行代码中对数据进行了一次归一化处理后,其范围在[0,1]之间,因此我们需要将我们的数据映射到[-1,1]之间。
代码语言:javascript复制
# 定义一个函数用来将范围在[0,1]之间的数据映射到[-1,1]之间
def change_range(image,label):
return 2*image-1, label
# 使用“map”方法对dataset进行处理
keras_ds = ds.map(change_range)
接下来我们定义模型,由于预训练好的“MobileNetV2”返回的数据维度为“(32,6,6,1280)”,其中32是一个“batch”的大小,“6,6”代表输出的特征图的大小为“6X6”,1280代表该层使用了1280个卷积核。为了适应我们的分类任务,我们需要在“MobileNetV2”返回数据的基础上再增加两层网络层。
代码语言:javascript复制
model = tf.keras.Sequential([
mobile_net,
tf.keras.layers.GlobalAveragePooling2D(),
tf.keras.layers.Dense(len(label_names))])
全局平均池化(GAP,Global Average Pooling)将每一个特征图求平均,将该平均值作为该特征图池化后的结果,因此经过该操作后数据的维度变为了(32,1280)。由于我们的花朵分类任务是一个5分类的任务,因此我们再使用一个全连接(Dense),将维度变为(32,5)。
接着我们编译一下模型,同时指定使用的优化器和损失函数:
代码语言:javascript复制
model.compile(optimizer=tf.keras.optimizers.Adam(),
loss='sparse_categorical_crossentropy',
metrics=["accuracy"])
model.summary()
“model.summary()”可以输出模型各层的参数概况,如图19所示:
图19 模型各层的参数概况
最后我们使用“model.fit”训练模型:
代码语言:javascript复制
model.fit(ds, epochs=1, steps_per_epoch=10)
这里参数“epochs”指定需要训练的回合数,“steps_per_epoch”代表每个回合要取多少个“batch”数据,通常“steps_per_epoch”的大小等于我们数据集的大小除以“batch”的大小后上取整。关于模型的训练部分,我们在后面中会详细介绍。
在本节中我们简单了解了“tf.data”API的使用,在后面章节的项目实战部分我们还会用到该API来构建数据输入管道,包括图像以及文本数据。