12.md 57.4 KB
Newer Older
W
wizardforcel 已提交
1 2 3 4 5 6


# 在 Raspberry Pi 上开发 TensorFlow 应用程序



W
wizardforcel 已提交
7
根据 Wikipedia 的说法,“ Raspberry Pi 是 Raspberry Pi 基金会在英国开发的一系列小型单板计算机,旨在促进学校和发展中国家的基础计算机科学教学。” [Raspberry Pi 的官方网站](https://www.raspberrypi.org)将其描述为“一种小型且价格合理的计算机,可以用来学习编程。” 如果您以前从未听说过或使用过 Raspberry Pi,请访问其网站,然后您很快就会爱上这个很棒的小东西。 几乎没有什么功能-实际上,TensorFlow 的开发人员从 2016 年中期开始在早期版本的 Raspberry Pi 上提供了 TensorFlow,因此我们可以在微型计算机上运行复杂的 TensorFlow 模型,您只需花费 35 美元即可购买到。 这可能超出了“基础计算机科学的教学”或“学习编程”的范围,但另一方面,如果我们考虑过去几年中移动设备的所有飞速发展,那么看到这一点我们就不会感到惊讶 如何在越来越小的设备中实现越来越多的功能。
W
wizardforcel 已提交
8

W
wizardforcel 已提交
9
在本章中,我们将进入 Raspberry Pi 的有趣世界,Raspberry Pi 是 TensorFlow 正式支持的最小设备。 我们将首先介绍如何获取和设置新的 Raspberry Pi 3 B 板,包括本章中使用的所有必要配件,以使其能够看,听和说。 然后,我们将介绍如何使用 [GoPiGo 机器人基础套件](https://www.dexterindustries.com/shop/gopigo3-robot-base-kit),将 Raspberry Pi 板变成一个可以移动的机器人。 之后,我们将提供最简单的工作步骤,以便在 Raspberry Pi 上设置 TensorFlow 1.6 并构建其示例 Raspberry Pi 应用程序。 我们还将讨论如何集成图像分类,这是我们在[第 2 章](../Text/02.html)*通过转移学习*对图像进行分类时使用的模型,并通过文字转语音功能使机器人告诉我们它可以识别的内容 以及如何集成音频识别,这是我们在[第 5 章](../Text/05.html)*了解简单语音命令*中使用的模型以及 GoPiGo API,可让您使用语音命令来控制机器人的运动 。
W
wizardforcel 已提交
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

最后,我们将向您展示如何使用 TensorFlow 和 OpenAI Gym,这是一个用于开发和比较强化学习算法的 Python 工具包,如何在模拟环境中实现强大的强化学习算法,以使我们的机器人能够在真实的身体中移动和平衡 环境。

在 Google I / O 2016 中,有一个名为*的会话,该会话如何使用 Cloud Vision 和 Speech API* 构建智能的 RasPi Bot(您可以在 YouTube 上观看视频)。 它使用 Google 的 Cloud API 执行图像分类以及语音识别和合成。 在本章中,我们将了解如何在设备上离线实现演示中的任务以及增强学习,从而展示 TensorFlow 在 Raspberry Pi 上的强大功能。

总而言之,我们将在本章中涵盖以下主题,以构建一个可以移动,看到,聆听,说话和学习的机器人:

*   设置 Raspberry Pi 并使其移动
*   在 Raspberry Pi 上设置 TensorFlow
*   图像识别和文字转语音
*   音频识别和机器人运动
*   在 Raspberry Pi 上进行强化学习





# 设置 Raspberry Pi 并使其移动



W
wizardforcel 已提交
31
小型单板 Raspberry Pi 计算机系列包括 Raspberry Pi 3B+,3B,2B,1B+,1A+,0 和 0W(有关详细信息,请参见[这里](https://www.raspberrypi.org/products/#buy-now-modal))。 我们将在此处使用 Pi 3B 主板,您可以从前面的链接或在 [Amazon](https://www.amazon.com/gp/product/B01CD5VC92) 上以 35 美元的价格购买。 我们在主板上使用并测试过的配件及其价格如下:
W
wizardforcel 已提交
32

W
wizardforcel 已提交
33 34 35 36
*   [CanaKit 5V 2.5A Raspberry Pi 电源](https://www.amazon.com/gp/product/B00MARDJZ4)约 10 美元,可在开发期间使用。
*   [Kinobo](https://www.amazon.com/gp/product/B00IR8R7WQ) - 大约 4 美元的可以记录您的语音命令的 USB 2.0 微型麦克风。
*   [USHONK USB 微型扬声器](https://www.amazon.com/gp/product/B075M7FHM1)约合 12 美元,可以播放合成声音。
*   [Arducam 5 Megapixels 1080p 传感器 OV5647 微型相机](https://www.amazon.com/gp/product/B012V1HEP4)约合 14 美元,以支持图像分类。
W
wizardforcel 已提交
37

W
wizardforcel 已提交
38 39 40
*   [16 GB MicroSD 和适配器](https://www.amazon.com/gp/product/B00TDBLTWK),价格约为 10 美元,用于存储 Raspbian(Raspberry Pi 的官方操作系统)的安装文件,并用作 安装后的硬盘驱动器。
*   一个 USB 磁盘,例如 [SanDisk 32GB USB Drive](https://www.amazon.com/gp/product/B008AF380Q),售价 9 美元,将用作交换分区(有关详细信息,请参阅下一节) 因此我们可以手动构建 TensorFlow 库,这是构建和运行 TensorFlow C ++代码所必需的。
*   售价 110 美元的 [GoPiGo 机器人基础套件](https://www.amazon.com/gp/product/B00NYB3J0A)[官方网站](https://www.dexterindustries.com/shop),将 Raspberry Pi 板变成可以移动的机器人。
W
wizardforcel 已提交
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55

您还需要 HDMI 电缆将 Raspberry Pi 板连接到计算机显示器,USB 键盘和 USB 鼠标。 总共要花 200 美元,包括 110 美元的 GoPiGo,来构建一个可以移动,看,听,说的 Raspberry Pi 机器人。 尽管与功能强大的 Raspberry Pi 计算机相比,GoPiGo 套件似乎有点昂贵,但是如果没有它,那么一动不动的 Raspberry Pi 可能会失去很多吸引力。

There's an older blog, *How to build a robot that “sees” with $100 and TensorFlow* ([https://www.oreilly.com/learning/how-to-build-a-robot-that-sees-with-100-and-tensorflow](https://www.oreilly.com/learning/how-to-build-a-robot-that-sees-with-100-and-tensorflow)), written by *Lukas Biewald* in September 2016, that covers how to use TensorFlow and Raspberry Pi 3 with some alternative parts to build a robot that sees and speaks. It's a fun read. What we cover here offers more detailed steps to set up Raspberry Pi 3 with GoPiGo, the user-friendly and Google-recommended toolkit to turn Pi into a robot, and the newer version of TensorFlow 1.6, in addition to adding voice command recognition and reinforcement learning.

现在,让我们首先看看如何设置 Raspbian,Raspberry Pi 开发板的操作系统。





# 设置 Raspberry Pi



W
wizardforcel 已提交
56
最简单的方法是遵循 [Raspbian 软件安装指南](https://www.raspberrypi.org/learning/software-guide/quickstart),总而言之,这是一个简单的三步过程:
W
wizardforcel 已提交
57

W
wizardforcel 已提交
58
1.  为 Windows 或 Mac 下载并安装 [SD 格式化程序](https://www.sdcard.org/downloads/formatter_4/index.html)
W
wizardforcel 已提交
59 60
2.  使用 SD 格式化程序格式化 MicroSD 卡。

W
wizardforcel 已提交
61
3.[这个页面](https://www.raspberrypi.org/downloads/noobs)上下载 Raspbian 的官方简易安装程序 New Out Of Box Software(NOOBS)的离线 ZIP 版本,将其解压缩,然后拖动并 将提取的`NOOBS`文件夹中的所有文件拖放到格式化的 MicroSD 卡中。
W
wizardforcel 已提交
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150

现在弹出 MicroSD 卡并将其插入 Raspberry Pi 板上。 将显示器的 HDMI 电缆以及 USB 键盘和鼠标连接到开发板上。 用电源为开发板供电,然后按照屏幕上的步骤完成 Raspbian 的安装,包括设置 Wifi 网络。 整个安装过程不到一个小时即可完成。 完成后,您可以打开一个终端并输入`ifconfig`来查找电路板的 IP 地址,然后从您的计算中使用`ssh pi@<board_ip_address>`来访问它,正如我们稍后将要看到的,这确实很方便并且需要 在移动中测试控制 Raspberry Pi 机器人-当移动时,您不想或不能将键盘,鼠标和显示器与板子一起使用。

但是默认情况下未启用 SSH,因此,当您首次尝试 SSH 到 Pi 板上时,会出现“ SSH 连接被拒绝”错误。 启用它的最快方法是运行以下两个命令:

```py
sudo systemctl enable ssh
sudo systemctl start ssh
```

之后,您可以使用`pi`登录的默认密码 ssh 进行登录。 当然,您可以使用`passwd`命令将默认密码更改为新密码。

现在我们已经安装了 Raspbian,让我们将 USB 迷你麦克风,USB 迷你扬声器和迷你相机插入 Pi 板上。 USB 麦克风和扬声器均可即插即用。 插入它们后,您可以使用`aplay -l`命令找出支持的音频播放设备:

```py
aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: Device_1 [USB2.0 Device], device 0: USB Audio [USB Audio]
 Subdevices: 1/1
 Subdevice #0: subdevice #0
card 2: ALSA [bcm2835 ALSA], device 0: bcm2835 ALSA [bcm2835 ALSA]
 Subdevices: 8/8
 Subdevice #0: subdevice #0
 Subdevice #1: subdevice #1
 Subdevice #2: subdevice #2
 Subdevice #3: subdevice #3
 Subdevice #4: subdevice #4
 Subdevice #5: subdevice #5
 Subdevice #6: subdevice #6
 Subdevice #7: subdevice #7
card 2: ALSA [bcm2835 ALSA], device 1: bcm2835 ALSA [bcm2835 IEC958/HDMI]
 Subdevices: 1/1
 Subdevice #0: subdevice #0
```

Pi 板上还有一个音频插孔,可用于在开发过程中获得音频输出。 但是 USB 扬声器肯定更方便。

要查找支持的记录设备,请使用`arecord -l`命令:

```py
arecord -l
**** List of CAPTURE Hardware Devices ****
card 1: Device [USB PnP Sound Device], device 0: USB Audio [USB Audio]
 Subdevices: 1/1
 Subdevice #0: subdevice #0
```

现在,您可以使用以下命令测试音频记录:

```py
arecord -D plughw:1,0 -d 3 test.wav
```

`-D`指定音频输入设备,这意味着它是具有卡 1,设备 0 的即插即用设备,如`arecord -l`命令的输出所示。 `-d`以秒为单位指定记录的持续时间。

要在 USB 扬声器上播放录制的音频,首先需要在主目录中创建一个名为 `.asoundrc`的文件,其内容如下:

```py
pcm.!default {
       type plug
       slave {
            pcm "hw:0,0"
       }
}

ctl.!default {
       type hw
       card 0
}
```

请注意,`"hw:0,0"``aplay -l`为 USB 扬声器设备返回的卡 0,设备 0 信息匹配。 现在,您可以使用`aplay test.wav`命令在扬声器上测试录制的音频播放。

Occasionally, after the Pi board reboots, the card number for the USB speaker gets changed by the system automatically and you won't hear the audio when running `aplay test.wav`. In that case, you can run `aplay -l` again to find the new card number set for the USB speaker and update the `~/``.asoundrc` file accordingly.

如果要调节扬声器的音量,请使用`amixer set PCM -- 100%`命令,其中 100%将音量设置为最大。

要加载相机的驱动程序,请运行`sudo modprobe bcm2835-v4l2`命令。 之后,要验证是否已检测到摄像机,请运行`vcgencmd get_camera`命令,该命令应返回`supported=1 detected=1` 。 要在每次主板启动时加载摄像机驱动程序(这是我们需要的),请运行`sudo vi /etc/modules`并在`/etc/modules`的末尾添加一行 `bcm2835-v4l2`(或者您可以运行`sudo bash -c "echo 'bcm2835-v4l2' >> /etc/modules"`)。 当我们运行 TensorFlow 图像分类示例时,我们将在后面的部分中测试相机。

这就是为我们的任务设置 Raspberry Pi 的全部内容。 现在,让我们看看如何使其移动。





# 使树莓派移动



W
wizardforcel 已提交
151
GoPiGo 是一个流行的工具包,可将您的 Raspberry Pi 板变成移动的机器人。 购买并收到我们之前提到的 GoPiGo 机器人基础套件后,请按照[这里](https://www.dexterindustries.com/GoPiGo/get-started-with-the-gopigo3-raspberry-pi-robot/1-assemble-gopigo3)将其与您的 Pi 板组装在一起。 根据您是同时观看 March March Madness 还是 NBA 季后赛比赛,这大约需要一两个小时。
W
wizardforcel 已提交
152 153 154 155 156

完成后,您的 Raspberry Pi 机器人以及我们之前列出的所有附件应如下所示:

![](img/a745c220-8c27-4b70-bef4-bc8dcbc518d8.png)Figure 12\. 1 Raspberry Pi robot with GoPiGo Kit and camera, USB speaker, and USB microphone

W
wizardforcel 已提交
157
现在,使用 Raspberry Pi 电源打开 Pi 机器人,并在启动后使用`ssh pi@<your_pi_board_ip>`连接到它。 要安装 GoPiGo Python 库,以便我们可以使用 [GoPiGo 的 Python API](http://gopigo3.readthedocs.io/en/master/api-basic.html) 控制机器人,请运行以下命令 ,它将执行一个 shell 脚本,该脚本创建一个新的`/home/pi/Dexter`目录并在其中安装所有库和固件文件:
W
wizardforcel 已提交
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192

```py
sudo sh -c "curl -kL dexterindustries.com/update_gopigo3 | bash"
```

您还应该转到`~/Dexter`目录并运行以下命令来更新 GoPiGo 板的固件:

```py
bash GoPiGo3/Firmware/gopigo3_flash_firmware.sh
```

现在运行`sudo reboot`重新启动板,以使更改生效。 Pi 板重新启动后,您可以从 iPython 测试 GoPiGo 和 Raspberry Pi 的运动,可以使用`sudo pip install ipython`进行安装。

要测试基本的 GoPiGo Python API,请先运行 iPython,然后逐行输入以下代码:

Be sure to put your GoPiGo Raspberry Pi robot on a safe surface as it'll start moving around. During the final test, you should use the GoPiGo battery pack to power the robot so it can move freely. But in the development and initial tests, you definitely should use the power adapter to save the battery unless you use a rechargeable battery. Always be careful if you put your robot on your desk as it may fall over if you issue a command causing it to make a bad move.

```py
import easygopigo3 as easy
gpg3_obj = easy.EasyGoPiGo3() 

gpg3_obj.drive_cm(5)
gpg3_obj.drive_cm(-5) 
gpg3_obj.turn_degrees(30)
gpg3_obj.turn_degrees(-30)
gpg3_obj.stop()
```

`drive_cm`根据其参数值是正值还是负值来向前或向后移动机器人。 `turn_degrees`顺时针或逆时针旋转机器人,取决于其参数值是正值还是负值。 因此,前面的示例代码将机器人向前移动 5 厘米,然后向后移动 5 厘米,顺时针旋转 30 度,然后逆时针旋转 30 度。 默认情况下,这些呼叫是阻止呼叫,因此直到机器人完成移动后它们才返回。 要进行非阻塞调用,请添加`False`参数,如下所示:

```py
gpg3_obj.drive_cm(5, False)
gpg3_obj.turn_degrees(30, False)
```

W
wizardforcel 已提交
193
您还可以使用`forward``backward`[许多其他 API 调用](http://gopigo3.readthedocs.io/en/master/api-basic.html)来控制机器人的运动,但是在本章中,我们仅使用`drive_cm``turn_degrees`
W
wizardforcel 已提交
194 195 196 197 198 199 200 201 202 203 204

我们现在准备使用 TensorFlow 向机器人添加更多智能。





# 在 Raspberry Pi 上设置 TensorFlow



W
wizardforcel 已提交
205
要在 Python 中使用 TensorFlow,就像我们稍后在*音频识别**强化学习*部分中所做的那样,我们可以在 TensorFlow Jenkins 持续集成站点上为 Pi 安装[每晚 TensorFlow 1.6 版本](http://ci.tensorflow.org/view/Nightly/job/nightly-pi/223/artifact/output-artifacts)
W
wizardforcel 已提交
206 207 208 209 210

```py
sudo pip install http://ci.tensorflow.org/view/Nightly/job/nightly-pi/lastSuccessfulBuild/artifact/output-artifacts/tensorflow-1.6.0-cp27-none-any.whl
```

W
wizardforcel 已提交
211
此方法更为常见,并在一个不错的博客条目中描述,[《为 Raspberry Pi 交叉编译 TensorFlow》](https://petewarden.com/2017/08/20/cross-compiling-tensorflow-for-the-raspberry-pi),作者是 *Pete Warden*
W
wizardforcel 已提交
212

W
wizardforcel 已提交
213
一种更复杂的方法是使用`makefile`,这在您需要构建和使用 TensorFlow 库时是必需的。 [TensorFlow 官方 makefile 文档](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/makefile)的 Raspberry Pi 部分包含构建 TensorFlow 库的详细步骤,但是它 可能不适用于每个版本的 TensorFlow。 此处的步骤与 TensorFlow 的早期版本(0.10)完美配合,但是在 TensorFlow 1.6 中会导致许多“未定义对`google::protobuf`的引用”错误。
W
wizardforcel 已提交
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299

TensorFlow 1.6 版本已经测试了以下步骤,可从[下载 https://github.com/tensorflow/tensorflow/releases/tag/v1.6.0](https://github.com/tensorflow/tensorflow/releases/tag/v1.6.0) ; 您当然可以在 TensorFlow 发行页面中尝试使用较新的版本,或者通过`git clone https://github.com/tensorflow/tensorflow`克隆最新的 TensorFlow 源,并修复所有可能的问题。

`cd`之后到 TensorFlow 源根目录,然后运行以下命令:

```py
tensorflow/contrib/makefile/download_dependencies.sh
sudo apt-get install -y autoconf automake libtool gcc-4.8 g++-4.8
cd tensorflow/contrib/makefile/downloads/protobuf/
./autogen.sh
./configure
make CXX=g++-4.8
sudo make install
sudo ldconfig # refresh shared library cache
cd ../../../../..
export HOST_NSYNC_LIB=`tensorflow/contrib/makefile/compile_nsync.sh`
export TARGET_NSYNC_LIB="$HOST_NSYNC_LIB"
```

确保您运行的是`make CXX=g++-4.8`,而不是运行在正式 TensorFlow Makefile 文档中的`make`,因为 Protobuf 必须使用与用于构建以下 TensorFlow 的版本相同的`gcc`版本进行编译 库,以修复那些“未定义对`google::protobuf`的引用”错误。 现在尝试使用以下命令构建 TensorFlow 库:

```py
make -f tensorflow/contrib/makefile/Makefile HOST_OS=PI TARGET=PI \
 OPTFLAGS="-Os -mfpu=neon-vfpv4 -funsafe-math-optimizations -ftree-vectorize" CXX=g++-4.8
```

经过几个小时的构建,您可能会收到诸如“虚拟内存耗尽:无法分配内存”之类的错误,否则 Pi 板将由于内存不足而冻结。 要解决此问题,我们需要设置一个交换,因为没有交换,当应用程序用尽内存时,由于内核崩溃,该应用程序将被杀死。 设置交换的方法有两种:交换文件和交换分区。 Raspbian 在 SD 卡上使用默认的 100 MB 交换文件,如下所示,使用`free`命令:

```py
pi@raspberrypi:~/tensorflow-1.6.0 $ free -h
total used free shared buff/cache available
Mem: 927M 45M 843M 660K 38M 838M
Swap: 99M 74M 25M
```

要将交换文件大小提高到 1 GB,请通过 `sudo vi /etc/dphys-swapfile`修改 `/etc/dphys-swapfile`文件,将`CONF_SWAPSIZE=100`更改为`CONF_SWAPSIZE=1024`,然后重新启动交换文件服务:

```py
sudo /etc/init.d/dphys-swapfile stop 
sudo /etc/init.d/dphys-swapfile start
```

此后,`free -h`将显示交换总量为 1.0 GB。

交换分区是在单独的 USB 磁盘上创建的,因此首选交换分区,因为交换分区不会碎片化,但 SD 卡上的交换文件很容易碎片化,从而导致访问速度变慢。 要设置交换分区,请将没有所需数据的 USB 闪存盘插入 Pi 板上,然后运行`sudo blkid`,您将看到类似以下内容:

```py
/dev/sda1: LABEL="EFI" UUID="67E3-17ED" TYPE="vfat" PARTLABEL="EFI System Partition" PARTUUID="622fddad-da3c-4a09-b6b3-11233a2ca1f6"
/dev/sda2: UUID="E67F-6EAB" TYPE="vfat" PARTLABEL="NO NAME" PARTUUID="a045107a-9e7f-47c7-9a4b-7400d8d40f8c"
```

`/dev/sda2`是我们将用作交换分区的分区。 现在卸载并将其格式化为交换分区:

```py
sudo umount /dev/sda2
sudo mkswap /dev/sda2
mkswap: /dev/sda2: warning: wiping old swap signature.
Setting up swapspace version 1, size = 29.5 GiB (31671701504 bytes)
no label, UUID=23443cde-9483-4ed7-b151-0e6899eba9de
```

您将在`mkswap`命令中看到一个 UUID 输出。 运行`sudo vi /etc/fstab`,将以下行添加到具有 UUID 值的`fstab`文件中:

```py
UUID=<UUID value> none swap sw,pri=5 0 0
```

保存并退出 fstab 文件,然后运行`sudo swapon -a`。 现在,如果再次运行`free -h`,将会看到“交换总数”接近 USB 存储设备的大小。 我们绝对不需要所有大小的交换空间—实际上,具有 1 GB 内存的 Raspberry Pi 3 板的建议最大交换大小为 2 GB,但是我们将其保留原样,因为我们只是想成功地构建内存。 TensorFlow 库。

更改任一交换设置后,我们可以重新运行`make`命令:

```py
make -f tensorflow/contrib/makefile/Makefile HOST_OS=PI TARGET=PI \
 OPTFLAGS="-Os -mfpu=neon-vfpv4 -funsafe-math-optimizations -ftree-vectorize" CXX=g++-4.8
```

完成此操作后,TensorFlow 库将以`tensorflow/contrib/makefile/gen/lib/libtensorflow-core.a`的形式生成,如果您已经阅读了我们手动构建 TensorFlow 库的前几章,则应该看起来很熟悉。 现在,我们可以使用该库构建图像分类示例。





# 图像识别和文字转语音



W
wizardforcel 已提交
300
`tensorflow/contrib/pi_examples: label_image`和相机中有两个 [TensorFlow Raspberry Pi 示例应用程序](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/pi_examples)。 我们将修改相机示例应用程序,以将文本集成到语音中,以便该应用程序在四处走动时可以说出其识别出的图像。 在构建和测试这两个应用程序之前,我们需要安装一些库并下载预构建的 TensorFlow Inception 模型文件:
W
wizardforcel 已提交
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348

```py
sudo apt-get install -y libjpeg-dev
sudo apt-get install libv4l-dev

curl https://storage.googleapis.com/download.tensorflow.org/models/inception_dec_2015_stripped.zip -o /tmp/inception_dec_2015_stripped.zip

cd ~/tensorflow-1.6.0
unzip /tmp/inception_dec_2015_stripped.zip -d tensorflow/contrib/pi_examples/label_image/data/
```

要构建`label_image`和相机应用程序,请运行:

```py
make -f tensorflow/contrib/pi_examples/label_image/Makefile
make -f tensorflow/contrib/pi_examples/camera/Makefile
```

构建应用程序时,您可能会遇到以下错误:

```py
./tensorflow/core/platform/default/mutex.h:25:22: fatal error: nsync_cv.h: No such file or directory
 #include "nsync_cv.h"
 ^
 compilation terminated.
```

要解决此问题,请运行`sudo cp tensorflow/contrib/makefile/downloads/nsync/public/nsync*.h /usr/include`

然后编辑`tensorflow/contrib/pi_examples/label_image/Makefile``tensorflow/contrib/pi_examples/camera/Makefile`文件,添加以下库,并在再次运行`make`命令之前包含路径:

```py
-L$(DOWNLOADSDIR)/nsync/builds/default.linux.c++11 \

-lnsync \
```

要测试运行这两个应用程序,请直接运行这些应用程序:

```py
tensorflow/contrib/pi_examples/label_image/gen/bin/label_image
tensorflow/contrib/pi_examples/camera/gen/bin/camera
```

看看 C ++源代码`tensorflow/contrib/pi_examples/label_image/label_image.cc``tensorflow/contrib/pi_examples/camera/camera.cc`,您会看到它们使用与前几章中的 iOS 应用类似的 C ++代码来加载模型图文件,准备输入张量,运行 模型,并获得输出张量。

默认情况下,摄像机示例还使用`label_image/data`文件夹中解压缩的预构建 Inception 模型。 但是对于您自己的特定图像分类任务,您可以像通过[第 2 章](../Text/02.html)*使用*通过转移学习对图像进行分类一样,提供通过转移学习重新训练的模型。 ]参数在运行两个示例应用程序时。

W
wizardforcel 已提交
349
通常,语音是 Raspberry Pi 机器人与我们互动的主要 UI。 理想情况下,我们应该运行 TensorFlow 支持的自然声音**文本到语音****TTS**)模型,例如 [WaveNet](https://deepmind.com/blog/wavenet-generative-model-raw-audio)[Tacotron](https://github.com/keithito/tacotron),但运行和部署不在本章范围之内。 这样的模型。 事实证明,我们可以使用称为 [CMU **Flite**](http://www.festvox.org/flite) 的简单得多的 TTS 库,它提供了相当不错的 TTS,并且 只需一个简单的命令即可安装它:`sudo apt-get install flite`。 如果要安装最新版本的 Flite 以期希望获得更好的 TTS 质量,只需从链接下载最新的 Flite 源并进行构建。
W
wizardforcel 已提交
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377

要使用我们的 USB 扬声器测试 Flite,请使用`-t`参数运行 flite,然后使用双引号引起来的文本字符串,例如 `flite -t "i recommend the ATM machine"`。 如果您不喜欢默认语音,则可以通过运行 `flite -lv`找到其他受支持的语音,它们应返回 `Voices available: kal awb_time kal16 awb rms slt` 。 然后,您可以指定用于 TTS 的语音: `flite -voice rms -t "i recommend the ATM machine"`

要使相机应用程序说出识别出的对象,这是当 Raspberry Pi 机器人四处移动时所期望的行为,可以使用以下简单的`pipe`命令:

```py
tensorflow/contrib/pi_examples/camera/gen/bin/camera | xargs -n 1 flite -t
```

您可能会听到太多声音。 要微调图像分类的 TTS 结果,还可以在使用`make -f tensorflow/contrib/pi_examples/camera/Makefile`重建示例之前,修改 camera.cc 文件并将以下代码添加到`PrintTopLabels`函数中:

```py
std::string cmd = "flite -voice rms -t \"";
cmd.append(labels[label_index]);
cmd.append("\"");
system(cmd.c_str());
```

现在,我们已经完成了 *如何使用 Cloud Vision 和 Speech API 演示*来构建智能 RasPi Bot 的图像分类和语音合成任务,而不使用任何 Cloud API。 使用我们在[第 5 章](../Text/05.html)*了解简单语音命令*中使用的相同模型,在 Raspberry Pi 上进行音频识别。





# 音频识别和机器人运动



W
wizardforcel 已提交
378
要使用 TensorFlow 教程中的[预训练音频识别模型](https://www.tensorflow.org/tutorials/audio_recognition)或我们之前描述的重新训练模型,我们将重用来自[这个页面](https://gist.github.com/aallan)的 Python 脚本`listen.py`,并在识别四个基本音频命令后添加 GoPiGo API 调用以控制机器人的运动:“左”,“右”,“去” ,”和“停止”。 预训练模型支持的其他六个命令-“是”,“否”,“向上”,“向下”,“打开”和“关闭”,在我们的示例中不太适用,如果需要 ,您可以使用[第 5 章](../Text/05.html)*了解简单语音命令*中所示的重新训练模型,以支持针对特定任务的其他语音命令。
W
wizardforcel 已提交
379

W
wizardforcel 已提交
380
要运行脚本,请先从[这里](http://download.tensorflow.org/models/speech_commands_v0.01.zip)下载预训练的音频识别模型,然后将其解压缩到`/tmp`,或者对我们在[第 5 章](../Text/05.html)“了解简单语音命令”中使用的模型使用`scp`到 Pi 板的`/tmp`目录,然后运行:
W
wizardforcel 已提交
381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417

```py
python listen.py --graph /tmp/conv_actions_frozen.pb --labels /tmp/conv_actions_labels.txt -I plughw:1,0
```

或者您可以运行:

```py
python listen.py --graph /tmp/speech_commands_graph.pb --labels /tmp/conv_actions_labels.txt -I plughw:1,0
```

请注意,`plughw value 1,0`应该与您的 USB 麦克风的卡号和设备号匹配,可以使用我们之前显示的`arecord -l`命令找到。

`listen.py`脚本还支持许多其他参数。 例如,我们可以使用`--detection_threshold 0.5`代替默认的检测阈值 0.8。

现在,让我们快速了解`listen.py`的工作原理,然后再添加 GoPiGo API 调用以使机器人移动。 `listen.py`使用 Python 的`subprocess`模块及其`Popen`类产生带有适当参数的运行`arecord`命令的新过程。 `Popen`类具有`stdout`属性,该属性指定`arecord`执行的命令的标准输出文件句柄,可用于读取记录的音频字节。

加载训练后的模型图的 Python 代码如下:

```py
with tf.gfile.FastGFile(filename, 'rb') as f:
  graph_def = tf.GraphDef()
  graph_def.ParseFromString(f.read())
  tf.import_graph_def(graph_def, name='')
```

使用`tf.Session()`创建 TensorFlow 会话,并在加载图形并创建会话之后,将记录的音频缓冲区以及采样率作为输入数据发送到 TensorFlow 会话的`run`方法,该方法返回 识别的预测:

```py
run(softmax_tensor, {
 self.input_samples_name_: input_data,
 self.input_rate_name_: self.sample_rate_
 })
```

在这里,将`softmax_tensor`定义为 TensorFlow 图的`get_tensor_by_name(self.output_name_)`,将`output_name_``input_samples_name_``input_rate_name_`分别定义为`labels_softmax``decoded_sample_data:0``decoded_sample_data:1`,与 我们在[第 5 章](../Text/05.html)*了解简单语音命令*中的 iOS 和 Android 应用程序中使用过。

W
wizardforcel 已提交
418
在之前的章节中,我们主要使用 Python 训练和测试 TensorFlow 模型,然后再使用本机 TensorFlow C ++库的 Java 接口代码在使用 C ++或 Android 的 iOS 中运行模型。 在 Raspberry Pi 上,您可以选择直接使用 TensorFlow Python API 或 C ++ API 在 Pi 上运行 TensorFlow 模型,尽管通常仍会在功能更强大的模型上进行训练 电脑。 有关完整的 TensorFlow Python API 文档,请参见[这里](https://www.tensorflow.org/api_docs/python)
W
wizardforcel 已提交
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486

要使用 GoPiGo Python API 使机器人根据您的语音命令移动,请首先在`listen.py`中添加以下两行:

```py
import easygopigo3 as gpg

gpg3_obj = gpg.EasyGoPiGo3()
```

然后将以下代码添加到`def add_data`方法的末尾:

```py
if current_top_score > self.detection_threshold_ and time_since_last_top > self.suppression_ms_:
  self.previous_top_label_ = current_top_label
  self.previous_top_label_time_ = current_time_ms
  is_new_command = True
  logger.info(current_top_label)

  if current_top_label=="go":
    gpg3_obj.drive_cm(10, False)
  elif current_top_label=="left":
    gpg3_obj.turn_degrees(-30, False)
  elif current_top_label=="right":
    gpg3_obj.turn_degrees(30, False)
  elif current_top_label=="stop":
    gpg3_obj.stop()
```

现在 p 将您的 Raspberry Pi 机器人放在地面上,从计算机上用`ssh`连接到它,然后运行以下脚本:

```py
python listen.py --graph /tmp/conv_actions_frozen.pb --labels /tmp/conv_actions_labels.txt -I plughw:1,0 --detection_threshold 0.5
```

您将看到以下输出:

```py
INFO:audio:started recording
INFO:audio:_silence_
INFO:audio:_silence_
```

然后,您可以说左,右,停止,前进和停止,以查看命令被识别并且机器人相应地移动:

```py
INFO:audio:left
INFO:audio:_silence_
INFO:audio:_silence_
INFO:audio:right
INFO:audio:_silence_
INFO:audio:stop
INFO:audio:_silence_
INFO:audio:go
INFO:audio:stop
```

您可以在单独的终端中运行相机应用程序,因此,当机器人根据您的语音命令走动时,它会识别出所看到的新图像并说出结果。 这就是构建一个基本的 Raspberry Pi 机器人所需的全部内容,该机器人可以听,动,看和说-Google I / O 2016 演示所做的事情,却不使用任何 Cloud API。 它远不是一个能听懂自然人的语音,进行有趣的对话或执行有用且不重要的任务的幻想机器人。 但是,借助预训练,再训练或其他强大的 TensorFlow 模型,并使用各种传感器,您当然可以为我们构建的 Pi 机器人增加越来越多的智能和物理动力。

在下一节中,您将看到如何在 Pi 上运行经过预训练和再训练的 TensorFlow 模型,我们将向您展示如何向使用 TensorFlow 构建和训练的机器人添加强大的强化学习模型。 毕竟,强化学习的反复试验方式及其与环境交互以获取最大回报的本质,使得强化学习成为机器人非常合适的机器学习方法。





# 在 Raspberry Pi 上进行强化学习



W
wizardforcel 已提交
487
[OpenAI Gym](https://gym.openai.com)是一个开源 Python 工具包,提供了许多模拟环境来帮助您开发,比较和训练强化学习算法,因此 您无需购买所有传感器并在实际环境中训练您的机器人,这在时间和金钱上都是昂贵的。 在本部分中,我们将向您展示如何在 TenAI 健身房的称为 [CartPole](https://gym.openai.com/envs/CartPole-v0)
W
wizardforcel 已提交
488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736

要安装 OpenAI Gym,请运行以下命令:

```py
git clone https://github.com/openai/gym.git
cd gym
sudo pip install -e .
```

您可以通过运行`pip list`来验证是否已安装 TensorFlow 1.6 和 Gym(*在 Raspberry Pi 上设置 TensorFlow 的最后一部分*部分介绍了如何安装 TensorFlow 1.6):

```py
pi@raspberrypi:~ $ pip list
gym (0.10.4, /home/pi/gym)
tensorflow (1.6.0)
```

或者您可以启动 iPython,然后导入 TensorFlow 和 Gym:

```py
pi@raspberrypi:~ $ ipython
Python 2.7.9 (default, Sep 17 2016, 20:26:04) 
IPython 5.5.0 -- An enhanced Interactive Python.

In [1]: import tensorflow as tf

In [2]: import gym

In [3]: tf.__version__
Out[3]: '1.6.0'

In [4]: gym.__version__
Out[4]: '0.10.4'
```

现在,我们都准备使用 TensorFlow 和 Gym 来构建一些有趣的在 Raspberry Pi 上运行的强化学习模型。





# 了解 CartPole 模拟环境



CartPole 是一种可用于训练机器人以保持平衡的环境-如果它携带某些东西并希望在移动时保持其放置状态。 由于本章的范围,我们将仅构建在模拟 CartPole 环境中工作的模型,但是可以肯定地将模型以及模型的构建和训练方式应用于类似于 CartPole 的实际物理环境。

在 CartPole 环境中,将杆连接到推车,该推车沿轨道水平移动。 您可以对购物车执行 1(向右加速)或 0(向左加速)操作。 杆子开始直立,目的是防止其跌落。 杆保持直立的每个时间步长都奖励 1。 当极点与垂直方向的夹角超过 15 度,或者手推车从中心移出 2.4 个单位以上时,情节就会结束。

现在让我们使用 CartPole 环境。 首先,创建一个新环境并找出代理可以在该环境中采取的措施:

```py
env = gym.make("CartPole-v0")
env.action_space
# Discrete(2)
env.action_space.sample()
# 0 or 1
```

每个观察(状态)都由关于购物车的四个值组成:其水平位置,速度,极角和角速度:

```py
obs=env.reset()
obs
# array([ 0.04052535, 0.00829587, -0.03525301, -0.00400378])
```

环境中的每个步骤(动作)将导致新的观察,动作的奖励,情节是否完成(如果是,则您无法采取任何进一步的步骤)以及一些其他信息:

```py
obs, reward, done, info = env.step(1)

obs
# array([ 0.04069127, 0.2039052 , -0.03533309, -0.30759772])
```

记住动作(或步骤)1 表示向右移动,0 表示向左移动。 要查看当您继续向右移动购物车时情节可以持续多长时间,请运行:

```py
while not done:
    obs, reward, done, info = env.step(1)
    print(obs)

#[ 0.08048328 0.98696604 -0.09655727 -1.54009127]
#[ 0.1002226 1.18310769 -0.12735909 -1.86127705]
#[ 0.12388476 1.37937549 -0.16458463 -2.19063676]
#[ 0.15147227 1.5756628 -0.20839737 -2.52925864]
#[ 0.18298552 1.77178219 -0.25898254 -2.87789912]
```

现在,让我们手动执行从头到尾的一系列操作,并打印出观测值的第一个值(水平位置)和第三个值(极点与垂直方向的度数),因为这两个值确定一个情节是否为 完成。

首先,重置环境并正确加速购物车几次:

```py
import numpy as np

obs=env.reset()
obs[0], obs[2]*360/np.pi
# (0.008710582898326602, 1.4858315848689436)

obs, reward, done, info = env.step(1)
obs[0], obs[2]*360/np.pi
# (0.009525842685697472, 1.5936049816642313)

obs, reward, done, info = env.step(1)
obs[0], obs[2]*360/np.pi
# (0.014239775393474322, 1.040038643681757)

obs, reward, done, info = env.step(1)
obs[0], obs[2]*360/np.pi
# (0.0228521194217381, -0.17418034908781568)
```

您会看到,推车向右移动时,其位置值会越来越大,杆的垂直度会越来越小,最后一步显示的是负度,这意味着杆位于中心的左侧。 所有这一切都是有道理的,在您心目中,您最喜欢的狗用杆子推着推车的画面有些生动。 现在,更改动作以使购物车向左(0)加速几次:

```py
obs, reward, done, info = env.step(0)
obs[0], obs[2]*360/np.pi
# (0.03536432554326476, -2.0525933052704954)

obs, reward, done, info = env.step(0)
obs[0], obs[2]*360/np.pi
# (0.04397450935915654, -3.261322987287562)

obs, reward, done, info = env.step(0)
obs[0], obs[2]*360/np.pi
# (0.04868738508385764, -3.812330822419413)

obs, reward, done, info = env.step(0)
obs[0], obs[2]*360/np.pi
# (0.04950617929263011, -3.7134404042580687)

obs, reward, done, info = env.step(0)
obs[0], obs[2]*360/np.pi
# (0.04643238384389254, -2.968245724428785)

obs, reward, done, info = env.step(0)
obs[0], obs[2]*360/np.pi
# (0.039465670006712444, -1.5760901885345346)
```

首先,您可能会惊讶于 0 动作导致位置(`obs[0]`)连续变大几次,但请记住,手推车以一定速度运动,并且一个或多个动作将手推车移至另一个 方向不会立即降低位置值。 但是,如果您继续将购物车向左移动,如前两个步骤所示,您会看到购物车的位置开始变小(向左)。 现在继续执行 0 动作,您会看到位置越来越小,负值表示推车进入中心的左侧,而杆的角度越来越大:

```py
obs, reward, done, info = env.step(0)
obs[0], obs[2]*360/np.pi
# (0.028603948219811447, 0.46789197320636305)

obs, reward, done, info = env.step(0)
obs[0], obs[2]*360/np.pi
# (0.013843572459953138, 3.1726728882727504)

obs, reward, done, info = env.step(0)
obs[0], obs[2]*360/np.pi
# (-0.00482029774222077, 6.551160678086707)

obs, reward, done, info = env.step(0)
obs[0], obs[2]*360/np.pi
# (-0.02739315127299434, 10.619948631208114)
```

如我们前面所述,定义 CartPole 环境的方式是情节“当极距与垂直方向成 15 度以上时结束”,因此让我们再做一些动作并打印出`done`值, 时间:

```py
obs, reward, done, info = env.step(0)
obs[0], obs[2]*360/np.pi, done
# (-0.053880356973985064, 15.39896478042983, False)

obs, reward, done, info = env.step(0)
obs[0], obs[2]*360/np.pi, done
# (-0.08428612474261402, 20.9109976051126, False)

obs, reward, done, info = env.step(0)
obs[0], obs[2]*360/np.pi, done
# (-0.11861214326416822, 27.181070460526062, True)

obs, reward, done, info = env.step(0)
# WARN: You are calling 'step()' even though this environment has already returned done = True. You should always call 'reset()' once you receive 'done = True' -- any further steps are undefined behavior.
```

环境决定何时将`done`返回`True`时会有一些延迟-尽管前两个步骤已经返回了大于 15 度的度数(当极点与垂直线成 15 度以上时,情节结束了) ,您仍然可以对环境执行 0 操作。 第三步将`done`返回为`True`,并且环境中的另一步骤(最后一步)将导致警告,因为环境已经完成了该情节。

对于 CartPole 环境,每个`step`调用返回的`reward`值始终为 1,信息始终为{}。 这就是关于 CartPole 模拟环境的全部知识。 现在我们了解了 CartPole 的工作原理,让我们看看可以在每种状态(观察)下制定什么样的策略,我们可以让该策略告诉我们要采取的操作(步骤),以便我们可以保持杆直立 换句话说,就是尽可能长的时间,这样我们才能最大化我们的回报。 请记住,强化学习中的一项政策只是一项功能,该功能以代理商所处的状态为输入,并输出代理商接下来应采取的行动,以实现价值最大化或长期回报。





# 从基本的直观政策开始



显然,每次都执行相同的动作(全 0 或 1s)不会使杆保持太直的状态。 为了进行基线比较,请运行以下代码,以查看在每个情节中应用相同操作时在 1,000 个情节中获得的平均奖励:

```py
# single_minded_policy.py

import gym
import numpy as np

env = gym.make("CartPole-v0")
total_rewards = []
for _ in range(1000):
  rewards = 0
  obs = env.reset()
  action = env.action_space.sample()
  while True:
    obs, reward, done, info = env.step(action) 
    rewards += reward
    if done:
      break
  total_rewards.append(rewards)

print(np.mean(total_rewards))
# 9.36
```

因此,所有 1,000 集的平均奖励约为 10。请注意, `env.action_space.sample()`对 0 或 1 动作进行采样,与随机输出 0 或 1 相同。您可以通过评估
[ `np.sum([env.action_space.sample() for _ in range(10000)])`,应该接近 5,000。

要查看其他策略如何更好地工作,让我们使用一个简单直观的策略,当极数度数为正(在垂直方向的右侧)时执行 1 动作(向右移动购物车),然后执行 0(将购物车向右移动) 当极数度数为负时(在垂直方向的左侧)。 这项政策是有道理的,因为我们可能会采取尽可能长时间保持平衡的措施:

```py
# simple_policy.py

import gym
import numpy as np

env = gym.make("CartPole-v0")

total_rewards = []
for _ in range(1000):
  rewards = 0
  obs = env.reset()
  while True:
    action = 1 if obs[2] > 0 else 0
    obs, reward, done, info = env.step(action) 
    rewards += reward
    if done:
       break
  total_rewards.append(rewards)

print(np.mean(total_rewards))
# 42.19
```

现在,每 1,000 集的平均奖励为 42,与 9.36 相比有很大提高。

W
wizardforcel 已提交
737
现在让我们看看我们是否可以制定出更好,更复杂的政策。 回想一下,策略只是从状态到操作的映射或功能。 在过去的几年中,我们在神经网络的兴起中了解到的一件事是,如果不清楚如何定义复杂的功能(例如强化学习中的策略),请考虑一下神经网络,毕竟这是通用函数逼近器 (有关详细信息,请参见[神经网络可以计算任何函数的可视化证明](http://neuralnetworksanddeeplearning.com/chap4.html),Michael Nelson)。
W
wizardforcel 已提交
738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852

We covered AlphaGo and AlphaZero in the last chapter, and Jim Fleming wrote an interesting blog entry titled Before AlphaGo there was TD-Gammon ([https://medium.com/jim-fleming/before-alphago-there-was-td-gammon-13deff866197](https://medium.com/jim-fleming/before-alphago-there-was-td-gammon-13deff866197)), which was the first reinforcement learning application that trains itself using a neural network as an evaluation function to beat human Backgammon champions. Both the blog entry and the book, *Reinforcement Learning: An Introduction* by *Sutton* and *Barto*, have in-depth descriptions of TD-Gammon; you can also Google "Temporal Difference Learning and TD-Gammon" for the original paper if you want to know more about using neural networks as a powerful universal function.



# 使用神经网络建立更好的政策



首先,让我们看看如何使用简单的完全连接的(密集)神经网络构建随机策略,该网络将观察中的 4 个值作为输入,使用 4 个神经元的隐藏层,并输出 0 动作的概率,基于 ,代理可以采样 0 到 1 之间的下一个动作:

```py
# nn_random_policy.py

import tensorflow as tf
import numpy as np
import gym

env = gym.make("CartPole-v0") 

num_inputs = env.observation_space.shape[0]
inputs = tf.placeholder(tf.float32, shape=[None, num_inputs])
hidden = tf.layers.dense(inputs, 4, activation=tf.nn.relu)
outputs = tf.layers.dense(hidden, 1, activation=tf.nn.sigmoid)
action = tf.multinomial(tf.log(tf.concat([outputs, 1-outputs], 1)), 1)

with tf.Session() as sess:
  sess.run(tf.global_variables_initializer())

  total_rewards = []
  for _ in range(1000):
    rewards = 0
    obs = env.reset()
    while True:
      a = sess.run(action, feed_dict={inputs: obs.reshape(1, num_inputs)})
      obs, reward, done, info = env.step(a[0][0]) 
      rewards += reward
      if done:
        break
    total_rewards.append(rewards)

print(np.mean(total_rewards))

```

请注意,我们使用 `tf.multinomial` 函数根据动作 0 和 1 的概率分布(分别定义为`outputs``1-outputs`(两者之和))对动作进行采样 概率为 1)。 总奖励的平均值约为 20 左右,比一心一意的策略好,但比上一节中的简单直观的策略差。 这是一个神经网络,它在没有任何训练的情况下生成随机策略。

为了训练网络,我们使用`tf.nn.sigmoid_cross_entropy_with_logits`定义了网络输出与所需`y_target`作用之间的损失函数,该损失函数是使用前面小节中的基本简单策略定义的,因此我们希望该神经网络策略能够实现大致相同的效果 奖励是基本的非神经网络策略:

```py
# nn_simple_policy.py

import tensorflow as tf
import numpy as np
import gym

env = gym.make("CartPole-v0")

num_inputs = env.observation_space.shape[0]
inputs = tf.placeholder(tf.float32, shape=[None, num_inputs])
y = tf.placeholder(tf.float32, shape=[None, 1])
hidden = tf.layers.dense(inputs, 4, activation=tf.nn.relu)
logits = tf.layers.dense(hidden, 1)
outputs = tf.nn.sigmoid(logits)
action = tf.multinomial(tf.log(tf.concat([outputs, 1-outputs], 1)), 1)

cross_entropy = tf.nn.sigmoid_cross_entropy_with_logits(labels=y, logits=logits)
optimizer = tf.train.AdamOptimizer(0.01)
training_op = optimizer.minimize(cross_entropy)

with tf.Session() as sess:
  sess.run(tf.global_variables_initializer())

  for _ in range(1000):
    obs = env.reset()

    while True:
      y_target = np.array([[1\. if obs[2] < 0 else 0.]])
      a, _ = sess.run([action, training_op], feed_dict={inputs: obs.reshape(1, num_inputs), y: y_target})
      obs, reward, done, info = env.step(a[0][0])
      if done:
        break
  print("training done")
```

我们将`outputs`定义为`logits`净输出的`sigmoid`函数,即动作概率 0,然后使用`tf.multinomial`采样动作。 请注意,我们使用标准的`tf.train.AdamOptimizer`及其`minimize`方法来训练网络。 要测试并查看该策略的性能,请运行以下代码:

```py
  total_rewards = []
  for _ in range(1000):
    rewards = 0
    obs = env.reset()

    while True:
      y_target = np.array([1\. if obs[2] < 0 else 0.])
      a = sess.run(action, feed_dict={inputs: obs.reshape(1, num_inputs)})
      obs, reward, done, info = env.step(a[0][0])
      rewards += reward
      if done:
        break
    total_rewards.append(rewards)

  print(np.mean(total_rewards))
```

总奖励的平均值约为 40 左右,这与使用无神经网络的简单策略的收益大致相同,这正是我们在训练阶段将`y: y_target`专门用于简单策略时的期望值 ,以训练网络。

现在,我们都准备探索如何在此基础上实现策略梯度方法,以使我们的神经网络性能更好,获得的奖励要大几倍。

策略梯度的基本思想是,为了训练神经工作以生成更好的策略,当所有代理从环境中知道的都是从任何给定状态采取行动时所能获得的奖励(这意味着我们不能 使用监督学习进行培训),我们可以采用两种新机制:

*   折扣奖励:每个动作的价值都需要考虑其未来动作的奖励。 例如,一个动作获得立即奖励 1,但是在两个动作(步骤)之后结束情节的长期奖励应该比获得立即奖励 1 但在 10 个步骤之后结束情节的动作具有较少的长期奖励。 动作的折现奖励的典型公式是其立即奖励加上其每个未来奖励的倍数和由未来步骤提供动力的折现率的总和。 因此,如果一个动作序列在剧集结束前有 1、1、1、1、1 个奖励,则第一个动作的折扣奖励为 *1+(1 * discount_rate)+(1 * discount_rate ** 2) +(1 ** *折扣率** 3)+(1 *折扣率** 4)*

*   测试运行当前策略,查看哪些操作导致较高的折扣奖励,然后使用折扣奖励更新当前策略的梯度(权重损失),以使具有较高折扣奖励的操作在网络更新后, 下次被选中的可能性更高。 重复这样的测试运行并多次更新该过程,以训练神经网络以获得更好的策略。

W
wizardforcel 已提交
853
有关更详细的讨论和策略梯度的演练,请参阅 Andrej Karpathy 的博客条目,[《深度强化学习:来自像素的乒乓》](http://karpathy.github.io/2016/05/31/rl)。 现在,让我们看看如何为 TensorFlow 中的 CartPole 问题实现策略梯度。
W
wizardforcel 已提交
854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891

首先,导入 tensorflow,numpy 和 gym,并定义一个用于计算标准化和折价奖励的助手方法:

```py
import tensorflow as tf
import numpy as np
import gym

def normalized_discounted_rewards(rewards):
    dr = np.zeros(len(rewards))
    dr[-1] = rewards[-1]
    for n in range(2, len(rewards)+1):
        dr[-n] = rewards[-n] + dr[-n+1] * discount_rate
    return (dr - dr.mean()) / dr.std()
```

例如,如果 discount_rate 为 0.95,则奖励列表[1,1,1]中第一个动作的折扣奖励为 1 + 1 * 0.95 + 1 * 0.95 ** 2 = 2.8525,并且折扣奖励 第二和最后一个元素是 1.95 和 1; 奖励列表[1,1,1,1,1]中第一个动作的打折奖励为 1 + 1 * 0.95 + 1 * 0.95 ** 2 + 1 * 0.95 ** 3 + 1 * 0.95 ** 4 = 4.5244,其余动作为 3.7099、2.8525、1.95 和 1 。 [1,1,1]和[1,1,1,1,1]的归一化贴现奖励为[1.2141,0.0209,-1.2350]和 [1.3777,  0.7242 ,  0.0362,-0.6879,-1.4502]。 每个规范化的打折清单按降序排列,这意味着动作持续的时间越长(在情节结束之前),其奖励就越大。

接下来,创建 CartPole 体育馆环境,定义`learning_rate``discount_rate`超参数,并像以前一样使用四个输入神经元,四个隐藏神经元和一个输出神经元构建网络:

```py
env = gym.make("CartPole-v0")

learning_rate = 0.05
discount_rate = 0.95

num_inputs = env.observation_space.shape[0]
inputs = tf.placeholder(tf.float32, shape=[None, num_inputs])
hidden = tf.layers.dense(inputs, 4, activation=tf.nn.relu) 
logits = tf.layers.dense(hidden, 1)
outputs = tf.nn.sigmoid(logits) 
action = tf.multinomial(tf.log(tf.concat([outputs, 1-outputs], 1)), 1)

prob_action_0 = tf.to_float(1-action)
cross_entropy = tf.nn.sigmoid_cross_entropy_with_logits(logits=logits, labels=prob_action_0)
optimizer = tf.train.AdamOptimizer(learning_rate)
```

W
wizardforcel 已提交
892
请注意,此处不再像以前的简单神经网络策略示例那样使用`minimize`函数,因为我们需要手动微调梯度以考虑每个动作的折价奖励。 这就要求我们首先使用`compute_gradients`方法,然后以所需的方式更新渐变,最后调用`apply_gradients`方法([我们大多数时候应该使用的`minimize`方法实际上是在幕后调用`compute_gradients`和`apply_gradients`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/training/optimizer.py))。
W
wizardforcel 已提交
893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922

因此,让我们现在为网络参数(权重和偏差)计算交叉熵损失的梯度,并设置梯度占位符,稍后将使用考虑了计算梯度和动作折现奖励的值来填充梯度占位符 在测试运行期间使用当前策略采取的措施:

```py
gvs = optimizer.compute_gradients(cross_entropy)
gvs = [(g, v) for g, v in gvs if g != None]
gs = [g for g, _ in gvs]

gps = []
gvs_feed = []
for g, v in gvs:
    gp = tf.placeholder(tf.float32, shape=g.get_shape())
    gps.append(gp)
    gvs_feed.append((gp, v))
training_op = optimizer.apply_gradients(gvs_feed)
```

`optimizer.compute_gradients(cross_entropy)`返回的`gvs`是一个元组列表,每个元组都由(可训练变量的`cross_entropy`的)梯度和可训练变量组成。 例如,如果您在整个程序运行后查看`gvs`,您将看到类似以下内容:

```py
[(<tf.Tensor 'gradients/dense/MatMul_grad/tuple/control_dependency_1:0' shape=(4, 4) dtype=float32>,
 <tf.Variable 'dense/kernel:0' shape=(4, 4) dtype=float32_ref>),
 (<tf.Tensor 'gradients/dense/BiasAdd_grad/tuple/control_dependency_1:0' shape=(4,) dtype=float32>,
 <tf.Variable 'dense/bias:0' shape=(4,) dtype=float32_ref>),
 (<tf.Tensor 'gradients/dense_2/MatMul_grad/tuple/control_dependency_1:0' shape=(4, 1) dtype=float32>,
 <tf.Variable 'dense_1/kernel:0' shape=(4, 1) dtype=float32_ref>),
 (<tf.Tensor 'gradients/dense_2/BiasAdd_grad/tuple/control_dependency_1:0' shape=(1,) dtype=float32>,
 <tf.Variable 'dense_1/bias:0' shape=(1,) dtype=float32_ref>)]
```

W
wizardforcel 已提交
923
请注意,kernel 只是权重的另一个名称,(4,4),(4,),(4,1)和(1,)是权重的形状和对第一个(输入到隐藏)和 第二层(隐藏到输出)。 如果您从 iPython 多次运行脚本,则`tf`对象的默认图形将包含先前运行的可训练变量,因此,除非调用`tf.reset_default_graph()`,否则需要使用`gvs = [(g, v) for g, v in gvs if g != None]`删除那些过时的训练变量, 将返回 None 渐变(有关`computer_gradients`的更多信息,请参见[这里](https://www.tensorflow.org/api_docs/python/tf/train/AdamOptimizer#compute_gradients))。
W
wizardforcel 已提交
924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045

现在,玩一些游戏并保存奖励和渐变值:

```py
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())

    for _ in range(1000):
        rewards, grads = [], []
        obs = env.reset()
        # using current policy to test play a game
        while True:
            a, gs_val = sess.run([action, gs], feed_dict={inputs: 
                                   obs.reshape(1, num_inputs)})
            obs, reward, done, info = env.step(a[0][0])
            rewards.append(reward)
            grads.append(gs_val)
            if done:
                break  
```

在测试游戏之后,使用折价奖励更新渐变并训练网络(请记住`training_op`被定义为`optimizer.apply_gradients(gvs_feed)`):

```py
        # update gradients and do the training 
        nd_rewards = normalized_discounted_rewards(rewards)
        gp_val = {}
        for i, gp in enumerate(gps):
            gp_val[gp] = np.mean([grads[k][i] * reward for k, reward in 
                             enumerate(nd_rewards)], axis=0)
        sess.run(training_op, feed_dict=gp_val)
```

最终,经过 1000 次迭代的测试和更新,我们可以测试经过训练的模型:

```py
    total_rewards = []

    for _ in range(100):
      rewards = 0
      obs = env.reset()

      while True:
        a = sess.run(action, feed_dict={inputs: obs.reshape(1, 
                                           num_inputs)})
        obs, reward, done, info = env.step(a[0][0])
        rewards += reward
        if done:
          break
      total_rewards.append(rewards)

    print(np.mean(total_rewards))
```

请注意,我们现在使用经过训练的策略网络和`sess.run`以当前观察为输入来执行下一步操作。 总奖励的输出平均值约为 200,这与我们是否使用神经网络的简单直观策略相比有很大改进。

您也可以在使用`tf.train.Saver`进行训练后保存训练后的模型,就像我们在前几章中曾经做过的多次:

```py
  saver = tf.train.Saver()
  saver.save(sess, "./nnpg.ckpt") 
```

然后,您可以使用以下命令在单独的测试程序中重新加载它:

```py
with tf.Session() as sess:
  saver.restore(sess, "./nnpg.ckpt")
```

之前的所有策略实现都在 Raspberry Pi 上运行,甚至使用 TensorFlow 训练强化学习策略梯度模型的模型实现也需要大约 15 分钟才能完成。 这是我们涵盖的每项政策,在 Pi 上运行后返回的总奖励:

```py
pi@raspberrypi:~/mobiletf/ch12 $ python single_minded_policy.py 
9.362

pi@raspberrypi:~/mobiletf/ch12 $ python simple_policy.py 
42.535

pi@raspberrypi:~/mobiletf/ch12 $ python nn_random_policy.py 
21.182

pi@raspberrypi:~/mobiletf/ch12 $ python nn_simple_policy.py 
41.852

pi@raspberrypi:~/mobiletf/ch12 $ python nn_pg.py 
199.116
```

现在,您已经拥有了一个强大的基于神经网络的策略模型,可以帮助您的机器人保持平衡,并在模拟环境中进行了全面测试,在将模拟环境的 API 返回值替换为真实环境数据后,您可以将其部署在真实的物理环境中 当然,但是用于构建和训练神经网络强化学习模型的代码当然可以轻松地重用。





# 概要



在本章中,我们首先详细介绍了使用所有必需的附件和操作系统以及将 Raspberry Pi 板变成移动机器人的 GoPiGo 工具包来设置 Raspberry Pi 的详细步骤。 然后,我们介绍了如何在 Raspberry Pi 上安装 TensorFlow 并构建 TensorFlow 库,以及如何将 TTS 与图像分类集成以及如何使用 GoPiGO API 进行音频命令识别,从而使 Raspberry Pi 机器人可以移动,看到,收听和讲话所有内容 无需使用 Cloud API。 最后,我们介绍了用于强化学习的 OpenAI Gym 工具包,并向您展示了如何使用 TensorFlow 构建和训练功能强大的强化学习神经网络模型,以使您的机器人在模拟环境中保持平衡。





# 最后的话



因此,该说再见了。 在本书中,我们从三个经过预先训练的 TensorFlow 模型开始,这些模型分别是图像分类,对象检测和神经样式转换,并详细讨论了如何重新训练模型并在 iOS 和 Android 应用中使用它们。 然后,我们介绍了使用 Python 构建的 TensorFlow 教程中的三个有趣的模型(音频识别,图像字幕和快速绘制),并展示了如何在移动设备上重新训练和运行这些模型。

之后,我们从零开始开发了用于预测 TensorFlow 和 Keras 中的股价的 RNN 模型,两个用于数字识别和像素转换的 GAN 模型以及一个用于 Connect 4 的类似于 AlphaZero 的模型,以及使用所有这些 TensorFlow 模型的完整 iOS 和 Android 应用 。 然后,我们介绍了如何将 TensorFlow Lite 以及 Apple 的 Core ML 与标准机器学习模型和转换后的 TensorFlow 模型一起使用,展示了它们的潜力和局限性。 最后,我们探索了如何使用 TensorFlow 构建 Raspberry Pi 机器人,该机器人可以使用强大的强化学习算法来移动,观看,聆听,讲话和学习。

我们还展示了同时使用 TensorFlow Pod 和手动构建的 TensorFlow 库的 Objective-C 和 Swift iOS 应用程序,以及使用即用型 TensorFlow 库和手动构建库的 Android 应用程序,以修复您可能遇到的各种错误 在移动设备上部署和运行 TensorFlow 模型时遇到的问题。

我们已经介绍了很多,但是还有很多要讲的。 TensorFlow 的新版本已经快速发布。 已经构建并实现了采用最新研究论文的新 TensorFlow 模型。 本书的主要目的是向您展示使用各种智能 TensorFlow 模型的足够的 iOS 和 Android 应用程序,以及所有实用的故障排除和调试技巧,以便您可以在移动设备上快速部署和运行自己喜欢的 TensorFlow 模型,以供下一次使用 杀手级移动 AI 应用程序。

如果您想使用 TensorFlow 或 Keras 构建自己的出色模型,实现最令您兴奋的算法和网络,则需要在本书结束后继续学习,因为我们没有详细介绍如何做到这一点, 但希望我们能激发您足够的动力来开始这一旅程,并从书中获得保证,一旦您构建并训练了模型,便知道如何快速,随时随地在移动设备上部署和运行它们。

关于走哪条路和要解决哪些 AI 任务,伊恩·古德费洛(Ian Goodfellow)在接受安德鲁·伍(Andrew Ng)采访时的建议可能是最好的:*问问自己,下一步做什么是最好的,哪条是最适合的 采取:强化学习,无监督学习或生成对抗网络*。 无论如何,这将是一条充满兴奋的绝妙之路,当然还要有艰苦的工作,而您从本书中学到的技能就像您的智能手机一样,随时可以为您服务,并准备好 您将使您的甜蜜而聪明的小设备变得更加甜蜜和智能。