打开网易新闻 查看更多图片

在这个星球上,只要是有男生存在的地方,“汽车”二字就是一个可以随时创造聊天、喷涌论题灵感的神奇源头。虽然现在自动驾驶已成为大家口中津津乐道的话题,但在笔者小时候,我们更关注的还是汽车实用性方面,比如买汽车哪种更省油以及该买自动还是非自动挡。

当笔者还在读大学时,时光就经常在和朋友们讨论奔驰 AMG 马力如何强劲,路虎更酷还是卡宴更帅这些话题中度过。那时对我们而言,自动驾驶依然仅存在于科幻电影中,这样的汽车出现在现实里宛如天方夜谭。然而区区数年后,“自动驾驶” 便随同 “大数据”、“机器学习”、“人工智能” (以下简称 AI) 这几个概念一起火遍全球,成为技术领域中最热门的话题。

图一、大数据、机器学习和人工智能已然成为自动驾驶的基础。

图片来自 https://m.sohu.com/n/489662073/

相信多数读者对大数据、机器学习、AI 和自动驾驶这些概念都不陌生。但这几个看似风马牛不相及的名词之间又是如何摩擦出火花,并迅速对整个汽车行业带来巨大变化的呢?本文便为大家描绘出自动驾驶背后的 AI 算法轮廓,并且提供核心而不乏趣味的 MATLAB 代码,供有兴趣的读者参考。

兵马未动粮草先行,在了解自动驾驶算法原理之前,我们先来看看 AI 和自动驾驶的历史,因为这两个理论的发展看似迅速,实则远没有想象中那么一帆风顺。

一、AI 与自动驾驶简史

尽管在国内, AI 在 2014 年以后才逐渐被受到重视,然而放眼全球,AI 的历史已经存在至少 70 年了,并且在这 70 年中经历过至少三次高峰期 (1974 年之前、上世纪八十年代、2011 年至今) 和两次低谷期 (低谷的出现大概是由于四个原因:研究经费不足、计算能力不足、传统 AI 方法的低效率和伦理学上的问题,详细情况请参考 [1])。

和其他理工科分支一样, AI 的横空出世自然需要有数学理论基础的支撑,而这数学理论主要有二。其一是维纳 (Norbert Wiener) 的控制论,用以描述像生命一样的反馈机制 (例如一个机器人被挨了一拳跌倒在地,就需要爬起来作为反馈) ;其二是图灵 (Alan Turing) 的图灵测试,用以判断一个机器在执行某项任务的时候 (比如下棋) ,是否有同人类一样的行为举止。微信登录界面的智能验证就是图灵测试的一个例子。

尽管 AI 理论是上世纪四十年代才逐渐破壳而出的,然而这并不影响人们对自动驾驶的憧憬——早在上世纪二三十年代,美国就出现了无人驾驶概念车。不过这种无人汽车需要无线电波操控,原理和遥控车并无区别,和现代意义上的自动驾驶还有很大差距。 当 AI 理论形成后,真正意义上的自动驾驶汽车才被开发出来, 作为自动驾驶汽车的“见闻色”,AI 为传统汽车的发展注入了全新可能。例如上世纪五六十年代的通用 Firebird 系列,在当时炙手可热的信息论和控制论的指引下,拥有了能够自动识别高速公路变道线,并指挥加减速和变道等简单动作的“大脑” (本质上就是自适应系统)。

打开网易新闻 查看更多图片

图二、通用公司的概念车 Firebird 系列成型于上世纪五十年代,它具有一套电子操纵系统,能在高速路上做到无人驾驶

不过由于当时各国政府对自动驾驶都存在不少担忧,前期的研究都只是小打小闹,并没有立即规模化或投入商业生产。直到 2010 年大数据时代的来临,各类上世纪八九十年代机器学习 (尤以深度学习为主) 算法突然得到了重生,同时也让 AI 领域焕然一新。AI 领域的成熟使得人们重新燃起了对自动驾驶的研究热情 —— 近十年来,各种基于 AI的自动驾驶公司如雨后春笋般纷纷涌现出来;“哪一年才是自动驾驶元年”这类话题的热度从没被降温过。

2014 年,国际汽车工程师学会 (SAE) 正式把自动驾驶划分为 L0-L5 一共六个等级。我们一般认为 L3 甚至 L4 级以上的自动驾驶才算真自动驾驶汽车,不过截至本月 (2021 年 9 月) ,除了日本本田在本土限量销售之外,还没有任何一款 L3 或以上的汽车被公开销售。

图三、自动驾驶等级划分。图片来自 SAE 中文网站

那么如今的AI 和自动驾驶能摩擦出什么样的火花呢?接下来登场的便是两个 AI 领域最前沿的例子—— 图像识别和强化学习。

二、图像识别——上帝说要有眼睛

要让一辆汽车自己动起来,首先需要给它一双眼睛。这并不困难,因为我们只需要给汽车安装外置摄像头就行了,甚至经过一系列信号强化之后,可以使得汽车的视力比飞行员还好。真正困难之处在于,这双眼睛必须要拥有人类的智慧,知道如何识别障碍物、交通标志、信号灯等等。于是,我们必须教汽车识别不同的东西,这个就是计算机视觉 (computer vision) 的主要研究课题之一,而计算机视觉正是机器学习领域中非常炙手可热的研究方向。

2010 年之前,在计算机视觉领域,物体识别的算法几乎都是通过 a) 先提取"特征张量" (通过各式各样的线性代数运算) ,b) 再把"特征张量"分成不同类别的两步走方针。不过到了 2012 年,一种叫做卷积神经网络 (CNN) 的算法突然脱颖而出,比其他物体识别算法准确率高出 10 个百分点。此后,神经网络在越来越多的领域大展身手,深度学习便逐渐成为一个香饽饽——许多传统算法立刻变得索然无味。

图四、2012 年可谓深度学习时来运转的一年

限于篇幅,关于深度学习的历史、种类和原理,这里就不做深入讨论了,有兴趣的读者可以参考 [3] (机器学习红宝书) 或 [4] (一篇综述,非常容易入手) 。我们只需要知道,所有的神经网络 (CNN、RNN、GNN 等) 无论层数有多少,名字多么花里胡哨,其任务都只有一个——即利用已知数据 x 和 y 对关联它们之间的函数 y = f(x) 估算出来 (这里 f 定义域是 m 维,值域是 n 维) 。

接下来我们以自动驾驶为例,看看如何在 MATLAB 环境下从头开始搭建一个简单的 CNN,让车载摄像头拥有基本的物体识别能力!

第一步:数据准备

要教摄像头识别物体,首先要告诉他们什么是"正确的识别例子",这些正确的例子是由人提前整理好的,又名训练数据集 (Training dataset) 。例如下图中:

打开网易新闻 查看更多图片

图五、训练集

黄框把图片中的汽车给套出来了。不过由于汽车的品牌、颜色、大小千差万别,而且就算同一款汽车,也难免横看成岭侧成峰,因此训练数据集 (也就是整理好的交通图片和圈出汽车的黄框) 数量不能少。

当摄像头读取了全部训练集以后,我们需要对它进行期末考试,期末考试的试卷叫做测试数据集 (test dataset) 。如果我们想实时监测摄像头的训练效果,我们还可以给它布置家庭作业来判断摄像头每次训练的效果,这种家庭作业叫做校验数据集 (validation dataset) 。测试集被用于评估摄像头的预测准确性,我们待会会继续提到它的用法。

笔者用下面的 MATLAB 代码下载所有数据集 (一共 295 张照片) ,并通过 6:1:3 的比例划分训练集、校验集和测试集。具体划分方法在此略去,有兴趣的读者请查看 github 文件 [5].

1. doTraining = false;

2. if ~doTraining&& ~exist('ssdResNet50VehicleExample_20a.mat','file')

3. disp('Downloading pretrained detector (44 MB)...');

4. pretrainedURL ='https://www.mathworks.com/supportfiles/vision/data/ssdResNet50VehicleExample_20a.mat';

5. websave('ssdResNet50VehicleExample_20a.mat',pretrainedURL);

6. End

7.
unzip vehicleDatasetImages.zipdata = load('vehicleDatasetGroundTruth.mat');vehicleDataset = data.vehicleDataset;

第二步:搭建神经网络

我们之前提到过,所有神经网络都不过是通过已知数据对某个函数进行近似,并没有涉及任何抽象概念。事实上在 MATLAB 里面,我们可以通过以下方式搭建一个简单实用的 CNN。这个CNN 一共只有 1+3+2=6 层:

8. % 搭建输入层。其维数和图片的大小相同,

9. % 是 32(横轴) * 32(纵轴) * 3(RGB 三种颜色)inputLayer = imageInputLayer([32 32 3], 'Name','input');

10.
% 搭建中间层(隐藏层)。一般分为三个步骤:

11. % 卷积: 可以理解为对图片加滤镜,滤镜种类由所谓的"核函数确定";

12. % 池化:把特征张量进行简单的降维处理,减轻摄像头的学业负担;

13. % 激活(一般不计入层数计算):对特征张量进行一定程度的归一化,这也是神经网络中非线性的主要来源;

14. filterSize = [33];

15. numFilters =32;

16. middleLayers =[

17. convolution2dLayer(filterSize,numFilters, 'Padding', 1,'Name','conv1')

18. reluLayer('Name','relu1')

19. convolution2dLayer(filterSize,numFilters, 'Padding', 1, 'Name','conv2')

20. reluLayer('Name','relu2')

21. maxPooling2dLayer(3,'Stride',2, 'Name','mp')];
% 搭建输出层

22. finalLayers = [

23. fullyConnectedLayer(64, 'Name','fc1')

24. reluLayer('Name', 'relu3')

25. fullyConnectedLayer(width(vehicleDataset), 'Name','fc2')

26. softmaxLayer('Name','softmax')

27. classificationLayer('Name','classification')

28. ];

第三步:训练神经网络

搭建好神经网络以后,我们就可以开始训练它们了。简单起见,笔者只使用电脑自带的 CPU 对摄像头进行训练。

29. layers = [

30. inputLayer

31. middleLayers

32. finalLayers

33. ];

34.
% 安装了 GPU 并行计算驱动的读者可以把 'MiniBatchSize' 设为 1

35. options =trainingOptions('sgdm',...

36. 'MaxEpochs',10,...

37. 'MiniBatchSize',1,...

38. 'InitialLearnRate',1e-3,...

39. 'CheckpointPath',tempdir,...

40. 'ValidationData',validationData);

41.
[detector, info] = trainFastRCNNObjectDetector(trainingData,layers,options);

随后 MATLAB 会把学习进度显示出来,从下面结果可见,在只用 CPU 的情况下,训练上面这个简单的 6 层 CNN 就需要十分钟左右。如果使用 GPU 进行并行训练,效率会成倍提升。

图六、训练结果

第四步:期末考试

训练完成以后,我们来看看用这个 11 层 CNN 识别车辆的效果吧:

图七、11 层 CNN 车辆识别效果。

左:原图;右:识别效果,黄色小数为置信值,置信值越靠近 1 表示识别效果越好

这识别效果似乎有点差强人意 —— 它只能完整地识别出了远方一辆 SUV,而却对正前方那么大一辆 MPV 置之不理!问题出在何处呢?事实上有两个原因:一是我们建立的 CNN 太简单,只有 6 层,导致模型参数个数太少;二是训练数据集太少。

有兴趣的读者自然可以发挥自己的想象力,在上文中的 CNN 中多加一些结构,再重新训练看看。不过此处为了简单起见,我们直接来看一个已经提前训练好的, 50 层的 CNN,著名的 ResNet50 的表现吧 (本质上就是一个含有两千三百万参数的非线性函数,完整代码见 [6]) :

图八、50 层 ResNet50 车辆识别效果。

左:原图;右:识别效果,黄色小数为置信值,置信值越靠近 1 表示识别效果越好

ResNet 的中文名叫残差网络,它在 2015 年被发布出来时因其解决了所谓的零梯度问题 (vanishing gradient problem,因为神经网络或多或少都会采用梯度下降算法,零梯度使得梯度下降算法失效) 而获得了大量的关注,因而迅速成为计算机视觉领域的一个标准模型,此后的各种神经网络比如 SSD,RetinaNet,YOLO 等都是基于残差网络的神经网络模型 [10]。此外,车辆识别只是作为一双合格眼睛的众多条件之一。我们还需要教它识别车道线、信号灯等等,这些也大都通过 CNN 来完成,限于篇幅,有兴趣的读者可以参考 [7]。

第三章、强化学习——从哪跌倒从哪爬起

注:本章代码全部囊括在 [9] 中。

有了类比人类智慧的眼睛以后,我们的汽车开始骄傲了:“只要有了强大的双眼,泰山天池任我横行!”它兴奋地冲上训练场,殊不知还不到一百米,就追了三次尾。它发现一个严重问题——尽管自己能识别所有物体,但四肢却狂野如初,不受慧眼管束。

为了管住狂野的四肢,我们还需要给自动汽车安装大脑,而这个大脑的核心就在于强化学习 (reinforcement learning) 机制。强化学习的目的在于,让一个机器学会自己在未知环境中不断试错,并完成指定任务,机器难免不犯错。经过前人不断总结,强化学习被划分为两个组成部分——一是策略 (英文叫 policy,当观测环境发生某些变化时,便采取特定行为。例如当汽车看到前面有障碍物时就减速,前面畅通无阻则加速至最高限速) ,二是训练算法 [8]。

为了让汽车学会如何避免追尾事件的发生,我们以 MATLAB Simulink 中的自适应巡航模块 (简称 rlACCMdl) 为例,演示强化学习如何能让汽车自动学会和前方车辆保持距离。这里采用的算法是深度学习中的演员-影评者模型 (Actor-Critic Model) 。该模型是著名的策略梯度算法 (policy gradient) 的一个特例,它把训练模块分为演员和评论者两个部分,并且两部分之间相互作用,相辅相成。它大致工作原理如下所示:

打开网易新闻 查看更多图片

图九、演员-评论者模型。演员模块的输出可以影响环境变量,评论者模块的输出可以影响演员模型中的参数。具体工作原理可以参考文献 [8] 的第十三章第五小节

我们可以直接用下面代码打开自适应巡航模块:

42.mdl = 'rlACCMdl';

43.open_system(mdl);

44.agentblk = [mdl '/RL Agent']; % 为我们的模块命名

下图是该模块的一个演示 (笔者自然免不了忙里偷闲地加一些备注) :

图十、自适应巡航模块演示。双击特定模块,可以进一步看到每个模块的细节设置,例如上图中前车加速度是一个正弦波函数,最优距离也由一个特定的数学式表达出来

接下来我们分为三步来完成这个实验。

第一步、建立初始环境

第一步当然是把训练场地搭建好:

45. % 建立观测者环境(这里监视汽车的速度和其误差)

46. observationInfo= rlNumericSpec([31],'LowerLimit',-inf*ones(3,1),'UpperLimit',inf*ones(3,1));

47. observationInfo.Name= 'observations';

48. observationInfo.Description= 'information on velocity error and ego velocity';

49. % 设置行为 —— 加速度在 [-3, 2]这个区间

50. actionInfo=rlNumericSpec([1 1],'LowerLimit',-3,'UpperLimit',2);

51. actionInfo.Name= 'acceleration';

52. % 建立强化训练环境

53. env =rlSimulinkEnv(mdl,agentblk,observationInfo,actionInfo);

54. env.ResetFcn =@(in)localResetFcn(in);% 每次训练开始时,前车的初值

55. rng(0)

第二步、分别建立演员和评论者模块

演员模块 —— 我们用四层全连接层来表示 (全连接层是最简单粗暴的神经网络连接层) 。这里演员可采取的行为是,取值在 [-2, 3] 范围的加速度。这个加速度是一个随机变量,其分布由环境参数 (前车距离、速度) 和评论者模型生成的回报值共同决定。

56. L = 48; % 神经元个数

57. actorNetwork =[

58. % 注意这里的 imageInputLayer 并非真要输入图像,而只需要输入位置、速度、加速度即可 imageInputLayer([3 1 1],'Normalization','none','Name','observation') fullyConnectedLayer(L,'Name','fc1')

59. reluLayer('Name','relu1')

60. fullyConnectedLayer(L,'Name','fc2')

61. reluLayer('Name','relu2')

62. fullyConnectedLayer(L,'Name','fc3')

63. reluLayer('Name','relu3')

64. fullyConnectedLayer(1,'Name','fc4')

65. tanhLayer('Name','tanh1') scalingLayer('Name','ActorScaling1','Scale',2.5,'Bias',-0.5)];

66. % 定义演员学习能力参数,有兴趣的读者可以将这些参数进行自由调整

67. actorOptions =rlRepresentationOptions('LearnRate',1e-4,'GradientThreshold',1,'L2RegularizationFactor',1e-4);

68. actor =rlDeterministicActorRepresentation(actorNetwork,observationInfo,actionInfo,... 'Observation',{'observation'},'Action',{'ActorScaling1'},actorOptions);

评论者模块 —— 公平起见,我们也用四层全连接层来表示:

69. statePath = [

70. % 注意这里的 imageInputLayer 并非真要输入图像,而只需要输入位置、速度、加速度即可

71. imageInputLayer([31 1],'Normalization','none','Name','observation')

72. fullyConnectedLayer(L,'Name','fc1')

73. reluLayer('Name','relu1')

74. fullyConnectedLayer(L,'Name','fc2')

75. additionLayer(2,'Name','add')

76. reluLayer('Name','relu2')

77. fullyConnectedLayer(L,'Name','fc3')

78. reluLayer('Name','relu3')

79. fullyConnectedLayer(1,'Name','fc4')];

80.
% 对演员模型产生影响,主要体现在影响其采取行为的分布函数

81. actionPath = [

82. imageInputLayer([11 1],'Normalization','none','Name','action')

83. fullyConnectedLayer(L, 'Name', 'fc5')];

84.
criticNetwork = layerGraph(statePath);

85. criticNetwork =addLayers(criticNetwork, actionPath);

86. criticNetwork =connectLayers(criticNetwork,'fc5','add/in2');

87.
criticOptions = rlRepresentationOptions('LearnRate',1e-3,'GradientThreshold',1,'L2RegularizationFactor',1e-4);

88. critic =rlQValueRepresentation(criticNetwork,observationInfo,actionInfo,... 'Observation',{'observation'},'Action',{'action'},criticOptions);

第三步:把两个模型结合起来,开始训练!

89. Ts = 0.1; % 初始时间

90. Tf = 60; % 终止时间

91. % 其他初值,例如限速、车辆速度、加速度等等

92. x0_lead = 50;v0_lead = 25; x0_ego = 10; v0_ego = 20;

93. D_default = 10;t_gap = 1.4; v_set = 30; amin_ego = -3; amax_ego = 2;

94. agentOptions =rlDDPGAgentOptions(...

95. 'SampleTime',Ts,...

96. 'TargetSmoothFactor',1e-3,...

97. 'ExperienceBufferLength',1e6,...

98. 'DiscountFactor',0.99,...

99. 'MiniBatchSize',64);

100. agentOptions.NoiseOptions.Variance= 0.6;

101. agentOptions.NoiseOptions.VarianceDecayRate = 1e-5;

102. agent = rlDDPGAgent(actor,critic,agentOptions);

103.
% 训练开始!

104. maxepisodes =350;

105. maxsteps =ceil(Tf/Ts);

106. trainingOpts =rlTrainingOptions(...

107. 'MaxEpisodes',maxepisodes,...

108. 'MaxStepsPerEpisode',maxsteps,...

109. 'Verbose',false,...

110. 'Plots','training-progress',...

111. 'StopTrainingCriteria','EpisodeReward',...

112. 'StopTrainingValue',260);

113. trainingStats = train(agent,env,trainingOpts);

下面是训练效果图 (训练到 307 步,此刻已经花费了笔者 2 小时时间) :

图十一、训练趋势图。总体而言,回报值会随着训练次数的增加而增加,直到不再变化

第四步:测试

模型训练完毕后,面对实际效果会怎么样呢?例如我们试试让前车离测试汽车 90 米,前车速度变成 10 m/s 会怎样:

  1. x0_lead = 100; v0_lead = 10;

  2. x0_ego = 10; v0_ego = 20;

  3. v_set = 50;

  4. Tf = 100;

  5. sim(mdl);

图十二、训练效果。左图是两车的速度,蓝色表示演员,红色表示前车;右图蓝线表示安全距离,黄线表示两车间距

从图十二可以看出,我们可以看到在时间小于 30s 时,由于和前车距离很近,我们的"演员"可以迅速飙车到接近 30m / s。然而此时安全距离变小,因此必须减速至和前车保持安全距离,因此在 30 s 后,两车速度、间距保持恒定。

如果我们看看咱们这位演员的加速度,我们看到当碰到一辆压速度的前车时,它内心有多么挣扎:

图十二、加速度变化。相信读者们或多或少可以理解 30 s 以后它挣扎的内心

四、总结

到现在为止,我们已经对自动驾驶及其背后相对前沿的算法,比如卷积神经网络和强化学习有了一定的认识 —— 通过卷积神经网络,我们可以给汽车一双慧眼;而强化学习算法则赋予汽车独立思考的能力,让慧眼所见得以体现在实际行动上。量变引发质变,当汽车们有了足够多的慧眼以及足够强大的思考能力以后,我们就离完全 (L5 级) 自动驾驶的时代不远了。

当然,除了卷积神经网络和强化学习以外,自动驾驶汽车还有许多重要的组成部件,例如激光雷达的应用、各种控制组件和传感器融合等等。这些组件充当着自动驾驶汽车的附加感官和另一部分大脑,在汽车视力不佳 (例如夜晚、暴雨天、大雪天) 的情况下为汽车点亮明灯,并同强化学习算法一道把汽车指引向正确的方向。不过限于篇幅,具体细节就不再赘述了,有兴趣的读者可以参考 MATLAB 官网自动驾驶工具包 [11]。

或许正在阅读这篇文章的你,就能加速 L5自动驾驶的普及。还等什么,打开手中的电脑,试一试文章中的代码吧,或者你就能从这些看似并不美观的代码中,找到许许多多有趣的结果!就算对自动驾驶兴趣不大,文章中关于卷积神经网络和强化学习的代码也能启发你找到它们的用武之地——毕竟在这个大数据时代,卷积神经网络和强化学习的应用已经广泛融入到我们生活中了。

参考文献

[1] https://sitn.hms.harvard.edu/flash/2017/history-artificial-intelligence/

[2] https://www.cs.cmu.edu/~tjochem/nhaa/navlab5_details.html

[3] Ian Goodfellow, Yoshua Bengio and Aaron Courville, Deep Learning, MIT Press 2016, http://www.deeplearningbook.org/

[4] CF Higham, DJ Higham, Deep learning: An introduction for applied mathematicians - Siam review, 2019 - SIAM

[5] https://github.com/yxgyylj/self_driving/blob/main/src/generate_data.m

[6] https://github.com/yxgyylj/self_driving/blob/main/src/plot_test.m

[7] https://blogs.mathworks.com/deep-learning/2017/11/17/deep-learning-for-automated-driving-part-2-lane-detection/?s_tid=srchtitle

[8] Richard S. Sutton, Andrew Barto, Reinforcement Learning: An Introduction, MIT Press 2015, https://www.andrew.cmu.edu/course/10-703/textbook/BartoSutton.pdf

[9] https://github.com/yxgyylj/self_driving/blob/main/src/actor_critic_rl.m

[10] https://jonathan-hui.medium.com/object-detection-speed-and-accuracy-comparison-faster-r-cnn-r-fcn-ssd-and-yolo-5425656ae359

[11] https://www.mathworks.com/help/driving/getting-started-with-automated-driving-toolbox.html?s_tid=CRUX_lftnav