介绍
在本文中,我们将创建一个简单的驾驶模拟以训练分类器来自动驾驶虚拟汽车。我们的模拟将由用户或计算机组成的 3D 场景组成,该场景由多风的道路和单个汽车控制器组成。汽车将在其前方放置一个摄像头,以提供其视角 (POV) 的图像。这个视角图像将用作我们分类器的输入来决定如何控制汽车。在模拟的每个步骤中,汽车将被允许向前移动或向左或向右转弯。
建立模拟
仿真图形
我们将首先创建图形函数来组装和显示模拟。首先让我们为地面创建一个纹理。为简单起见,我们将栅格化一条包含道路左转和右转的内置曲线。
代码语言:javascript复制track = Rasterize[
With[{road =
HilbertCurve[2, DataRange -> {{0.25, 1.75}, {0.25, 1.75}}] /.
Line -> BSplineCurve},
Graphics[{
CapForm["Round"],
Gray, AbsoluteThickness[36], road,
Yellow, Dashing[0.033], AbsoluteThickness[2], road
},
PlotRange -> {{0, 2}, {0, 2}},
Background -> RGBColor["#e8e1ba"]
]
]
]
接下来,我们将定义一个函数来组装包含地面和汽车的 3D 场景。我们将使用两个不同的相机渲染这个场景,以提供道路上汽车的概览以及汽车的视角图像。
代码语言:javascript复制CarSimScene[sim_] := Module[{ground, car},
ground = {
Texture[track],
EdgeForm[None],
Polygon[
{{0, 0, 0}, {100, 0, 0}, {100, 100, 0}, {0, 100, 0}},
VertexTextureCoordinates -> {{0, 0}, {1, 0}, {1, 1}, {0, 1}}
]
};
car =
With[{dimensions = {2, 1, 0}},
{Red,
Rotate[
Cuboid[sim["Position"] - dimensions ,
sim["Position"] dimensions {0, 0, 1}], sim["Rotation"],
{0, 0, 1},
sim["Position"]
]
}
];
{
最后,我们需要一个函数将 3D 场景中的相机定位在汽车的位置,以记录视点图像。此图像仅在分类时使用,因此我们以相对较低的分辨率进行光栅化。经过一些实验,我选择使用 ImageResolution 而不是 RasterSize,因为它生成最终图像的速度快了 2 倍以上。ViewVector 坐标是通过实验确定的,以匹配汽车内部的典型视图。
代码语言:javascript复制CarPOV[sim_] := Module[{
rotation = sim["Rotation"],
pos = sim["Position"]
},
Rasterize[
Graphics3D[
CarSimScene[sim],
ImageSize -> Small,
ViewVector -> {
sim["Position"] {2.1 Cos[sim["Rotation"]],
2.1 Sin[sim["Rotation"]], 1.5},
sim["Position"] {5 Cos[sim["Rotation"]],
5 Sin[sim["Rotation"]], -0.25}
},
ViewAngle -> 1.5,
Boxed -> False,
Background -> LightBlue,
ViewRange -> {0, 0.1}
],
ImageResolution -> 8
]
]
我们现在可以将所有图形函数放在一个单一的总体函数中,该函数显示整个汽车模拟,包括 3D 场景和汽车视角的插图。
代码语言:javascript复制DisplayCarSim[sim_] := Module[{},
Overlay[{
Graphics3D[
CarSimScene[sim],
PlotRange -> {{0, 100}, {0, 100}, Automatic},
ImageSize -> Large,
Background -> LightBlue,
Boxed -> False
],
Labeled[
Framed[sim["POV"], FrameMargins -> None, FrameStyle -> Gray,
ImageSize -> Small], "Car POV", LabelStyle -> Gray]
}]
]
模拟内部
随着我们的模拟图形完成,我们将继续构建推进模拟的功能。我们的模拟状态将包含在单个关联中,该关联包含汽车的位置、旋转和当前 POV 图像的属性,以及所有先前 POV 图像及其相关动作的历史记录。
NewCarSim 为新的模拟初始化我们的数据模型。我们允许一个参数来定义汽车从轨道上的哪个方向开始。我们将在“前进”方向生成训练数据,然后在相反方向测试训练好的自动驾驶汽车。
代码语言:javascript复制NewCarSim[direction_] := Module[{sim},
sim = If[
SymbolName[direction] == "Forward",
<|"Position" -> {10, 10, 0}, "Rotation" -> 0|>,
<|"Position" -> {90, 14, 0}, "Rotation" -> Pi|>
];
sim["History"] = {};
sim["POV"] = CarPOV[sim];
sim
]
推进模拟很容易,StepCarSim 采用模拟状态和动作,“向上”、“向左”或“向右”之一,并相应地移动汽车。每次模拟向前推进时,当前汽车 POV 图像都会与历史列表中提供的操作一起保存。在模拟步骤结束时,生成新的汽车 POV 图像。
代码语言:javascript复制StepCarSim[sim_, action_] := Module[{},
AppendTo[sim["History"], sim["POV"] -> action];
Switch[
action,
"Left", sim["Rotation"] = 0.05,
"Right", sim["Rotation"] -= 0.05,
"Up",
sim["Position"] = {Cos[sim["Rotation"]], Sin[sim["Rotation"]],
0}
];
sim["POV"] = CarPOV[sim];
sim
];
SetAttributes[StepCarSim, HoldFirst];
我们的模拟几乎完成,现在是时候构建交互式界面,让一个人驾驶汽车并生成训练数据。
生成示例训练数据
代码语言:javascript复制sim = NewCarSim[Forward];
EventHandler[
Style[Dynamic[DisplayCarSim[sim]], Selectable -> False],
{
"LeftArrowKeyDown" :> StepCarSim[sim, "Left"],
"RightArrowKeyDown" :> StepCarSim[sim, "Right"],
"UpArrowKeyDown" :> StepCarSim[sim, "Up"]
}
]
当我手动驾驶汽车沿着整条赛道行驶时,模拟生成了近 600 个训练示例。如果您不想创建自己的数据,可以在下面的补充材料部分找到这些数据。让我们来看看一些训练示例:
代码语言:javascript复制TableForm@RandomSample[sim["History"], 3
训练自动驾驶汽车
生成示例数据后,我们现在准备训练分类器来驾驶汽车!
代码语言:javascript复制selfDrivingCar =
Classify[sim["History"], Method -> "LogisticRegression"]
我选择指定逻辑回归是因为它在相对较短的计算时间内提供了最好的准确性。我们可以显示关于我们的分类器的信息并看到超过 70% 的准确率。通过检查学习曲线和准确率曲线,看起来我们的学习接近极限,但可能仍然可以从更多的训练示例中受益。尽管如此,凭借我们少量的示例数据,自动驾驶汽车表现良好。
代码语言:javascript复制Information[selfDrivingCar]
为了预览我们的自动驾驶汽车,我们将使用我们的新分类器来驾驶汽车进行一百次模拟。这只会让我们绕轨道走一点路,但表明分类器正在工作。
代码语言:javascript复制DynamicModule[{selfDrivingSim = NewCarSim[Backward]},
Print[Dynamic[DisplayCarSim[selfDrivingSim]]];
Do[StepCarSim[selfDrivingSim, selfDrivingCar[selfDrivingSim["POV"]]],
100];
]
我们可以增加看到汽车行驶到最后的步骤数,但由于渲染性能相对较慢,我更喜欢每 5 或 10 帧进行预渲染,然后将它们组装在 Manipulate 中,以便轻松地来回平移:
代码语言:javascript复制selfDrivingSim = NewCarSim[Backward];
frames = Table[
With[{img = Rasterize[DisplayCarSim[selfDrivingSim]]},
Do[StepCarSim[selfDrivingSim,
selfDrivingCar[selfDrivingSim["POV"]]], 5];
img
],
120
];
Manipulate[frames[[t]], {t, 1, Length[frames] - 1, 1}]
那么,我们的自动驾驶汽车的工作情况如何?事实上很好。它可以轻松地沿着整个轨道行驶,并且也可以保持在道路的右侧行驶!但是如果我们在错误的道路一侧启动汽车会发生什么?它会混淆吗?让我们来看看:
代码语言:javascript复制DynamicModule[{selfDrivingSim = NewCarSim[Backward]},
selfDrivingSim["Position"] -= {0, 4, 0};
Print[Dynamic[DisplayCarSim[selfDrivingSim]]];
Do[StepCarSim[selfDrivingSim, selfDrivingCar[selfDrivingSim["POV"]]],
100];
]
当自动驾驶汽车从道路的错误一侧起步时,它会立即纠正并转向右侧! 我们的汽车运行良好,特别是考虑到适度的训练集和有限的 Classify 手动优化。
结论
总之,我们可以使用 Mathematica 来创建和渲染 3D 模拟,同时还可以与其机器学习工具连接以构建完整的端到端实验系统。我希望这个例子可以让您深入了解 Mathematica 的机器学习和图形功能的强大功能。