【不周山之数据科学】深度学习入门指南(基于 TensorFlow)

深度学习作为时下最火热的话题,想必不少同学都非常感兴趣,但是看热闹容易,找门道难。如何能快速上手通过实战有个大概的认识呢?相信阅读完本文,大家会有自己的答案。


更新历史

  • 2017.05.26: 更新 docker 安装教程
  • 2017.03.18: 更新链接
  • 2017.02.25: 完成初稿(还少一些概念讲解)
  • 2017.02.01: 开始编写

写在前面

正所谓万事开头难,在我们学习新东西的时候,一开始往往是最迷茫的,因为我们甚至都不知道自己不知道些什么,于是便好像无头苍蝇般迷失方向。这里我总结了一下自己的学习经历,基于 TensorFlow 来和大家一起入门。

而对于新手来说,最难的问题恐怕就是不知道要从哪里开始了,不要方,请首先问自己以下几个问题:

  1. 我懂 Python 吗?如果不懂,那么请左转 Python 官网。如果懂,下一问
  2. 我懂机器学习吗?如果不懂,那么请右转 Practical Machine Learning w/ Python tutorial 进行复习/预习。如果懂,下一问
  3. 我懂深度学习吗?如果不懂,那么请直走 Introduction to Neural Networks part
  4. 我安装了 TensorFlow 吗?如果没有,请到官网装一下。装好了之后可以看 TensorFlow basics tutorialmodeling a deep neural network

准备工作

考虑到不少同学可能面对上面给出的茫茫多参考资料有点懵,这里还是更加详细地记录前期的理论和环境准备工作。

附本机配置:

  • MacBook Pro (Retina, 13-inch, Late 2013)
  • CPU: 2.4 GHz Intel Core i5
  • Memory: 8 GB 1600 MHz DDR3
  • GPU: Intel Iris 1536 MB

Virtualenv 安装 TensorFlow

安装的教程,建议不要过分依赖中文(因为很可能过时或者环境不匹配),官方的安装教程在这里(自备梯子)。我们主要展示在 Mac OS X 下的安装

因为我的机器没有 Nvidia 的显卡(只有一个集成显卡),所以我选择是只支持 CPU 的 TensorFlow 版本,这个版本比较好安装,速度也比较慢,但是作为入门,了解基本概念是足够了的。大家也不必说打着深度学习的幌子配一波动辄五位数的深度学习工作站,真正想学习的同学肯定会尽量利用手头上的材料,而不是买买买的(当然如果有土豪要资助我,我也是很乐意的)。

在 Mac OS X 上,除了复杂的源代码安装方式外,比较主流的方式是使用 virtualenv, 原生 pip 或 Docker,这三种方式中比较推荐 virtualenv,我们就隔离出来了一个『纯净』的环境,用于 TensorFlow,可以避免很多奇奇怪怪的问题。另外 Docker 的方式也是很常见的(公司中比较多用这个,只要折腾好镜像,使用起来非常简单),不过 Docker 目前在 Mac OS X 上不支持 GPU(反正我也是没有 GPU 的)。

这里我们选择官方推荐的 virtualenv 方式。请打开 shell 进行一波输出(注,为了避免出现意外,从这里建议全程科学上网):

# 安装 pip,注:pip 是 python 的包管理器
sudo easy_install pip
# 安装/升级 virtualenv
sudo pip install --upgrade virtualenv
# 安装完成之后我们创建一个 virtualenv 环境,这个目录大家可以随意
# 本文中默认使用 ~/tensorflow
virtualenv --system-site-packages ~/tensorflow
# 进入这个虚拟环境
# 以后每次试验都需要先敲一下这段代码
source ~/tensorflow/bin/activate
# 如果成功,会出现
# dawang @ wdxtub in ~ [17-02-24 16:35:38]
# $ say hello
# (tensorflow) <- 主要是这个,有这个就说明是在这个环境里了
# 首先检查下 python 版本,注意是 -V 是大写
# 考虑到各类包的兼容和更新问题,我们暂时使用 python 2.7
python -V
# 下面就开始安装 tensorflow(注意随着版本更新,链接也是会更新的)
pip install --upgrade https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.0.0-py2-none-any.whl

我们需要做的就是等待其下载、编译、链接和配置完成。

Docker 安装 Tensorflow

如果使用 Docker 的话,使用什么操作系统就无所谓了,大家可以根据自己的需要下载:

Docker 的安装比较简单,直接点开安装包跟着操作即可。安装完成后,我们就可以启动 tensorflow 的镜像了。启动镜像的命令格式为 docker run -it -p hostPort:containerPort TensorFlowImage(注:目前 Mac 上的 Docker 暂时不支持 GPU)

其中:

  • -p 主机端口:容器端口 是可选的配置,如果你想通过命令行运行 Tensorflow,那么可以忽略。如果你想利用 Jupyter notebook 来运行 Tensorflow,那么使用 8888:8888。如果你想在容器内运行 TensorBoard,那么再用一个 -p 6006:6006
  • TensorFlowImage(Tensorflow 镜像) 是必备的,一些可选的有:
    • gcr.io/tensorflow/tensorflow: TensorFlow 二进制镜像
    • gcr.io/tensorflow/tensorflow:latest-devel: Tensorflow 二进制镜像
  • dockerhub 也提供该镜像,使用 docker run -it -p 8888:8888 tensorflow/tensorflow 即可,如果是要使用 gpu 版本,则是 nvidia-docker run -it -p 8888:8888 tensorflow/tensorflow:latest-gpu
  • 国内由于众所周知的原因,建议先把镜像 pull 下来,比如 docker pull registry.cn-hangzhou.aliyuncs.com/denverdino/tensorflow,启动的话则是 docker run -it registry.cn-hangzhou.aliyuncs.com/denverdino/tensorflow /bin/bash(选项 -i 用于保持 STDIN 在当前的窗口上,-t 用于分配一个 pesudo-tty,两个选项使得当前的窗口可以像一个 linux 的 bash 一样运行。第一个参数指定了使用的镜像,第二个参数指定了启动这个镜像后启用的命令)

启动之后就可以用 Kitematic 看到启动的 container,并使用 GUI 管理了(当然,也可以继续用命令行)。然后我们就可以继续走下面的验证流程了。

验证 TensorFlow

首先要强调的是,每次打开一个新 shell 时,都需要激活我们之前配置好的 virtualenv,命令为:

# 激活环境
source ~/tensorflow/bin/activate
# 退出环境
deactivate

激活之后我们输入 python 进入交互编程模式,然后输入下面的代码:

>>> import tensorflow as tf
>>> hello = tf.constant('Hello, TensorFlow. Visit wdxtub.com!')
>>> sess = tf.Session()
>>> print(sess.run(hello))
# 如果出现下面这句,则说明安装成功
Hello, TensorFlow. Visit wdxtub.com!

那么问题来了,我们刚才的例子做了啥呢?答案其实很简单,啥都没做,就是用 tensorflow 输出了一个字符串常量。不过接下来,我们就要搞点事情了(先搞事情,再讲原理)。

挂载自定 notebook

日常使用的时候,我们常常需要对一些网上找来的教程进行学习,我们可以用下面这条命令把本地的目录挂载到 docker 中,这样就可以顺利访问并学习了。

docker run --name tf_with_notebook -p 8888:8888 -p 6006:6006 -v /Users/dawang/Documents/dlnotebook:/notebooks/dl tensorflow/tensorflow

一个简单的例子

大家对解方程应该都很熟悉,比如 $y=ax+b$,我们可以根据任意三个变量(比如说 $a,x,b$)求出最后一个变量的值。至于这个公式的图像是一条直线,于是我们可以认为,$y$ 和 $x$ 之间是有线性关系的,我们只要知道 $a,b$ 就可以进行 $x,y$ 的互相推导。

那么问题来了,如果我们不知道 $a,b$,只有 $x,y$ 的值怎么办呢?比如说现在我只知道 $x=2,y=7$,那么 $a,b$ 的可能值是 $a=3,b=1$,也可能是 $a=2,b=3$,还可能是 $a=1,b=5$(还有千万种可能)。如果这个时候又来了一个 $x=4$,我们怎么估算 $y$ 呢?这个时候我们说 $y$ 是 13($3x+1, x=4$),11($2x+3, x=4$)或 9($x+5, x=4$)都是可以的。

于是我们需要更多的证据,如果这个时候我们得知 $x=4$ 时,$y=11$,那么是不是就说明,$a=2,b=3$ 了呢?遗憾的是,仍然不能这么说,我们只能认为,在已知条件下,更可能是 $a=2,b=3$。至于为什么只能说『更可能』,因为我们通过数据来倒推系数(也就是 $a,b$)是一个盲人摸象的过程,我们连真实的值都不知道是什么,又如何能确定呢?

不过不要泄气,我们只多知道了一组新的数据,就可以从千千万万种不同 $a,b$ 值中找到更有可能的值,这就是进步,随着数据的增多,即使我们不能找到真实值,但只要我们估计的值跟真实值足够接近,很多场景下我们也够用了。

别急,还没完,我们回过头来看看为什么我们知道了 $x=4,y=11$ 之后就可以提高 $a=2,b=3$ 的可能性呢?因为一个事情!就是当 $a=2,b=3$ 时,我们已知的两组数据都可以完美『对』上!而对于 $a=3,b=1$ 来说,虽然可以符合第一组数据 $x=2,y=7$,但对于 $x=4$ 时计算出来的 $y=13$,就和实际值 $y=11$ 有偏差了。那我们当然要选择一个偏差小的 $a,b$ 组合啦。

这里注意,我们用来判断一组 $a,b$ 是否更符合的条件便是所谓的损失函数,我们寻找最佳 $a,b$ 的过程实际上就是让损失函数最小,也就是我们常说的优化过程。

总结一下,机器学习的过程实际上是通过我们能够观察到的数据,通过一定规则(让损失函数最小)找到最可能的对应方式的过程(即找到 $y=ax+b$ 中的 $a,b$)。虽然机器学习的方法很多,但万变不离其宗,基本都是这个套路。

注:这种应用方式通常称为『拟合』,也就是在给定数据源之后,找到一个最可能产生这样数据的模型(对应到上面的例子就是求 $a,b$)。另一种常见的应用称为『分类』,也就是给定数据之后,把它划分到某一类,比方说物体检测、人脸识别类似的应用,就是一个『分类』的过程。不过这里就不展开了。

接入神经网络

说了这么多,终于可以进入正题了。神经网络是一种的数学模型,是受到生物学影响而逐步从生物学引申过来的概念,经过不断的发展,计算机领域的神经网络和生物学领域的神经网络已经不太能简单用神经网络这个词来概括了。我们不必过多纠结于名字,只要知道原理从高层概念上来看有点类似即可。

计算机的每个神经元都非常简单(这和生物学是很像的),这些神经元的相互连接会根据外界的信息改变,非常适合对输入和输出间复杂的关系进行建模。

和人类的学习过程一样,神经网络需要很多数据,这些数据会作用于不同的神经元,最终固化成某种『模式』,对应到具体问题的话,就是找到最可能的系数组合(即前面的 $a,b$,但实际上要复杂得多)。

TensorFlow 就是由谷歌开发的神经网络计算框架,我们可以利用它来轻松完成许多计算,就不用重复做底层的工作了。一个简单的示意图为:

图中有不少概念,这个我们会专门在习题课中进行介绍,这里只要有一个简单的感觉即可。结合前面『一个简单的例子』中的总结,我们可以归纳出如下几个步骤

1) 准备数据:获得有标签的样本数据(带标签的训练数据称为有监督学习);

2) 设置模型:先构建好需要使用的训练模型,可供选择的机器学习方法其实也挺多的,换而言之就是一堆数学函数的集合;

3) 损失函数和优化方式:衡量模型计算结果和真实标签值的差距;

4) 真实训练运算:训练之前构造好的模型,让程序通过循环训练和学习,获得最终我们需要的结果“参数”;

5) 验证结果:采用之前模型没有训练过的测试集数据,去验证模型的准确率。

这里我们把前四步过一遍,第五步同样会在习题课中进行介绍。前面我们已经安装好了 TensorFlow,这里我们直接上代码,请大家注意注释的讲解:

# -*- coding:utf-8 -*-
import tensorflow as tf
import numpy as np
# 添加层
def add_layer(inputs, in_size, out_size, activation_function=None):
# add one more layer and return the output of this layer
# 区别:大框架,定义层 layer,里面有 小部件
with tf.name_scope('layer'):
# 区别:小部件
with tf.name_scope('weights'):
Weights = tf.Variable(tf.random_normal([in_size, out_size]), name='W')
with tf.name_scope('biases'):
biases = tf.Variable(tf.zeros([1, out_size]) + 0.1, name='b')
with tf.name_scope('Wx_plus_b'):
Wx_plus_b = tf.add(tf.matmul(inputs, Weights), biases)
if activation_function is None:
outputs = Wx_plus_b
else:
outputs = activation_function(Wx_plus_b, )
return outputs
# 1.训练的数据
# Make up some real data
x_data = np.linspace(-1,1,300)[:, np.newaxis]
noise = np.random.normal(0, 0.05, x_data.shape)
y_data = np.square(x_data) - 0.5 + noise
# 2.定义节点准备接收数据
# define placeholder for inputs to network
# 区别:大框架,里面有 inputs x,y
with tf.name_scope('inputs'):
xs = tf.placeholder(tf.float32, [None, 1], name='x_input')
ys = tf.placeholder(tf.float32, [None, 1], name='y_input')
# 3.定义神经层:隐藏层和预测层
# add hidden layer 输入值是 xs,在隐藏层有 10 个神经元
l1 = add_layer(xs, 1, 10, activation_function=tf.nn.relu)
# add output layer 输入值是隐藏层 l1,在预测层输出 1 个结果
prediction = add_layer(l1, 10, 1, activation_function=None)
# 4.定义 loss 表达式
# the error between prediciton and real data
with tf.name_scope('loss'):
loss = tf.reduce_mean(tf.reduce_sum(tf.square(ys - prediction),
reduction_indices=[1]))
# 5.选择 optimizer 使 loss 达到最小
# 这一行定义了用什么方式去减少 loss,学习率是 0.1
with tf.name_scope('train'):
train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)
# important step 对所有变量进行初始化
init = tf.global_variables_initializer()
sess = tf.Session()
# 上面定义的都没有运算,直到 sess.run 才会开始运算
# 区别:sess.graph 把所有框架加载到一个文件中放到文件夹"logs/"里
# 接着打开terminal,进入你存放的文件夹地址上一层,运行命令 tensorboard --logdir='logs/'
# 会返回一个地址,然后用浏览器打开这个地址,在 graph 标签栏下打开
writer = tf.summary.FileWriter("logs/", sess.graph)
# important step
sess.run(init)
# 迭代 1000 次学习,sess.run optimizer
for i in range(1000):
# training train_step 和 loss 都是由 placeholder 定义的运算,所以这里要用 feed 传入参数
sess.run(train_step, feed_dict={xs: x_data, ys: y_data})
if i % 50 == 0:
# to see the step improvement
print(i, sess.run(loss, feed_dict={xs: x_data, ys: y_data}))

// TODO
解释代码

我们运行一下,就可以得到如下结果(每次可能都不一样)

(0, 0.83473957)
(50, 0.022982161)
(100, 0.010143606)
(150, 0.0062472103)
(200, 0.0054993271)
(250, 0.0051222593)
(300, 0.0048237662)
(350, 0.0045826179)
(400, 0.0043984707)
(450, 0.0042431904)
(500, 0.0041155368)
(550, 0.0040093502)
(600, 0.0039139241)
(650, 0.0038403464)
(700, 0.0037833541)
(750, 0.0037375824)
(800, 0.0036966042)
(850, 0.0036602831)
(900, 0.0036307489)
(950, 0.0035978416)

这说明经过 1000 次的迭代之后,我们的误差已经从最开始的 0.834 降到了 0.003。与此同时,我们也生成了对应的网络结构,可以通过运行 tensorboard --logdir='logs/' 后访问 localhost:6006 进行查看。我们就可以看到刚才这段代码所对应的结构,是不是跟之前的 gif 一样?

参考资料

镜像

捧个钱场?