从牛顿到爱因斯坦——2016 GTC大会手记(1)

2016年是AI元年。AlphaGo战胜李世石的那一刻,这个事实就已经是板上钉钉了。人类在自己发明的最优美最复杂的游戏中输给了机器。那是一个巅峰时刻,它并不是一个时代的终结,而是一个崭新时代的开始。我们为AlphaGo欢呼时,也许只是远远看到AI向我们走来,而今天在美国加州圣何塞举行的GTC大会上,我们清晰得听到了AI的敲门声。

NVIDIA公司CEO黄仁勋在GTC大会上宣布了多项产品,而所有这一切却仅仅指向一件事,那就是深度学习!

谈论深度学习之前,我们需要澄清几个概念——“AI 人工智能”、“Neural Network 神经网络”和“Deep Learning 深度学习”。

AI

AI是人类的一个梦想:我们想让机器变得跟我们同样聪明或者超过我们。而这个梦想,目前只完成了一半。美国心理学家、诺贝尔经济学奖得主丹尼尔·卡尼曼有一个“系统一”和“系统二”的理论。人类的“系统一”便是宽泛模糊的心理能力,或者说自主意识;比如,对面的人是不是生气了,姑娘真好看,回锅肉很好吃,某部电影是否是烂片等等;当然,这也包括围棋的“棋感”。而“系统二”是有客观结果的事务,比如数学运算,信息处理。人脑需要一段时间才能完成或者甚至无法完成属于“系统二”的任务。对于计算机则相反,“系统二”易如反掌,而“系统一”则是登天的难题。

直觉和理性

神经网络

神经网络就是我们为了解决机器的“系统一”难题而发展出来的计算机系统。这个系统采用规模很小但是数量庞大的计算单元来模拟人类的神经元;同时,这些数量庞大的神经元通过网络或者某种硬件连接起来模拟神经元之间可以通过神经突触传递信息的能力。每个计算单元可以接受一些输入,恰似神经元可以接受某种刺激;这些计算单元也可以给出一些输出并将这个输出传递给其他神经元作为输入,这正是对神经元之间的通信进行模拟。而且,这些神经元构成的网络是分层的,底层的输出恰是上层的输入。

神经元构成的层级结构

从宏观上看,整个神经网络会接受某些输入并且分析计算给出一些结果。设想一下人类观赏一朵花的过程:眼睛看到花的颜色和形状,鼻子闻到气味,这些信息经过大脑过滤并进一步处理,接着,根据已经存储在脑内的某些特征进行比较,人们可以知道这朵花是月季还是玫瑰。神经网络对信息的处理过程与此类似,它具有抽取输入、存储结果、分析和给出反馈的所有能力。使用”神经网络“,”系统一“的难题不再不可捉摸,而变成了一种机器可以学习的东西。

深度学习

深度学习是一种用神经网络解决计算问题的方法。之所以称其为”深度“,正是因为这个网络的层数众多。它与众不同的原因在于,这种算法的自诞生以来,其目标就是给所有其他算法画上句号。深度学习成功了。在很多领域,从此以后,只有一个算法,而这个算法能解决所有问题。它天然的具有暴力美学倾向。计算从此不再依赖于精巧的设计,而依赖于超大量的数据和强大的计算能力。GPU正是让机器长出大脑并且变得筋肉暴突的妖怪。

神经网络

GPU

每个GPU都有数量庞大的小型计算单元,而这些计算单元可以模拟神经元。

gpu-architecture

上图是NVIDIA的PASCAL核心GPU的结构示意图。图中密密麻麻的绿色小方格代表的就是可以模拟神经元的小型计算单元。GPU技术经历了Fixed pipeline技术之后,几乎所有的GPU厂商都走向了更加通用的Generic Shader技术。Generic Shader技术使用众多结构相同的微小的ALU(逻辑和计算单元)来对图形数据进行处理。通常,图形图像数据可以被分割成数据块,而且这些数据块之间不会有相互依赖的关系。借助这个特性,GPU可以在所有ALU中装载相同的处理程序对大量图形图像数据进行并行处理。这种并行处理最直观的体现就是下面这个视频。视频中GPU用10毫秒绘制了非常漂亮的蒙娜丽莎。

NVIDIA率先发现了这种通用的处理器结构可以用于除了图形计算以外的其他领域。于是,CUDA作为一门专门支持并行计算的计算机语言而诞生。

nvidia_adrian

更进一步,CUDA针对众多ALU处理器核心进行编程的能力被迅速用于模拟神经网络。为了完成多层神经网络结构,计算机程序会让GPU在按照顺序在装载不同的CUDA kernel(内核)。而每一次装载后运行的结果会被缓存在GPU的共享存储器中,这些结果通常会作为下一层神经元的输入来使用。采用这种方法不断的重新加载CUDA内核,计算机就可以使用GPU对生物的神经网络进行模拟。

机器的神经元

本届GTC发布的重量级硬件产品包含了TESLA P100NVIDIA DGX-1NVLINK,它们的目标也只有一个,就是为运行深度学习程序的计算机提供更多更快的神经元和神经突触。

NVIDIA DGX-1

DGX-1是专门用来运行深度学习的计算机;与传统计算机体系结构相比,使用它训练AlexNet的时间从原来的250小时缩短到2小时。

战胜李世石的分布式AlphaGo实际上由1,202块CPU和176块GPU组成。这些机器的神经元在运行期间对电能的消耗巨大,自身发热更是惊人。而NVIDIA此次在GTC发布的DGX-1自身就集成了所有这些计算能力。

DGX1Parts_575px

并且以低廉的价格推出市场。

DGX1Price

而在移动设备上,NVIDIA此前推出的Tegra TX1处理器和本次针对汽车市场推出的DRIVE PX2同样也具备运行神经网络程序的能力。

drivepx2

机器视觉

深度学习正在蚕食一切传统算法,包括机器视觉算法。机器视觉的目标就是要让机器具有人类的视觉感知能力。当人们看到第一朵玫瑰花开始,就有人想弄清楚人类视觉的原理。为了探索这个问题,伟大的牛顿甚至用自己的身体实验。他曾经用缝衣针刺穿面部深入眼球下方刺激以观察变化!

牛顿的手稿

从牛顿到爱因斯坦

与人类探索自身视觉之初的冲动一样,机器视觉到目前为止发展出的理论是一套没有主题的自助餐。没有统一的理论,仅仅有针对某些问题有某些方法的教条。深度学习则抛弃传统完全开辟了另一条道路,特别是针对模式识别的问题单独来看的话,这条全新的方法效果尤为显著。

深度学习在ImageNet测试上取得的成绩

上图中的绿色标记为深度学习算法取得的成绩,而蓝色标记是传统算法取得的成绩。可以看出,深度学习算法在该领域取得了极大成功,秒杀一切传统算法。

未来的趋势就是如此,伴随着GPU这样的技术变的更小更快更省电,机器视觉的教条也许会一个一个的土崩瓦解。

无人机用机载深度学习设备处理地面图像

上图显示的是本届GTC的一个讨论主题:无人机利用机载深度学习设备对地面图像进行处理。

从以程序为中心转变为以数据为中心,这是计算模型的根本改变。这是从牛顿到爱因斯坦的变革,一切都变了!

huanggtc2016

Debian Jessie + GCC 4.8 + NVIDIA CUDA 6.5

虽然Debian Jessie的官方库已经提供nvidia-cuda-toolkit这个包。但是,作为为东家赚钱的工具,开源社区很难得到东家的真正支持。目前这个包的版本是6.0,而Nvidia官方提供的CUDA版本已经到了7.0。另外一方面,Nvidia的Tegra K1芯片的开发板Jetson TK-1上仍然在运行6.5版的CUDA。如果你也和我一样希望在移动平台上做并行计算,那么CUDA 6.5是在百般纠结之后的唯一正确选择。

Debian Jessie已经将默认的GCC版本升级到gcc-4.9;而CUDA 6.5的编译器nvcc却只能与gcc-4.8协同工作。所以,又遇到比上一个状况更纠结的问题。

这篇文章就是记录下如何解决这些问题的方法。

第一,从NVIDIA官方下载.run的自解压文件安装CUDA 6.5。下载页面在这里:

https://developer.nvidia.com/cuda-downloads-geforce-gtx9xx

,或者直接使用下面的命令下载

# wget http://developer.download.nvidia.com/compute/cuda/6_5/rel/installers/cuda_6.5.19_linux_64.run -O /tmp/cuda_6.5.19_linux_64.run

下载完成后,运行安装程序:

# chmod +x /tmp/cuda_6.5.19_linux_64.run
# /tmp/cuda_6.5.19_linux_64.run

这个过程中安装程序会提醒系统的gcc版本不能与当前CUDA工具链一起工作,并且中止安装过程。

You are attempting to install on an unsupported configuration. Do you wish to continue? ((y)es/(n)o) [ default is no ]: 

此时只能回答“no”并且退出安装。只有检查安装LOG才能发现NVIDIA的小秘密!那就是,

使用这个参数重新运行安装程序

# /tmp/cuda_6.5.19_linux_64.run --override

安装程序还会询问安装位置,为了避免和debian的包管理冲突,我选择了/opt/nvidia/cuda-6.5。同时,安装程序还会将CUDA的样例代码安装到${HOME}

第二,在Debian Jessie上安装gcc-4.8g++-4.8并在每次编译CUDA代码是将其路径指定给编译程序。步骤如下:

# sudo apt-get install gcc-4.8 g++-4.8
# cd ${HOME}/NVIDIA_CUDA-6.5_Samples/
# GCC=g++-4.8 make

用下面的命令运行最简单的样例代码确认结果。

# ${HOME}/NVIDIA_CUDA-6.5_Samples/bin/x86_64/linux/release/simpleTexture

在OpenCV中使用视频

目前Computer Vision领域最前沿的技术都集中在视频分析上。理所当然得,OpenCV也支持USB摄像头作为一个重要的输入类别。此前,OpenCV 1.1的API就提供了cvCaptureFromCAM函数从摄像头取得数据。OpenCV 2.0开始视频输入被包装成了VideoCapture这个类。而且,使用上也更加简便。虽然看上去在OpenCV中使用从摄像头作为输入并不困难,但是因为缺乏广泛的厂商支持,这件事情在Linux操作系统上却常常让人担心。这篇文章的主旨还是将我目前的配置记录下来,以便应付我那不怎么可靠的记忆力。

硬件

虽然Linux的世界Microsoft永远不会懂,但是Linux的开发者却一直在试图弄懂Microsoft的硬件,而且结果相当不错。Microsoft的硬件LifeCam几乎可以在Debian Jessie上面完美运行!这是我使用的LifeCam Cimema(官网盗图):

Microsoft LifeCam Cinema

v4l2驱动

V4L是Linux内核为各种视频输入设备提供的驱动框架。为了使用USB接口的视频设备,在系统中还需要其他内核模块支持这些硬件。以下是必要的内核模块列表:

$ lsmod | grep v4l
v4l2_common            12995  1 videobuf2_core
videodev              126451  4 uvcvideo,v4l2_common,videobuf2_core
i2c_core               46012  5 drm,i2c_i801,nvidia,v4l2_common,videodev

除此以外,用户空间还需要像gstreamer这样的视频处理框架来支持视频的编解码和显示。如果一一安装这些软件组件,那将会是一个非常痛苦的过程。所以,为了方便起见,直接安装处于这个软件栈的最顶端的应用程序将会达到事半功倍的效果。安装下面的程序:

$ sudo apt-get install v4l2ucp guvcview

安装结束后,分层确认安装结果。使用v4l2ctrl确认视频设备的配置。

$ v4l2ctrl -s /tmp/result

这个程序会探测系统中的/dev/video0并且将其配置保存到/tmp/result。在我的LifeCam上的确认结果如下:

9963776:                     Brightness:133
9963777:                       Contrast:5
9963778:                     Saturation:83
9963788:White Balance Temperature, Auto:1
9963800:           Power Line Frequency:2
9963802:      White Balance Temperature:4500
9963803:                      Sharpness:25
9963804:         Backlight Compensation:0
10094849:                 Exposure, Auto:3
10094850:            Exposure (Absolute):156
10094856:                 Pan (Absolute):0
10094857:                Tilt (Absolute):0
10094858:               Focus (absolute):4
10094860:                    Focus, Auto:1
10094861:                 Zoom, Absolute:0

然后,使用guvcview这个应用程序来测试视频录像。

guvcview_video_capture

使用OpenCV抓取视频

#include <opencv2/opencv.hpp>

using namespace cv;

int main(int argc, char** argv)
{
  VideoCapture cap(0); // open the default camera
  if (!cap.isOpened()) // check if we succeeded
    return -1;

  Mat edges;
  namedWindow("Edges", 1);

  for (;;) {
    Mat frame;
    cap >> frame; // get a new frame from camera
    cvtColor(frame, edges, CV_BGR2GRAY);
    GaussianBlur(edges, edges, Size(7,7), 1.5, 1.5);
    Canny(edges, edges, 0, 30, 3);
    imshow("Edges", edges);
    if (waitKey(30) >= 0) break;
  }

  // the camera will be deinitialized automatically in VideoCapture
  // destructor
  return 0;
}

这段程序可以从摄像头抓取视频,并且对视频中的帧进行边缘探测。主循环使用了Canny边缘探测法。下面是运行时的截图。

OpenCv_VideoCapture_Example