基于卷积神经网络的交通标志检测
我们将建立一个 CNN 模型,以检测交通标志。
CNN Model
如果你想从事计算机视觉领域的职业,学习卷积神经网络或 ConvNets 或 CNN 是非常重要的。CNN 有助于直接在图像上运行神经网络,并且比许多深度神经网络更有效和准确。与其他模型相比,ConvNet 模型在图像上的训练更容易、更快。
如果你不熟悉 ConvNet 的基础知识,你可以从这里学习。
我们将使用keras
包来建立 CNN 模型。
获取数据集
德国交通标志检测数据集在此提供。该数据集由 43 个不同类别的 39209 幅图像组成。这些图像在这些类别之间分布不均匀,因此该模型可以比其他类别更准确地预测一些类别。
我们可以用各种图像修改技术填充数据集,如旋转、颜色失真或模糊图像。我们将在原始数据集上训练模型,并将查看模型的准确性。然后,我们将添加更多的数据,使每个类均匀,并检查模型的准确性。
数据预处理
CNN 模型的局限性之一是它们不能在不同维度的图像上进行训练。因此,数据集中必须有相同维度的图像。
我们将检查数据集的所有图像的尺寸,以便我们可以将图像处理成具有相似的尺寸。在该数据集中,图像具有从 16163 到 1281283 的非常动态的尺寸范围,因此不能直接传递给 ConvNet 模型。
我们需要将图像压缩或插值成一维图像。不,为了压缩大部分数据,不要过度拉伸图像,我们需要确定两者之间的维度,并保持图像数据的最大准确性。我决定用 64643 的尺寸。
我们将使用[opencv](https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_setup/py_intro/py_intro.html)
包将图像转换成给定的尺寸。
import cv2def resize_cv(img):
return cv2.resize(img, (64, 64), interpolation = cv2.INTER_AREA)
cv2
是opencv
的一个包。[resize](https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_geometric_transformations/py_geometric_transformations.html?highlight=resize#scaling)
方法将图像变换到给定的维度。在这里,我们将图像转换为 64*64 的尺寸。插值将定义您想要使用哪种类型的技术来拉伸或压缩图像。Opencv 提供了 5 种类型的插值技术,基于它们用来评估结果图像的像素值的方法。技法有 [INTER_AREA, INTER_NEAREST, INTER_LINEAR, INTER_CUBIC, INTER_LANCZOS4](https://docs.opencv.org/2.4/modules/imgproc/doc/geometric_transformations.html#resize)
。我们将使用INTER_AREA
插值技术,它更适合图像抽取,但对于外推技术,它类似于INTER_NEAREST
。我们可以使用INTER_CUBIC
,但是它需要很高的计算能力,所以我们不会使用它。
数据加载
上面我们学习了如何预处理图像。现在,我们将加载数据集,并在决定的维度中转换它们。
该数据集总共包括 43 个类。换句话说,该数据集中有 43 种不同类型的交通标志,每个标志都有自己的文件夹,由不同大小和清晰度的图像组成。数据集中总共有 39209 幅图像。
我们可以绘制不同交通标志的图像数量直方图。
import seaborn as sns
fig = sns.distplot(output, kde=False, bins = 43, hist = True, hist_kws=dict(edgecolor="black", linewidth=2))
fig.set(title = "Traffic signs frequency graph",
xlabel = "ClassId",
ylabel = "Frequency")
Traffic signs frequency graph
ClassId 是为每个唯一的交通标志指定的唯一 Id。
正如我们从图表中看到的,数据集并不包含每个类别的等量图像,因此,该模型在检测某些交通标志时可能会比其他交通标志更准确。
我们可以通过使用旋转或扭曲技术改变图像来使数据集一致,但我们将在其他时间这样做。
由于数据集被划分到多个文件夹中,并且图像的命名不一致,我们将通过将(64643)维中的所有图像转换到一个列表list_image
中,并将它相似的交通标志转换到另一个列表output
中来加载所有图像。我们将使用imread
读取图像。
list_images = []
output = []
for dir in os.listdir(data_dir):
if dir == '.DS_Store' :
continue
inner_dir = os.path.join(data_dir, dir)
csv_file = pd.read_csv(os.path.join(inner_dir,"GT-" + dir + '.csv'), sep=';')
for row in csv_file.iterrows() :
img_path = os.path.join(inner_dir, row[1].Filename)
img = imread(img_path)
img = img[row[1]['Roi.X1']:row[1]['Roi.X2'],row[1]['Roi.Y1']:row[1]['Roi.Y2'],:]
img = resize_cv(img)
list_images.append(img)
output.append(row[1].ClassId)
data_dir
是数据集所在目录的路径。
数据集已加载,现在我们需要将其分为训练集和测试集。也在验证集中。但是如果我们直接分割,那么模型将不会得到所有交通标志的训练,因为数据集不是随机的。所以,首先我们将随机化数据集。
input_array = np.stack(list_images)import keras
train_y = keras.utils.np_utils.to_categorical(output)randomize = np.arange(len(input_array))
np.random.shuffle(randomize)
x = input_array[randomize]
y = train_y[randomize]
我们可以看到,我已经将输出数组转换为分类输出,因为模型将以这种方式返回。
现在,分割数据集。我们将以 60:20:20 的比例将数据集分别拆分为训练数据集、验证数据集和测试数据集。
split_size = int(x.shape[0]*0.6)
train_x, val_x = x[:split_size], x[split_size:]
train1_y, val_y = y[:split_size], y[split_size:]split_size = int(val_x.shape[0]*0.5)
val_x, test_x = val_x[:split_size], val_x[split_size:]
val_y, test_y = val_y[:split_size], val_y[split_size:]
训练模型
from keras.layers import Dense, Dropout, Flatten, Input
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import BatchNormalization
from keras.optimizers import Adam
from keras.models import Sequentialhidden_num_units = 2048
hidden_num_units1 = 1024
hidden_num_units2 = 128
output_num_units = 43epochs = 10
batch_size = 16
pool_size = (2, 2)
#list_images /= 255.0
input_shape = Input(shape=(32, 32,3))model = Sequential([Conv2D(16, (3, 3), activation='relu', input_shape=(64,64,3), padding='same'),
BatchNormalization(),Conv2D(16, (3, 3), activation='relu', padding='same'),
BatchNormalization(),
MaxPooling2D(pool_size=pool_size),
Dropout(0.2),
Conv2D(32, (3, 3), activation='relu', padding='same'),
BatchNormalization(),
Conv2D(32, (3, 3), activation='relu', padding='same'),
BatchNormalization(),
MaxPooling2D(pool_size=pool_size),
Dropout(0.2),
Conv2D(64, (3, 3), activation='relu', padding='same'),
BatchNormalization(),
Conv2D(64, (3, 3), activation='relu', padding='same'),
BatchNormalization(),
MaxPooling2D(pool_size=pool_size),
Dropout(0.2),Flatten(),Dense(units=hidden_num_units, activation='relu'),
Dropout(0.3),
Dense(units=hidden_num_units1, activation='relu'),
Dropout(0.3),
Dense(units=hidden_num_units2, activation='relu'),
Dropout(0.3),
Dense(units=output_num_units, input_dim=hidden_num_units, activation='softmax'),
])model.compile(loss='categorical_crossentropy', optimizer=Adam(lr=1e-4), metrics=['accuracy'])trained_model_conv = model.fit(train_x.reshape(-1,64,64,3), train1_y, epochs=epochs, batch_size=batch_size, validation_data=(val_x, val_y))
我们已经使用了keras
包。
对于了解每一层的意义你可以阅读 这篇博客 。
评估模型
model.evaluate(test_x, test_y)
该模型得到评估,你可以找到 99%的准确性。
预测结果
pred = model.predict_classes(test_x)
您可以预测每个图像的类别,并验证模型的工作方式。
你可以在这里 找到整个工作 代码。
制作:风的轨迹
我们如何创建了一个机场跑道全球建筑的地图,结果是一个风的地图。
空中旅行已经存在了一百多年。自成立以来,数以千计的机场跑道已经…
trailsofwind.figures.cc](https://trailsofwind.figures.cc/)
当在飞机上飞行时,人造建筑以及它们如何改变地球的面貌令人叹为观止。鸟瞰图为人类如何与景观互动提供了一个全新的视角,利用每个山谷建造村庄,在森林周围塑造田野,在山路上修建高速公路。然而,一些最大的人造建筑是航空旅行本身所需要的:机场。但是为什么它们被建成现在的样子,跑道的方向是如何确定的呢?
在做了一些研究后,我们发现大多数机场跑道都建在该地区的平均风向上,以防止侧风着陆,这对飞机和乘客都是危险的。在风向非常不规则的地区,跑道通常被设计成不同的方向,以确保运行过程的平稳。这就是为什么在这样的地区,机场不仅有平行的跑道,还会根据风向的变化形成不同的形状,比如三角形和正方形。
机场跑道形状
我们的结论是,机场的方位和巨大数量理论上可以提供一个地区的大致风向信息,因为机场在其建筑中反映了风向。
数据探索
工具 JupiterLab,牛郎星
对于更密集的工作,我们有一个 CSV 格式的所有机场、国家和地区的数据转储,我们更新…
ourairports.com](http://ourairports.com/data/)
在寻找合适的记录时,我们看到了 我们的报告 页面。它包含了世界上所有机场的广泛集合,还提供了进一步的信息,如乘客频率、高于的海拔和低于海平面的以及另一个具有关于跑道(长度、宽度、表面等)的详细参考的数据集。).在移除了不相关的数据,如直升机场、关闭的机场和没有坐标的跑道之后,我们将跑道数据集与机场数据集进行了比较。****
如您所见,数据集中的许多跑道都没有地理配准,这就是大量跑道无法显示的原因。由此导致的许多机场的缺乏在南美、非洲和巴布亚新几内亚尤为明显。
在使用 follow 初步可视化跑道以查看数据集中偏离的跑道线后,我们手动消除了错误。
跑道在长度和宽度方面有很大的不同,在大多数情况下遵循在机场降落的飞机类型的规格。一种类型的飞机起飞所需的跑道长度取决于各种属性,如重量、空气动力学、发动机和当地气压,对于一架 A380(见第 130 页)来说,长度在几百米到几千米之间。
这个可视化的数据集显示,跑道的宽度比长度有更清晰的结构。
为了比较跑道方向,统一显示它们是很重要的。我们用 Geographiclib 计算了跑道端点坐标之间的中点,并计算了相对于南北轴的旋转角度,以便稍后在地图上投影这些线。
计算出各自的角度后,是时候看看全球跑道的方向了。因此,我们将方位减少到几乎 179°的半圆,以避免重复计算角度(因为着陆带可以从两侧飞行),并给出更清晰的表示。
这里引人注目的是跑道朝向磁北极的高架方向。在南北轴(0°)和东西轴(90°**)**可以看到明显增加的数量,较小的峰值在 45°和-45°处。
为了在 web 应用程序中进一步使用,所有不重要的信息都被删除并导出到最终的 JSON 文件中。坐标点四舍五入到小数点后六位数,以减小文件大小。
设计
工具 Sketchapp,Mapbox Studio
为了可视化我们的发现,我们需要一个交互式的世界地图,它将跑道绘制成可比较的线条,并给出各个跑道的详细视图。
Mapbox Studio Interface
Mapbox Studio 提供了用您自己的设计来设计 Openstreet 地图数据的可能性,以及添加额外的外部地图数据和字体的可能性。为了可视化实际的地图,我们的挑战是使地图尽可能简单,同时提供适当的方向信息。
应该表示以下元素。
- 跑道
- 滑行道
- 立面模型
- 水
- 街道
- 国家边界
- 机场名称
- 国际航空运输协会机场代码
由三个字母组成的国际航空运输协会-机场代码用于准确识别机场。类似于国家的 ISO 代码。
这也用于设计机场机票,我们想把它放在微型网站的侧边栏中。在这些票的帮助下,可以讲述一个带有相应的可视化、图例和插图的故事。
在完整的桌面版本中,用户可以在地图上自由导航,也可以在侧边栏中滚动讲故事。在智能手机版本中,地图用于在滚动浏览故事的同时显示我们探索的本质,因为地图上许多线条的表示非常注重性能,并且导航有限。为了在移动设备上提供更好的用户体验,重点应该是故事而不是免费导航。
在探险中还有许多其他有趣的发现。然而,为了保持故事简短切题,我们在我们的社交媒体账户中发布了有趣的事实作为预尝,而不是用太多的信息塞满网站。
编码
主要工具 React.js,D3.js,Mapbox GL,Typescript,React Waypoints
我们的微型网站的基础是 react-map-gl,一个绑定到 Mapbox GL JS 的 react,它允许我们显示我们自己在 Mapbox Designstudio 中创建的地图设计。地图上的画布覆盖使线条的实际可视化成为可能。
为了在缩放时使线条变小并消失,我们使用了 D3 提供的缩放功能。
React Waypoints 提供了在滚动侧边栏时触发某些事件的可能性,以便使用 react-map-gl 提供的 fly to 插值器飞到地图的区域。
虽然在 Sketchapp 中有两个侧边栏图形被设计并导出为静态 SVG,但所有跑道的分布表示必须通过编程来创建。为了不超出南北轴上的计数范围,对数标度是必要的。同时,可视化应该与地图直接联系起来理解,这就是为什么线的径向表示似乎是最合适的,能够直接在地图上读取角度。
结论
像往常一样,我们会在项目上花更多的时间来展示更多有趣的事实和见解。
未来,我们希望直接从 OpenStreetMap 中提取跑道数据,以增加显示机场的数量。此外,可以添加一个带有一些过滤器的微型网站,以便根据某些标准搜索跑道。为了提高性能,还可以为显示的行编写自己的着色器。
我们乐于分享我们的知识,并一直在寻找新的挑战和跨学科的项目和合作伙伴。
我们将非常高兴收到您的反馈。
使用 Pytorch 训练线条分割模型
让我们从确定我们想要解决的问题开始,这个问题是由这个项目激发的。
给定包含文本行的图像,返回该图像的像素标签,每个像素属于背景或手写行。
项目结构
它由 5 个主要部分组成,一个用于笔记本,一个用于共享 python 代码、数据集、Google Cloud 脚本,一个用于保存模型权重。
在一个生产项目中,您可能会有更多像web
和api
这样的目录。
我还选择使用pipenv
而不是conda
和virtualenv
来管理我的 python 环境。我最近才从conda
切换到pipenv
,我发现它在任何地方都能一如既往地正常工作。
对于 GPU 培训,我使用了一个谷歌云实例和一个 T4 英伟达 GPU。Bash 脚本管理实例的生命周期,从最初创建到启动、连接和停止。
数据
数据集在raw
目录中的[toml](https://en.wikipedia.org/wiki/TOML)
文件中描述,一个toml
文件基本上由键、值对组成。git 忽略了data
下的其他目录,因为它们将包含实际的完整数据集下载。
笔记本电脑
我使用笔记本进行探索,并将其作为构建、清理数据集和构建培训基本管道所需代码的高级容器。
Python 文件
在src
目录下,我保存了可以在不同笔记本之间共享和重用的代码。遵循良好的软件工程实践是快速正确完成工作的关键,在 ML 代码中发现和识别 bug 是非常困难的。这就是为什么你要从小处着手,并经常重申。
python 环境
您可以使用以下命令使用 linuxbrew 或 macbrew 在 Linux 或 mac 上安装pipenv
:
brew install pipenv
然后您可以使用pipenv install SOMETHING
从您的项目目录中下载您的依赖项。
数据集
我将使用这个旧的学术数据集作为基础来构建线条分割数据集,以训练一个 UNet 小型网络来检测手写线条。
数据集中的原始图像如下所示,它们还带有定义边界框的 XML 文件。
在notebooks/01-explore-iam-dataset.ipynb
我下载了数据集,解压后用 XML 文件中的数据叠加了一些随机图像。
接下来,我裁剪图像并生成遮罩图像来匹配新的尺寸。遮罩图像是我们将用于训练最终模型的地面真实图像。
最后,我将数据分为训练、有效和测试
网络
因为我们没有很多数据可用于训练,所以我使用了一个基于 Keras 实现的迷你版本的 UNet 架构。
使用这个巨大的库,我可以通过一个特定输入大小的前馈来可视化网络。
培训渠道
既然我们已经准备好了数据,并且定义了我们想要训练的网络,那么是时候建立一个基本的训练管道了。
首先是定义一个火炬dataset
,并使用DataLoader
遍历它
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms, utils class FormsDataset(Dataset): def __init__(self, images, masks, num_classes: int, transforms=None):
self.images = images
self.masks = masks
self.num_classes = num_classes
self.transforms = transforms
def __getitem__(self, idx):
image = self.images[idx]
image = image.astype(np.float32)
image = np.expand_dims(image, -1)
image = image / 255
if self.transforms:
image = self.transforms(image)
mask = self.masks[idx]
mask = mask.astype(np.float32)
mask = mask / 255
mask[mask > .7] = 1
mask[mask <= .7] = 0
if self.transforms:
mask = self.transforms(mask)
return image, mask
def __len__(self):
return len(self.images)train_dataset = FormsDataset(train_images, train_masks, number_of_classes, get_transformations(True))
train_data_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
print(f'Train dataset has {len(train_data_loader)} batches of size {batch_size}')
接下来,我定义训练循环
# Use gpu for training if available else use cpu
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')# Here is the loss and optimizer definition
criterion = torch.nn.NLLLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)# The training loop
total_steps = len(train_data_loader)
print(f"{epochs} epochs, {total_steps} total_steps per epoch")for epoch in range(epochs):
for i, (images, masks) in enumerate(train_data_loader, 1):
images = images.to(device)
masks = masks.type(torch.LongTensor)
masks = masks.reshape(masks.shape[0], masks.shape[2], masks.shape[3])
masks = masks.to(device)
# Forward pass
outputs = model(images)
softmax = F.log_softmax(outputs, dim=1)
loss = criterion(softmax, masks)
# Backward and optimize
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (i) % 100 == 0:
print (f"Epoch [{epoch + 1}/{epochs}], Step [{i}/{total_steps}], Loss: {loss.item():4f}")
以下是最终的预测
你可以点击查看 TF2 支持的 Keras 项目。
谢谢你能走到这一步。我想说的最后一点是,不幸的是,大多数可用的在线材料要么提供了糟糕的建议,要么非常基本,它们实际上没有提供多少价值,有些是完全错误的。有一些很棒的资源,比如他们的 60 分钟闪电战系列(T1)和很棒的 T2 API 文档(T3)。还有这个小抄和这个伟大的 GitHub 回购。
如果你喜欢阅读这篇文章,或者觉得它有帮助,我很乐意收到你的来信,给我留言或者在 Twitter 上关注我,当我发布新内容时,你会得到通知。
训练自定义数据集掩码 RCNN
在 Mask RCNN 模型上轻松训练自定义数据集的教程:终于轮到你了!
如何使用自定义数据集训练掩膜 RCNN 模型的分步说明。
要求
首先简单地克隆下面的库,它是一个单独类分段的演示。(我们也将涵盖多个类别)。
git clone [https://github.com/miki998/Custom_Train_MaskRCNN](https://github.com/miki998/Custom_Train_MaskRCNN)
正如 README.md 中提到的,需要安装一个 mrcnn 库,这个库不是标准的。您只需要做以下事情:
git clone [https://github.com/matterport/Mask_RCNN.git](https://github.com/matterport/Mask_RCNN.git)
cd Mask_RCNN
python setup.py install
cd ../
rm -rf Mask_RCNN
最重要的是,需要一个起始权重,当然你可以编写自己的随机初始化器,但是在我们的例子中,我们决定简单地选择原作者给出的缺省值. h5。要得到重量,你可以从我的驱动器上得到。
cd weights
wget [https://drive.google.com/open?id=1h62_fPkdrBufw1xCaeGwm2SgLWVtFHFQ](https://drive.google.com/open?id=1h62_fPkdrBufw1xCaeGwm2SgLWVtFHFQ)
cd ../
用法(指训练脚本)
在克隆存储库之后,您应该得到如下结构。所有要进行的修改都在 train.py 文件中,因此使用您最喜欢的文本编辑器,您只需编辑然后添加数据集。
从第 65 行到第 74 行,简单地修改 category 变量和它的类名,以匹配您的数据集(下面是最初编写的内容):
# define 81 classes that the coco model knowns about
Category = 'food'class_names = [‘bread-wholemeal’, ‘potatoes-steamed’, ‘broccoli’, ‘butter’, ‘hard-cheese’, ‘water’, ‘banana’, ‘wine-white’, ‘bread-white’, ‘apple’, ‘pizza-margherita-baked’, ‘salad-leaf-salad-green’, ‘zucchini’, ‘water-mineral’, ‘coffee-with-caffeine’, ‘avocado’, ‘tomato’, ‘dark-chocolate’, ‘white-coffee-with-caffeine’, ‘egg’, ‘mixed-salad-chopped-without-sauce’, ‘sweet-pepper’, ‘mixed-vegetables’, ‘mayonnaise’, ‘rice’, ‘chips-french-fries’, ‘carrot’, ‘tomato-sauce’, ‘cucumber’, ‘wine-red’, ‘cheese’, ‘strawberries’, ‘espresso-with-caffeine’, ‘tea’, ‘chicken’, ‘jam’, ‘leaf-spinach’, ‘pasta-spaghetti’, ‘french-beans’, ‘bread-whole-wheat’]
额外注意:显然,如果你想改变超参数,如学习每张图片或批量大小的 GPU 的步骤/ nb,这里是如何做到这一点。
class CustomConfig(Config): """Configuration for training on the toy dataset. Derives from the base Config class and overrides some values. """
# Give the configuration a recognizable name NAME = category # We use a GPU with 12GB memory, which can fit two images. # Adjust down if you use a smaller GPU.
IMAGES_PER_GPU = 1 # Number of classes (including background)
NUM_CLASSES = 1 + len(class_names)
# Background + toy # Number of training steps per epoch
STEPS_PER_EPOCH = 100 # Skip detections with < 90% confidence
DETECTION_MIN_CONFIDENCE = 0.9
你只需要改变写在 train.py 这一部分的参数,更多的参数,你可以查看 matterport 的 github:https://github.com/matterport/Mask_RCNN
最后,在将您自己的数据集放入数据集文件夹(检查文件夹内部以了解要放入的内容及其格式)后,运行以下命令开始训练:
python3 train.py train --dataset=./dataset --weights=coco
输入以下命令后,您应该会看到下图:
非常感谢您的阅读,敬请关注更多有趣的文章!你可以随时联系我获取更多的信息,或者如果你想在这个问题上合作。另外,点击这个链接(指向联盟计划)真的会帮我解决问题!您只需完成一些快速任务(只需等待和激活通知),所有这些将真正帮助我了解更多未来的硬件相关内容!
使用 AMD GPU 和 Keras 训练神经网络
ROCm 平台入门
AMD 正在开发一个新的高性能计算平台,称为 ROCm。它的目标是创建一个通用的开源环境,能够与 Nvidia(使用 CUDA)和 AMD GPUs 接口(进一步信息)。
本教程将解释如何设置一个神经网络环境,在一个或多个配置中使用 AMD GPUs。
在软件方面:我们将能够使用 Docker 在 ROCm 内核上运行 Tensorflow v1.12.0 作为 Keras 的后端。
安装和部署 ROCm 需要特定的硬件/软件配置。
硬件要求
官方文档(ROCm v2.1)建议采用以下硬件解决方案。
支持的 CPU
当前支持 PCIe Gen3 + PCIe 原子处理器的 CPU 有:
- AMD 锐龙 CPUs
- AMD 锐龙 APU 中的 CPU:
- AMD 锐龙线程处理器
- AMD EPYC CPU;
- 英特尔至强 E7 v3 或更新的 CPUs
- 英特尔至强 E5 v3 或更新的 CPUs
- 英特尔至强 E3 v3 或更新的 CPUs
- 英特尔酷睿 i7 v4(i7–4xxx)、酷睿 i5 v4(i5–4xxx)、酷睿 i3 v4(i3–4xxx)或更新的 CPU(即 Haswell 系列或更新的产品)。
- 一些 Ivy Bridge-E 系统
支持的 GPU
ROCm 官方支持使用以下芯片的 AMD GPUs:
- GFX8 GPUs
- “斐济”芯片,如 AMD 公司的镭龙 R9 Fury X 和镭龙本能 MI8
- “北极星 10”芯片,如在 AMD 镭龙 RX 480/580 和镭龙本能军情六处
- “北极星 11”芯片,如 AMD 镭龙 RX 470/570 和镭龙专业 WX 4100
- “北极星 12”芯片,如 AMD 公司的镭龙 RX 550 和镭龙 RX 540
- GFX9 GPUs
- “织女星 10”芯片,如 AMD 公司的镭龙 RX 织女星 64 和镭龙本能 MI25
- “织女星 7 纳米”芯片(镭龙本能 MI50,镭龙七)
软件要求
在软件方面,ROCm 的当前版本(v2.1)仅在基于 Linux 的系统中受支持。
ROCm 2.1.x 平台支持以下操作系统:
Ubuntu 16.04.x 和 18.04.x(版本 16.04.3 和更新版本或内核 4.13 和更新版本)
CentOS 7.4、7.5 和 7.6(使用 devtoolset-7 运行时支持)
RHEL 7.4、7.5 和 7.6(使用 devtoolset-7 运行时支持)
测试设置
作者使用了以下硬件/软件配置来测试和验证环境:
硬件
- CPU:英特尔至强 E5–2630 l
- 内存:2 个 8 GB
- 主板:微星 X99A 金环版
- GPU: 2 个 RX480 8GB + 1 个 RX580 4GB
- 固态硬盘:三星 850 Evo (256 GB)
- 硬盘:WDC 1TB
软件
- 操作系统:LTS Ubuntu 18.04
ROCm 安装
为了让一切正常工作,建议在全新安装的操作系统中开始安装过程。以下步骤参照 Ubuntu 18.04 LTS 操作系统,其他操作系统请参照官方文档。
第一步是安装 ROCm 内核和依赖项:
更新你的系统
打开一个新的终端CTRL + ALT + T
sudo apt update
sudo apt dist-upgrade
sudo apt install libnuma-dev
sudo reboot
添加 ROCm apt 库
要下载和安装 ROCm stack,需要添加相关的存储库:
wget -qO - http://repo.radeon.com/rocm/apt/debian/rocm.gpg.key | sudo apt-key add -echo 'deb [arch=amd64] http://repo.radeon.com/rocm/apt/debian/ xenial main' | sudo tee /etc/apt/sources.list.d/rocm.list
安装 ROCm
现在需要更新 apt 库列表并安装rocm-dkms
元包:
sudo apt update
sudo apt install rocm-dkms
设置权限
官方文档建议使用当前用户创建一个新的video
组来访问 GPU 资源。
首先,检查系统中的组,发出:
groups
然后将您自己添加到视频群组:
sudo usermod -a -G video $LOGNAME
您可能希望确保您添加到系统中的任何未来用户都被默认放入“视频”组。为此,您可以运行以下命令:
echo 'ADD_EXTRA_GROUPS=1' | sudo tee -a /etc/adduser.conf
echo 'EXTRA_GROUPS=video' | sudo tee -a /etc/adduser.conf
然后重新启动系统:
reboot
测试 ROCm 堆栈
现在建议发出以下命令来测试 ROCm 安装。
打开一个新的终端CTRL + ALT + T
,发出以下命令:
/opt/rocm/bin/rocminfo
输出应该如下所示:链接
然后复核签发:
/opt/rocm/opencl/bin/x86_64/clinfo
输出应该是这样的:链接
官方文档最后建议将 ROCm 二进制文件添加到 PATH:
echo 'export PATH=$PATH:/opt/rocm/bin:/opt/rocm/profiler/bin:/opt/rocm/opencl/bin/x86_64' | sudo tee -a /etc/profile.d/rocm.sh
恭喜你!ROCm 已正确安装在您的系统中,并且命令:
rocm-smi
应该显示您的硬件信息和统计数据:
rocm-smi command output
提示: 看看rocm-smi -h
命令,探索更多的功能和 OC 工具
张量流 Docker
让 ROCm + Tensorflow 后端工作的最快、最可靠的方法是使用 AMD 开发者提供的 docker 镜像。
安装 Docker CE
首先,需要安装 Docker。为此,请遵循 Ubuntu 系统的说明:
要在 Ubuntu 上开始使用 Docker CE,请确保您满足先决条件,然后安装 Docker。先决条件…
docs.docker.com](https://docs.docker.com/install/linux/docker-ce/ubuntu/)
提示 :为了避免插入sudo docker <command>
而不是docker <command>
,向非根用户提供访问权限是很有用的:将 Docker 作为非根用户管理。
拉 ROCm 张量流图像
现在该拉 AMD 开发者提供的 Tensorflow docker 了。
打开新的终端CTRL + ALT + T
并发布:
docker pull rocm/tensorflow
几分钟后,映像将被安装到您的系统中,准备就绪。
创造一个持久的空间
由于 docker 容器的短暂性,一旦 Docker 会话关闭,所有的修改和存储的文件都将随容器一起删除。
因此,在物理驱动器中创建一个永久空间来存储文件和笔记本是非常有用的。更简单的方法是创建一个文件夹,用 docker 容器初始化。为此,发出以下命令:
mkdir /home/$LOGNAME/tf_docker_share
该命令将创建一个名为tf_docker_share
的文件夹,用于存储和查看 docker 中创建的数据。
起始码头工人
现在,在新的容器会话中执行图像。只需发送以下命令:
docker run -i -t \
--network=host \
--device=/dev/kfd \
--device=/dev/dri \
--group-add video \
--cap-add=SYS_PTRACE \
--security-opt seccomp=unconfined \
--workdir=/tf_docker_share \
-v $HOME/tf_docker_share:/tf_docker_share rocm/tensorflow:latest /bin/bash
docker 正在目录/tf_docker_share
上执行,您应该看到类似于:
这意味着你现在正在 Tensorflow-ROCm 虚拟系统中运行。
安装 Jupyter
Jupyter 是一个非常有用的工具,用于神经网络的开发、调试和测试。不幸的是,它目前没有默认安装在由 ROCm 团队发布的 tensor flow-ROCm Docker image 上。因此需要手动安装 Jupyter。
为此,在 Tensorflow-ROCm 虚拟系统提示符下,
1.发出以下命令:
pip3 install jupyter
它会将 Jupyter 包安装到虚拟系统中。让这个终端开着。
2.打开一个新的端子CTRL + ALT + T
。
找到发出命令的CONTAINER ID
:
docker ps
应该会出现一个类似如下的表格:
Container ID on the left
第一列表示被执行容器的容器 ID 。复制它,因为下一步需要它。
3.是时候提交了,永久地写入图像的修改。从同一个终端,执行:
docker commit <container-id> rocm/tensorflow:<tag>
其中tag
值是任意名称,例如personal
。
4.要再次检查图像是否已正确生成,请从同一终端发出以下命令:
docker images
这应该会生成如下所示的表格:
值得注意的是,在本教程的剩余部分,我们将引用这个新生成的图像。
使用新的docker run
命令,看起来像:
docker run -i -t \
--network=host \
--device=/dev/kfd \
--device=/dev/dri \
--group-add video \
--cap-add=SYS_PTRACE \
--security-opt seccomp=unconfined \
--workdir=/tf_docker_share \
-v $HOME/tf_docker_share:/tf_docker_share rocm/tensorflow:<tag> /bin/bash
同样,tag
的值是任意的,例如personal
。
进入 Jupyter 笔记本电脑环境
我们终于可以进入木星的环境了。在其中,我们将使用 Tensorflow v1.12 作为后端,Keras 作为前端,创建第一个神经网络。
清洁
首先关闭所有先前执行的 Docker 容器。
- 检查已经打开的容器:
docker ps
2.关闭所有码头集装箱:
docker container stop <container-id1> <container-id2> ... <container-idn>
3.关闭所有已经打开的终端。
执行 Jupyter
让我们打开一个新的终端CTRL + ALT + T
:
- 运行一个新的 Docker 容器(
personal
标签将被默认使用):
docker run -i -t \
--network=host \
--device=/dev/kfd \
--device=/dev/dri \
--group-add video \
--cap-add=SYS_PTRACE \
--security-opt seccomp=unconfined \
--workdir=/tf_docker_share \
-v $HOME/tf_docker_share:/tf_docker_share rocm/tensorflow:personal /bin/bash
您应该登录 Tensorflow-ROCm docker 容器提示符。
Logged into docker container
2.执行 Jupyter 笔记本:
jupyter notebook --allow-root --port=8889
应该会出现一个新的浏览器窗口,如下所示:
Jupyter root directory
如果新标签没有自动出现,在浏览器上,返回到执行jupyter notebook
命令的终端。在底部,有一个链接(在上面按下CTRL + left mouse button
)然后,浏览器中的一个新标签会将你重定向到 Jupyter 根目录。
Typical Jupyter notebook output. The example link is on the bottom
用 Keras 训练神经网络
在本教程的最后一部分,我们将在 MNIST 数据集上训练一个简单的神经网络。我们将首先建立一个完全连接的神经网络。
全连接神经网络
让我们创建一个新的笔记本,从 Jupyter 根目录的右上角菜单中选择 Python3 。
The upper-right menu in Jupyter explorer
一个新的 Jupiter 笔记本应该会在新的浏览器选项卡中弹出。通过点击窗口左上角的Untitled
将其重命名为fc_network
。
Notebook renaming
让我们检查 Tensorflow 后端。在第一个单元格中插入:
import tensorflow as tf; print(tf.__version__)
然后按SHIFT + ENTER
执行。输出应该类似于:
Tensorflow V1.12.0
我们用的是 Tensorflow v1.12.0。
让我们导入一些有用的函数,接下来使用:
from tensorflow.keras.datasets import mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout
from tensorflow.keras.optimizers import RMSprop
from tensorflow.keras.utils import to_categorical
让我们设置批量大小、时期和类的数量。
batch_size = 128
num_classes = 10
epochs = 10
我们现在将下载并预处理输入,将它们加载到系统内存中。
# the data, split between train and test sets
(x_train, y_train), (x_test, y_test) = mnist.load_data()x_train = x_train.reshape(60000, 784)
x_test = x_test.reshape(10000, 784)
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255
print(x_train.shape[0], 'train samples')
print(x_test.shape[0], 'test samples')# convert class vectors to binary class matrices
y_train = to_categorical(y_train, num_classes)
y_test = to_categorical(y_test, num_classes)
是时候定义神经网络架构了:
model = Sequential()
model.add(Dense(512, activation='relu', input_shape=(784,)))
model.add(Dropout(0.2))
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.2))
model.add(Dense(num_classes, activation='softmax'))
我们将使用一个非常简单的双层全连接网络,每层有 512 个神经元。它还包括神经元连接 20%的下降概率,以防止过度拟合。
让我们来看看网络架构的一些见解:
model.summary()
Network architecture
尽管问题很简单,但我们有相当多的参数需要训练(大约 700.000),这也意味着相当大的计算功耗。卷积神经网络将解决降低计算复杂性的问题。
现在,编译模型:
model.compile(loss='categorical_crossentropy',
optimizer=RMSprop(),
metrics=['accuracy'])
并开始训练:
history = model.fit(x_train, y_train,
batch_size=batch_size,
epochs=epochs,
verbose=1,
validation_data=(x_test, y_test))
Training process
神经网络已经在单个 RX 480 上以相当高的 47us/step 进行了训练。相比之下,Nvidia Tesla K80 达到了 43us/step,但贵了 10 倍。
多 GPU 训练
作为一个额外的步骤,如果您的系统有多个 GPU,可以利用 Keras 功能,以减少培训时间,在不同的 GPU 之间分割批处理。
为此,首先需要通过声明一个环境变量来指定用于训练的 GPU 数量(将以下命令放在单个单元上并执行):
!export HIP_VISIBLE_DEVICES=0,1,...
从 0 到…的数字定义了用于训练的 GPU。如果您想禁用 GPU 加速,只需:
!export HIP_VISIBLE_DEVICES=-1
还需要添加multi_gpu_model
功能。
举个例子,如果你有 3 个 GPU,前面的代码会相应地修改。
结论
本教程到此结束。下一步将在 MNIST 数据集上测试卷积神经网络。比较单个和多个 GPU 的性能。
这里相关的是,AMD GPUs 在计算负载下表现相当好,而价格只是它的一小部分。GPU 市场正在快速变化,ROCm 为研究人员、工程师和初创公司提供了非常强大的开源工具,可以采用,从而降低了硬件设备的前期成本。
条乘 条乘
请随意评论这篇文章,以提高他的质量和效果。
从头开始训练卷积神经网络
一个为 CNN 派生反向传播并在 Python 中从头实现它的简单演练。
在这篇文章中,我们将深入探讨大多数卷积神经网络(CNN)介绍所缺乏的东西:如何训练 CNN ,包括推导梯度,从头实现反向投影*(仅使用 numpy ),并最终建立一个完整的训练管道!*
这篇文章假设你对 CNN 有基本的了解。我对 CNN 的介绍涵盖了你需要知道的一切,所以我强烈建议你先读一下。如果你来这里是因为你已经读过了,欢迎回来!
这篇文章的部分内容也假设了多变量微积分的基础知识。如果你想的话,你可以跳过这些章节,但是我建议即使你不理解所有的内容,也要阅读它们。当我们得到结果时,我们将逐步编写代码,即使是表面的理解也会有所帮助。
1.搭建舞台
系好安全带。是时候进入状态了。
我们将继续我对 CNN 的介绍。我们用 CNN 来解决 MNIST 手写数字分类问题:
Sample images from the MNIST dataset
我们的(简单的)CNN 由一个 Conv 层、一个 Max 池层和一个 Softmax 层组成。这是我们 CNN 的图表:
Our CNN takes a 28x28 grayscale MNIST image and outputs 10 probabilities, 1 for each digit.
我们写了 3 个类,每层一个:Conv3x3
、MaxPool
和Softmax
。每个类都实现了一个forward()
方法,我们用它来构建 CNN 的前向传递:
你可以在你的浏览器 中查看代码或者 运行 CNN。在 Github 上也有。
这是我们 CNN 现在的输出:
*MNIST CNN initialized!
[Step 100] Past 100 steps: Average Loss 2.302 | Accuracy: 11%
[Step 200] Past 100 steps: Average Loss 2.302 | Accuracy: 8%
[Step 300] Past 100 steps: Average Loss 2.302 | Accuracy: 3%
[Step 400] Past 100 steps: Average Loss 2.302 | Accuracy: 12%*
显然,我们想做到 10%以上的准确率…让我们给 CNN 一个教训。
2.培训概述
训练神经网络通常包括两个阶段:
- 前向阶段,输入完全通过网络。
- 反向阶段,其中梯度被反向传播(反向传播)并且权重被更新。
我们将遵循这种模式来训练我们的 CNN。我们还将使用两个主要的特定于实现的想法:
- 在向前阶段,每一层将缓存向后阶段所需的任何数据(如输入、中间值等)。这意味着任何反向阶段之前必须有相应的正向阶段。
- 在反向阶段,每一层将接收一个梯度,并且返回一个梯度。它将接收关于其输出* (∂L / ∂out)的损耗梯度,并返回关于其输入 (∂L / ∂in).)的损耗梯度*
这两个想法将有助于保持我们的培训实施干净和有组织。了解原因的最佳方式可能是查看代码。训练我们的 CNN 最终会是这样的:
看到那看起来多漂亮多干净了吗?现在想象建立一个有 50 层而不是 3 层的网络——拥有好的系统更有价值。
3.反向投影:Softmax
我们将从结尾开始,然后朝着开头前进,因为这就是反向传播的工作方式。首先,回忆一下交叉熵损失:
其中 p_c 是正确类别 c 的预测概率(换句话说,我们当前图像实际上是多少位)。
想要更长的解释?阅读我的 CNN 简介的 交叉熵损失 部分。
我们需要计算的第一件事是 Softmax 层的反向相位∂L / ∂out_s 的输入,其中 out_s 是 Softmax 层的输出:10 个概率的向量。这很简单,因为只有 p_i 出现在损耗方程中:
Reminder: c is the correct class.
这是你在上面看到的初始梯度:
我们几乎已经准备好实现我们的第一个反向阶段——我们只需要首先执行我们之前讨论过的正向阶段缓存:
我们在这里缓存了 3 个对实现后向阶段有用的东西:
- 在变平之前
input
的形状。** - 后的
input
我们把它压平。 - 总计,是传递给 softmax 激活的值。
这样一来,我们就可以开始推导反投影阶段的梯度了。我们已经导出了 Softmax 反向阶段的输入:∂L / ∂out_s。关于∂L / ∂out_s,我们可以利用的一个事实是只有正确的类* c 才是非零的。这意味着除了 out_s©之外,我们可以忽略任何东西!*
首先,让我们计算 out_s©相对于总数的梯度(传递给 softmax 激活的值)。设 t_i 为第 I 类的总和,那么我们可以写出 _s©为:
你应该从我的 CNN 教程的 Softmax 部分认出了上面的等式。
现在,考虑某个类 k,使得 k 不是 c,我们可以将 out_s©重写为:
并使用链式法则推导出:
记住,这是假设 k 不等于 c。现在让我们对 c 进行推导,这次使用商法则:
唷。这是整篇文章中最难的部分——从这里开始只会变得更容易!让我们开始实现它:
还记得∂L / ∂out_s 只对正确的类 c 是非零的吗?我们通过在d_L_d_out
中寻找非零梯度来开始寻找 c。一旦我们发现了这一点,我们就可以使用上面得出的结果来计算梯度∂out_s(i) / ∂t ( d_out_d_totals
):
我们继续吧。我们最终想要损失相对于权重、偏差和输入的梯度:
- 我们将使用权重梯度,∂L / ∂w,来更新我们的层的权重。
- 我们将使用偏差梯度∂L / ∂b 来更新图层的偏差。
- 我们将从我们的
backprop()
方法返回输入渐变,∂L / ∂input,这样下一层可以使用它。这是我们在培训概述部分谈到的回报梯度!
为了计算这 3 个损失梯度,我们首先需要导出另外 3 个结果:总数相对于权重、偏差和输入的梯度。这里的相关等式是:**
这些渐变很容易!
将所有东西放在一起:
将它写入代码就不那么简单了:
首先,我们预计算d_L_d_t
,因为我们将多次使用它。然后,我们计算每个梯度:
**d_L_d_w**
:我们需要 2d 数组来做矩阵乘法(@
),但是d_t_d_w
和d_L_d_t
是 1d 数组。 np.newaxis 让我们很容易地创建一个新的长度为 1 的轴,所以我们最终将维度为(input_len
,1)和(1,nodes
)的矩阵相乘。因此,d_L_d_w
的最终结果将具有 shape (input_len
,nodes
),与self.weights
相同!**d_L_d_b**
:这个很简单,因为d_t_d_b
是 1。**d_L_d_inputs**
:我们将维度为(input_len
,nodes
)和(nodes
,1)的矩阵相乘,得到长度为input_len
的结果。
试着做上面计算的小例子,特别是
*d_L_d_w*
和*d_L_d_inputs*
的矩阵乘法。这是理解为什么这段代码能正确计算梯度的最好方法。
随着所有的梯度计算,所有剩下的是实际训练 Softmax 层!我们将使用随机梯度下降(SGD)来更新权重和偏差,就像我们在我的神经网络简介中所做的那样,然后返回d_L_d_inputs
:
注意,我们添加了一个learn_rate
参数来控制我们更新权重的速度。此外,在返回d_L_d_inputs
之前,我们必须reshape()
,因为我们在向前传球时拉平了输入:
整形为last_input_shape
确保该层返回其输入的渐变,格式与输入最初提供给它的格式相同。
试驾:Softmax Backprop
我们已经完成了我们的第一个反向投影实现!让我们快速测试一下,看看它有什么好的。我们将从我的CNN 简介开始实现一个train()
方法:
运行此命令会得到类似于以下内容的结果:
*MNIST CNN initialized!
[Step 100] Past 100 steps: Average Loss 2.239 | Accuracy: 18%
[Step 200] Past 100 steps: Average Loss 2.140 | Accuracy: 32%
[Step 300] Past 100 steps: Average Loss 1.998 | Accuracy: 48%
[Step 400] Past 100 steps: Average Loss 1.861 | Accuracy: 59%
[Step 500] Past 100 steps: Average Loss 1.789 | Accuracy: 56%
[Step 600] Past 100 steps: Average Loss 1.809 | Accuracy: 48%
[Step 700] Past 100 steps: Average Loss 1.718 | Accuracy: 63%
[Step 800] Past 100 steps: Average Loss 1.588 | Accuracy: 69%
[Step 900] Past 100 steps: Average Loss 1.509 | Accuracy: 71%
[Step 1000] Past 100 steps: Average Loss 1.481 | Accuracy: 70%*
损失在下降,准确性在上升——我们的 CNN 已经在学习了!
4.反向推进:最大池化
Max Pooling 层不能被训练,因为它实际上没有任何权重,但是我们仍然需要为它实现一个方法来计算梯度。我们将从再次添加前向阶段缓存开始。这次我们需要缓存的只是输入:
在向前传递期间,最大池层通过在 2x2 块上选取最大值来获取输入体积并将其宽度和高度尺寸减半。反向过程则相反:我们将通过将每个梯度值分配给来加倍损失梯度的宽度和高度*,其中原始最大值是其对应的 2x2 块中的**。*
这里有一个例子。考虑最大池层的这个前进阶段:
An example forward phase that transforms a 4x4 input to a 2x2 output
同一层的反向阶段将如下所示:
An example backward phase that transforms a 2x2 gradient to a 4x4 gradient
每个渐变值都被分配到原始最大值所在的位置,其他值为零。
为什么最大池层的反向阶段是这样工作的?思考一下∂L / ∂inputs 直觉上应该是什么样子。不是其 2x2 块中最大值的输入像素对损失的边际影响为零,因为稍微改变该值根本不会改变输出!换句话说,对于非最大像素,∂L / ∂inputs = 0。另一方面,为最大值的输入像素会将其值传递给输出,因此∂output / ∂input = 1,意味着∂L / ∂input = ∂L / ∂output.
我们可以使用我在 CNN 简介中写的助手方法很快实现这一点。我将再次把它包括在内作为提醒:
对于每个过滤器中每个 2×2 图像区域中的每个像素,如果它是正向传递期间的最大值,我们将梯度从d_L_d_out
复制到d_L_d_input
。
就是这样!我们的最后一层。
5.背景:Conv
我们终于来了:通过 Conv 层反向传播是训练 CNN 的核心。前向阶段缓存很简单:
提醒一下我们的实现:为了简单起见, 我们假设 conv 层的输入是一个 2d 数组 。这只对我们有用,因为我们把它作为网络的第一层。如果我们正在构建一个需要多次使用
*Conv3x3*
的更大的网络,我们必须使输入成为一个 3d 数组。
我们主要对 conv 图层中滤镜的损耗梯度感兴趣,因为我们需要它来更新我们的滤镜权重。我们已经有了∂L / ∂out 的 conv 层,所以我们只需要∂out / ∂filters。为了计算,我们问自己:改变过滤器的重量会如何影响 conv 层的输出?
事实是改变任何滤波器权重都会影响该滤波器的整个输出图像,因为在卷积期间每个输出像素使用每个像素权重。为了更容易理解,让我们一次只考虑一个输出像素:修改一个滤镜会如何改变一个特定输出像素的输出?
这里有一个超级简单的例子来帮助思考这个问题:
A 3x3 image (left) convolved with a 3x3 filter (middle) to produce a 1x1 output (right)
我们将一个 3×3 图像与一个全零 3×3 滤波器进行卷积,产生 1×1 输出。如果我们将中央过滤器的重量增加 1 会怎么样?输出将增加中心图像值 80:
类似地,将任何其他过滤器权重增加 1 将使输出增加相应图像像素的值!这表明特定输出像素相对于特定滤波器权重的导数就是相应的图像像素值。计算证实了这一点:
我们可以将所有这些放在一起,找出特定滤波器权重的损耗梯度:
我们已经准备好为我们的 conv 层实现反向投影了!
我们通过迭代每个图像区域/滤波器并递增地构建损失梯度来应用我们导出的方程。一旦我们涵盖了所有内容,我们就像以前一样使用 SGD 更新self.filters
。请注意解释我们为什么要返回的注释——输入损耗梯度的推导与我们刚才所做的非常相似,留给读者作为练习:)。
就这样,我们结束了!我们已经通过我们的 CNN 实现了一个完整的向后传递。是时候测试一下了…
6.训练 CNN
我们将训练 CNN 几个时期,在训练过程中跟踪它的进展,然后在单独的测试集上测试它。以下是完整的代码:
运行代码的输出示例:
*MNIST CNN initialized!
--- Epoch 1 ---
[Step 100] Past 100 steps: Average Loss 2.254 | Accuracy: 18%
[Step 200] Past 100 steps: Average Loss 2.167 | Accuracy: 30%
[Step 300] Past 100 steps: Average Loss 1.676 | Accuracy: 52%
[Step 400] Past 100 steps: Average Loss 1.212 | Accuracy: 63%
[Step 500] Past 100 steps: Average Loss 0.949 | Accuracy: 72%
[Step 600] Past 100 steps: Average Loss 0.848 | Accuracy: 74%
[Step 700] Past 100 steps: Average Loss 0.954 | Accuracy: 68%
[Step 800] Past 100 steps: Average Loss 0.671 | Accuracy: 81%
[Step 900] Past 100 steps: Average Loss 0.923 | Accuracy: 67%
[Step 1000] Past 100 steps: Average Loss 0.571 | Accuracy: 83%
--- Epoch 2 ---
[Step 100] Past 100 steps: Average Loss 0.447 | Accuracy: 89%
[Step 200] Past 100 steps: Average Loss 0.401 | Accuracy: 86%
[Step 300] Past 100 steps: Average Loss 0.608 | Accuracy: 81%
[Step 400] Past 100 steps: Average Loss 0.511 | Accuracy: 83%
[Step 500] Past 100 steps: Average Loss 0.584 | Accuracy: 89%
[Step 600] Past 100 steps: Average Loss 0.782 | Accuracy: 72%
[Step 700] Past 100 steps: Average Loss 0.397 | Accuracy: 84%
[Step 800] Past 100 steps: Average Loss 0.560 | Accuracy: 80%
[Step 900] Past 100 steps: Average Loss 0.356 | Accuracy: 92%
[Step 1000] Past 100 steps: Average Loss 0.576 | Accuracy: 85%
--- Epoch 3 ---
[Step 100] Past 100 steps: Average Loss 0.367 | Accuracy: 89%
[Step 200] Past 100 steps: Average Loss 0.370 | Accuracy: 89%
[Step 300] Past 100 steps: Average Loss 0.464 | Accuracy: 84%
[Step 400] Past 100 steps: Average Loss 0.254 | Accuracy: 95%
[Step 500] Past 100 steps: Average Loss 0.366 | Accuracy: 89%
[Step 600] Past 100 steps: Average Loss 0.493 | Accuracy: 89%
[Step 700] Past 100 steps: Average Loss 0.390 | Accuracy: 91%
[Step 800] Past 100 steps: Average Loss 0.459 | Accuracy: 87%
[Step 900] Past 100 steps: Average Loss 0.316 | Accuracy: 92%
[Step 1000] Past 100 steps: Average Loss 0.460 | Accuracy: 87%
--- Testing the CNN ---
Test Loss: 0.5979384893783474
Test Accuracy: 0.78*
我们的代码有效!仅在 3000 个训练步骤中,我们就从一个损失 2.3、准确率 10%的模型,变成了损失 0.6、准确率 78%的模型。
*想自己尝试或修改这段代码吗? 在浏览器中运行本 CNN**。*在 Github 上也有。
为了节省时间,我们在这个例子中只使用了整个 MNIST 数据集的一个子集——我们的 CNN 实现不是特别快。如果我们真的想训练一个 MNIST CNN,我们会使用一个 ML 库,比如 Keras 。为了展示我们 CNN 的强大,我使用 Keras 来实现和训练我们刚刚从头构建的完全相同的* CNN:*
在完整的* MNIST 数据集(60k 训练图像)上运行该代码会得到如下结果:*
*Epoch 1
loss: 0.2433 - acc: 0.9276 - val_loss: 0.1176 - val_acc: 0.9634
Epoch 2
loss: 0.1184 - acc: 0.9648 - val_loss: 0.0936 - val_acc: 0.9721
Epoch 3
loss: 0.0930 - acc: 0.9721 - val_loss: 0.0778 - val_acc: 0.9744*
我们用这个简单的 CNN 实现了 97.4% 的测试准确率!有了更好的 CNN 架构,我们可以进一步改进——在这个官方的 Keras MNIST CNN 示例中,他们在 12 个时期后达到了 99.25% 的测试准确度。这个真的准确度好。
这篇帖子的所有代码都可以在Github上获得。
现在怎么办?
我们完了!在这篇文章中,我们做了一个完整的演练如何训练一个卷积神经网络。然而,这仅仅是开始。您还可以做更多的事情:
- 使用适当的 ML 库,如 Tensorflow 、 Keras 或 PyTorch ,尝试更大/更好的 CNN。
- 了解如何将批处理规范化用于 CNN。
- 了解数据扩充如何用于改善图像训练集。
- 阅读 ImageNet 项目及其著名的计算机视觉竞赛 ImageNet 大规模视觉识别挑战赛( ILSVRC )。
最初发表于【https://victorzhou.com】*。*
用 PyTorch 训练神经网络
“一知半解是一件危险的事情;**(亚历山大·波普)**深饮或尝不出皮耶里的春天
Human brain vs Neural network (image source here)
所以在之前的文章中,我们已经建立了一个非常简单和“幼稚”的神经网络,它不知道将输入映射到输出的函数。为了使它更加智能,我们将通过向它显示“真实数据”的例子来训练网络,然后调整网络参数(权重和偏差)。简而言之,我们在调整模型参数(权重和偏差)的同时,通过迭代训练数据集来提高精确度。
为了找到这些参数,我们需要知道我们的网络对真实输出的预测有多差。为此,我们将计算cost
,也称为loss function
。
成本
Cost
或loss function
是我们预测误差的度量。通过相对于网络参数最小化loss
,我们可以找到loss
最小的状态,并且网络能够以高精度预测正确的标签。我们使用一个叫做gradient descent
的过程找到这个minimum loss
。在处检查不同种类的cost
功能
梯度下降
梯度下降需要成本函数。我们需要这个cost
函数,因为我们需要将其最小化,以便获得高预测精度。GD 的全部意义在于最小化cost
函数。该算法的目标是到达最低值error value
的过程。为了在成本函数中得到最低的error value
(相对于一个权重),我们需要调整模型的参数。那么,我们需要调整参数到什么程度呢?我们可以使用calculus
找到它。使用calculus
我们知道函数的slope
是关于值的函数的derivative
。gradient
是损失函数的斜率,指向变化最快的方向。
在上反向传播
Gradient Descent
对于单层网络来说实施起来很简单,但是对于多层网络来说,实施起来更复杂和更深入。多层网络的训练是通过反向传播完成的,这实际上是微积分中链式法则的应用。如果我们把一个两层网络转换成一个图形表示,这是最容易理解的。
image source: udacity course material
向前传球
在向前传递中,数据和操作是自下而上的。
步骤 1: 我们通过线性变换**𝐿1**
将输入**𝑥**
传递给权重**𝑊1**
和偏差**𝑏1**
****第二步:然后输出经过 sigmoid 运算**𝑆**
和另一个线性变换𝐿2
第三步:最后我们计算ℓ.的损失
我们用这个损失来衡量网络的预测有多糟糕。目标是调整weights
和biases
以最小化损失。
偶数道次
为了用梯度下降来训练权重,我们通过网络反向传播损失的梯度。
每个操作在输入和输出之间都有一些梯度。
当我们向后发送梯度时,我们将引入的梯度与操作的梯度相乘。
从数学上来说,这实际上就是用链式法则计算损耗相对于重量的梯度。
我们使用这个梯度和一些学习速率α来更新我们的权重。
学习率α
被设置为使得权重更新步长足够小,使得迭代方法稳定在最小值。
点击了解更多关于 backprop 的信息
PyTorch 的损失
PyTorch 提供了损失,如交叉熵损失 nn.CrossEntropyLoss
。对于像 MNIST 这样的分类问题,我们使用 softmax 函数来预测分类概率。
为了计算loss
,我们首先定义criterion
,然后传入网络的output
,并修正标签。
nn.CrossEntropyLoss
标准将nn.LogSoftmax()
和nn.NLLLoss()
组合在一个类中。输入应该包含每个类的分数。
也就是说,我们需要将网络的原始输出传递给损耗,而不是 softmax 函数的输出。这种原始输出通常被称为*logits*
或*scores*
我们使用logits
是因为softmax
给出的概率通常非常接近于zero
或one
,但浮点数不能准确地表示接近 0 或 1 的值(在此阅读更多信息)。通常最好避免用概率进行计算,通常我们使用对数概率。
亲笔签名
Torch 提供了一个名为autograd
的模块来自动计算张量的。这是一种计算导数的引擎。它记录在梯度激活张量上执行的所有操作的图形,并创建一个非循环图形,称为动态计算图形。该图的叶是输入张量,而根是输出张量。****
Autograd
的工作原理是跟踪在张量上执行的操作,然后通过这些操作向后前进,计算沿途的梯度。**
为了确保 PyTorch 跟踪张量上的操作并计算梯度,我们需要设置requires_grad = True
。我们可以用torch.no_grad()
关闭代码块的渐变。
训练网络
最后,我们需要一个optimizer
来更新梯度权重。我们从 PyTorch 的[optim](https://pytorch.org/docs/stable/optim.html)
包中得到这些。例如,我们可以通过optim.SGD
使用随机梯度下降。
训练神经网络的过程:
- 通过网络向前传递
- 使用网络输出来计算损耗
- 用
loss.backward()
对网络进行反向遍历,以计算梯度 - 使用优化器更新权重
我们将准备用于训练的数据:
**import torch
from torch import nn
import torch.nn.functional as F
from torchvision import datasets, transforms# Define a transform to normalize the data
transform = transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,)),
])# Download and load the training data
trainset = datasets.MNIST('~/.pytorch/MNIST_data/', download=True, train=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)**
用真实数据训练:
在一些术语中,一次遍历整个数据集被称为*epoch*
。所以这里我们将循环通过trainloader
来获得我们的训练批次。对于每一批,我们将进行一次训练,计算损失,进行一次反向训练,并更新权重。
**model = nn.Sequential(nn.Linear(784, 128),
nn.ReLU(),
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, 10),
nn.LogSoftmax(dim=1))# Define the loss
criterion = nn.NLLLoss()# Optimizers require the parameters to optimize and a learning rate
optimizer = optim.SGD(model.parameters(), lr=0.003)epochs = 5
for e in range(epochs):
running_loss = 0
for images, labels in trainloader:
# Flatten MNIST images into a 784 long vector
images = images.view(images.shape[0], -1)
# Training pass
optimizer.zero_grad()
output = model(images)
loss = criterion(output, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
else:
print(f"Training loss: {running_loss/len(trainloader)}")**
N 注:
optimizer.zero_grad()
:当我们用相同的参数进行多次反向传递时,梯度就会累积。这意味着我们需要在每次训练过程中将梯度归零,否则我们将保留以前训练批次的梯度。
看吧!训练损失在每一个时期都在下降。
随着网络的训练,我们可以检查它的预测。
Prediction result after training
现在我们的网络是辉煌的!它能准确预测我们图像中的数字。是不是很酷?
声明:本文基于我在 facebook-udacity 奖学金挑战项目中的学习。
用迁移学习训练情绪检测器
在这篇博文中,我们将讨论如何使用预先训练的计算机视觉模型、迁移学习以及使用谷歌图像创建自定义数据集的巧妙方法来快速创建情绪检测器。
注意:特定任务的代码片段包含在本文中,但是要重新创建结果,你应该参考代码库。
Figure 1. Applying the trained emotion detection model on the cast of Friends
让我们得到一些数据
在任何机器学习任务中,要做的第一件事就是收集数据。我们需要的是成千上万张带有面部表情标签的图像。公开的 FER 数据集 [1]是一个很好的起点,有 28,709 张带标签的图片。我们将使用他们标签的子集作为我们的目标情绪:
- 愤怒的
- 厌恶的
- 幸福的
- 悲哀的
- 惊讶的
但是,由于这些图像的分辨率只有 48 x 48,因此最好也有一个具有更丰富功能的数据集。为此,我们将使用Google _ Images _ downloadpython 包从 Google Images 中查询和抓取数据。
这种方法的好处是:( a)它从“野外”检索成千上万的图像,( b)我们可以使用查询中的关键字自动标记图像。
当查询谷歌图片时,通常你在结果列表中走得越远,它们就变得越不相关。这意味着我们应该为每次查询返回的图像数量设定一个限制。
为了增加图像的数量,可以使用同义词对每种情感执行几个查询。例如,除了“快乐”,我们还可以查询“微笑”、“愉快”和“兴高采烈”。
大部分的查询应该被结构化为“人脸”。事实证明,如果在查询中不包含“人类”,那么大部分图片都是表情符号😊
下面的代码片段显示了我们如何配置图像抓取器并检索最多 1000 个“快乐”图像。
这里需要注意一些事情:
- 要使用这个库,必须下载 chrome 驱动程序。详见本页。
- Google Images 限制了返回的项目数量(通常少于 1000 个)
太好了,我们现在有两个不同来源的面部表情标签了!
寻找面孔
在我们训练情绪检测器之前,我们首先必须能够定位每张图像中的人脸。由于目标是从面部表情中对情绪进行分类,因此只保留我们找到的面部并丢弃所有其他(大部分不相关)特征是有意义的。
幸运的是,有一些开源库提供了预先训练好的人脸检测模型。对于这个项目,我们将使用 facenet-pytorch 库,它提供了一个在 VGGFace2 和 CASIA-Webface 数据集上预先训练的多任务 CNN [2]。
下面的代码片段显示了我们如何加载预训练的 MTCNN 模型,并使用它来为图像中的每个人脸找到一个边界框。
这有两个应用:
- 为了训练,我们将把我们的图像过滤成只有一张脸的图像(查询到的有多张脸的图像可能与目标情感不太相关),然后使用得到的边界框从图像中裁剪出该脸。
- 在情感分类器被训练后,人脸检测模型将被用于从一幅图像中提取所有人脸,并将它们分别馈送给模型(例如,参见图 1 )。
一旦人脸从每张图像中分离出来,它就会被调整到标准形状,然后转换成单通道灰度图像(见图 2 )。后一步是确保模型将专注于实际的面部表情,而不是学习颜色可能带来的任何偏见。
例如,“sad”查询更可能包含已经是黑白的图像。强制所有图像为灰度将消除这种偏见。
Figure 2. The first step crops the face from the raw image using an open-source, pre-trained face detection model. The second step resizes the image and transforms it to grayscale.
准备基础模型
由于我们没有大型数据集,我们应该避免从头开始训练我们的分类器。正如大多数计算机视觉转移学习任务中常见的那样,我们将继续对 ImageNet 上预先训练的模型进行微调,ImageNet 是一个数据集,包含数百万张来自一千个不同标签的图像。
这里的目标是通过给它的参数一个好的起点,使我们的模型更容易训练。它可以利用从 ImageNet 学到的现有特征提取器,并调整它们来完成这项新任务。
对 ImageNet 预训练模型的一个警告是,它们期望它们的输入是 RGB 图像。我们可以通过将图像转换为三通道灰度图像来回避这个问题。虽然这可行,但不是最佳的,因为我们将模型外推至新的特征空间(ImageNet 不包含灰度图像)。
相反,让我们用一个随机初始化的单通道卷积层替换模型中的第一个(三通道)卷积层。我们将使用 Resnet-50 [3]作为我们的模型。
下一步是微调这个模型,以便在给定灰度输入时,它使用彩色输入模拟原始模型的输出。图 3 中概述了培训流程。
Figure 3. Fine-tuning Resnet-50 using grayscale versions of ImageNet
这里,RGB 图像作为输入被馈送到预训练的 Resnet-50 模型。该图像还被转换为灰度,并作为输入馈送到修改后的“Gray Resnet-50”,其中第一卷积层现在接受单通道输入。
然后,两个输出嵌入通过 L2 损失函数。尽管有大量的计算在进行,反向传播步骤中唯一更新的参数是“Gray Resnet”模型中的第一个单通道卷积层。
这个过程是无人监督的,这意味着我们可以在任何数据集上微调模型,而不用担心找到标签。我最终使用同一个谷歌图片抓取器为每个标签下载了 10 张图片。所有 1000 个 ImageNet 标签见该资源。
图 4 在两幅不同的图像上比较原始模型和修改后的版本,每幅图像都有彩色和灰度版本。对于有金翅雀的那一对,“灰色 Resnet”模型对这只鸟是金翅雀的信心较低,因为它看不到这只鸟与众不同的黄色羽毛。
对于有疣猪的那一对,“灰色 Resnet”模型比原始模型对其预测有更高的信心。作为一种推测,这可能是因为原始模型专注于皮毛的颜色(疣猪和公猪有相似的棕色皮毛),而“灰色 Resnet”可以专注于结构差异。
Figure 4. The top-3 predicted labels of the original Resnet and the “Gray Resnet” model for two pairs of images.
这一微调步骤的目标是建立一个预训练模型,该模型较少关注颜色,而更多关注形状和结构。
面部训练
现在我们有了基本模型,让我们应用迁移学习对情绪进行分类!这将涉及我们的预训练“灰色 Resnet”模型的两个阶段的微调。
第一阶段涉及对 FER 数据集的微调。这将作为在“野生”数据上训练模型之前的中间步骤。由于影像的分辨率较低(48 x 48),我们希望模型能够从 FER 数据集中识别简单的要素。
第二阶段从第一阶段获取模型,然后根据“野生”数据进一步微调它。这里,模型将扩展先前的表示,以从“野生”数据中存在的更丰富的特征中学习。
在实践中,我发现,当所有的参数都允许训练时,FER 微调模型达到了最佳的验证分数。另一方面,当除了最后 11 层之外的所有层都被冻结时,对“野生”数据进行微调的模型获得了最好的分数(参见下面的代码,了解如何使用初始化的 Resnet-50 来实现这一点的示例)。
在第二阶段不允许训练整个模型的原因是因为在“野生”数据集中的例子较少。考虑到其大容量,如果在该阶段允许训练其所有参数,则该模型将更容易过度拟合。
下面的图 5 显示了 80:20 列车测试分割的 FER 数据的验证结果。该模型的总体准确度为 87.2%,并且似乎该模型发现“高兴”和“惊讶”情绪最成功。另一方面,该模型在区分“愤怒”和“悲伤”方面最为费力。也许这是因为这两种情绪通常都包含皱眉。
Figure 5. Evaluation metrics on the FER images
下面的图 6 显示了 70:30 训练测试分割的“野生”谷歌图像数据的验证结果。
Figure 6. Evaluation metrics on the scraped Google Images
乍看之下,该模型的性能似乎有所下降,因为整体精度已降至 63.9%。这是因为我们模型的特征空间已经改变了。这个“野生”数据集中的图像更加多样和嘈杂,使得它们比科学的 FER 基准更难分类。事实上,直接应用来自 FER 数据(第一阶段)的模型,而不对“野生”数据进行微调,其准确度低于 51%。
应用训练好的模型
现在模型已经训练好了,让我们找点乐子,试着把它应用到不同的图像上。
在**图 1 中,**情绪检测器被应用到《老友记》的演员阵容中(为了方便起见,下面复制了这张图片)。这张图片突出了模型如何在“快乐”和“悲伤”的面孔之间做出决定。特别是,有更多曲线和露出牙齿的笑容比没有曲线和牙齿的笑容更能让人对“开心”产生更高的信心。
Figure 1. Applying the trained emotion detection model on the cast of Friends
在下面的图 7 中,该模型被应用于哈利波特角色。这里我们有五种目标情绪的例子。
Figure 7. Detecting emotions expressed by Harry Potter characters
我们甚至可以扩展这一模型,通过找到人脸并对每一帧中的情绪进行分类,来检测视频中的情绪(见下面的图 8 )。这个模型似乎把我做出的悲伤表情和惊讶混淆了,然而,这可能是因为我不擅长做出悲伤表情:)
Figure 8. Applying the emotion detector on a video
不足为奇的是,当目标情绪被强烈表现出来,并与更复杂/混合的面部表情斗争时,该模型似乎工作得很好。这可能是因为谷歌图片更有可能返回强烈表达的情感,因为它们与查询更相关。
结束语
我开始这个项目是为了通过利用大量现有的开源工作来创造一些有趣的东西。这样做让我能够快速旋转项目的重要部分,这反过来又让我能够专注于学习这些部分是如何工作的,以及我可以将它们粘合在一起的最佳方式。
请注意,根据从 Google Images scraper 返回的数据量,花一些时间手动检查结果可能是个好主意。例如,在我的例子中,返回了 5000 多张图片,我花了大约 30 分钟浏览数据并删除任何错误标记/无意义的图片。
改进项目的一些潜在方法包括:
- 使用预训练的面部检测模型作为基础模型(而不是 ImageNet)
- 对微调步骤进行更多的实验(例如,应该允许训练多少层?)
- 使用与面部图像串联的面部标志数据/模型
代码库:【https://github.com/martin-chobanyan/emotion】T2
参考
[1] I. J. Goodfellow,D. Erhan,P. L. Carrier,A .库维尔,M. Mirza,B. Hamner,W. Cukierski,Y. Tang,D. Thaler,D.-H. Lee 等人,“表征学习中的挑战:关于三次机器学习竞赛的报告”,国际神经信息处理会议。施普林格,2013
[2]蔡、、、和杨百奇.“基于多任务级联卷积网络的联合头部姿态估计用于人脸对齐.” 2018 年第 24 届国际模式识别大会(ICPR) ,2018
[3]何、、、、任、。“图像识别的深度剩余学习.” 2016 年 IEEE 计算机视觉与模式识别大会(CVPR) ,2016
浏览器中的机器学习:为自定义图像分类训练和服务 Mobilenet 模型
Photo by Louis Hansel on Unsplash
Tensorflow.js
有几种方法可以微调深度学习模型,但在 web 浏览器上使用 WebGL 加速是我们不久前经历的事情,引入了 Tensorflow.js 。我将使用 Tensorflow.js 与 Angular 一起构建一个 Web 应用程序,该应用程序在 Mobilenet 和 Kaggle 数据集的帮助下训练卷积神经网络来检测疟疾感染的细胞,该数据集包含 27.558 个感染和未感染的细胞图像。
演示 WebApp
访问现场演示应用程序查看运行中的代码。该应用程序在 Google Chrome 浏览器中运行没有任何问题。
访问我的 GitHub repositor y 获取该项目的完整代码。您可以在此存储库的资产文件夹中找到图像,以及将图像加载到浏览器所需的 CSV 文件。您应该解压缩这些图像,并将它们放入您正在处理的 Angular 项目的 assets 文件夹中。
作为基础模型的 Mobilenet
正如我上面提到的,我将使用“mobilenet”作为自定义图像分类器的基础模型。预训练的“mobilenet”模型,兼容 tensorflow.js,相对较小(20MB),可以直接从 Google API 存储文件夹下载。
未感染和寄生细胞,这是我想用我们的定制模型分类的两个类别,而原始的“mobilenet”模型被训练来分类 1000 个不同的对象。
我将使用除最后 5 层之外的所有“mobilenet”层,并在这个截断模型的顶部添加一个具有 2 个单元和 softmax 激活的密集层,以使修改后的模型适合我们的分类任务。
我不会训练所有的层,因为这将需要大量的时间和计算能力。这对于我们的情况也是不必要的,因为我们使用预先训练的模型,该模型已经学习了许多表示。相反,我将冻结大多数层,只保留最后 2 层可训练。
疟疾细胞图像的预处理
在开始编码之前,我们必须考虑如何在训练期间将图像输入到我们的定制模型中。mobilenet 模型需要特定的图像大小(224x224x3)和图像预处理操作,我们必须对图像应用相同的预处理,然后才能将它们提供给我们的模型。此外,为了不使我们的模型偏向一个类,我们必须在训练时期为每个类提供相同数量的图像。
Photo by Paweł Czerwiński on Unsplash
Angular WebApp 正在初始化
在我们清楚了模型和数据集之后,是时候使用 Angular 命令行界面来初始化 Angular web 应用程序了。
npm install -g @angular/cli
ng new TFJS-CustomImageClassification
cd TFJS-CustomImageClassification
然后我会使用’ nmp '包管理器来安装 tensorflow.js 库。为了完善 web 应用程序,我将使用角状材料类。
TFJS-CustomImageClassification **npm install @tensorflow/tfjs --save** TFJS-CustomImageClassification **ng add @angular/material**
生成基于自定义 Mobilenet 的模型
首先,让我们开始编码,生成一个函数来修改预训练的模型,以便我们可以使用这个修改后的模型来完成我们的特定任务,即对感染疟疾的图像进行分类。由于我不想从头开始训练模型,我将冻结所有层,除了我需要重新训练微调模型的层。
//-------------------------------------------------------------
// modifies the pre-trained mobilenet to detect malaria infected
// cells, freezes layers to train only the last couple of layers
//-------------------------------------------------------------
async getModifiedMobilenet()
{
*const* trainableLayers= ['denseModified','conv_pw_13_bn','conv_pw_13','conv_dw_13_bn','conv _dw_13'];
*const* mobilenet = await
tf.loadLayersModel('https://storage.googleapis.com/tfjs- models/tfjs/mobilenet_v1_0.25_224/model.json');*console*.log('Mobilenet model is loaded')
*const* x=mobilenet.getLayer('global_average_pooling2d_1');
*const* predictions= <tf.SymbolicTensor> tf.layers.dense({units: 2, activation: 'softmax',name: 'denseModified'}).apply(x.output);
*let* mobilenetModified = tf.model({inputs: mobilenet.input, outputs: predictions, name: 'modelModified' });
*console*.log('Mobilenet model is modified')mobilenetModified =
this.freezeModelLayers(trainableLayers,mobilenetModified)
*console*.log('ModifiedMobilenet model layers are freezed')mobilenetModified.compile({loss: categoricalCrossentropy, optimizer: tf.train.adam(1e-3), metrics: ['accuracy','crossentropy']});mobilenet.dispose();
x.dispose();
return mobilenetModified
}
//-------------------------------------------------------------
// freezes mobilenet layers to make them untrainable
// just keeps final layers trainable with argument trainableLayers
//-------------------------------------------------------------freezeModelLayers(*trainableLayers*,*mobilenetModified*)
{
for (*const* layer of mobilenetModified.layers)
{
layer.trainable = false;
for (*const* tobeTrained of trainableLayers)
{
if (layer.name.indexOf(tobeTrained) === 0)
{
layer.trainable = true;
break;
}
}
}
return mobilenetModified;
}
Photo by Toa Heftiba on Unsplash
准备培训数据
为了训练该模型,我们需要 224×224×3 形状张量和另一个包含 1,0 值的 1 维张量中的未感染和感染细胞图像来指示图像的类别。我要做的是读取包含图像 src 和类信息的 CSV 文件,然后生成 HTMLImageElement 以在浏览器中查看它们。Capture()函数然后将获取图像 id,以便从浏览器上的图像生成所需的图像张量。请注意,我们必须预处理图像张量,因为 mobilenet 需要标准化的输入。老实说,我在这里使用的数据管道并不是正确的方式,因为我是一次将整块图像数据加载到内存中。使用 tf.fitDataset 并在需要时迭代使用内存会好得多。
//-------------------------------------------------------------
// this function generate input and target tensors for the training
// input tensor is produced from 224x224x3 image in HTMLImageElement
// target tensor shape2 is produced from the class definition
//-------------------------------------------------------------
generateData (*trainData*,*batchSize*)
{
*const* imageTensors = [];
*const* targetTensors = [];*let* allTextLines = this.csvContent.split(/\r|\n|\r/);
*const* csvSeparator = ',';
*const* csvSeparator_2 = '.';for ( *let* i = 0; i < batchSize; i++)
{
// split content based on comma
*const* cols: *string*[] = allTextLines[i].split(csvSeparator);
*console*.log(cols[0].split(csvSeparator_2)[0])if (cols[0].split(csvSeparator_2)[1]=="png")
{
*const* imageTensor = this.capture(i);
*let* targetTensor =tf.tensor1d([this.label_x1[i],this.label_x2[i]]);
targetTensor.print();
imageTensors.push(imageTensor);
targetTensors.push(targetTensor);
}
}
*const* images = tf.stack(imageTensors);
*const* targets = tf.stack(targetTensors);
return {images, targets};
}
//-------------------------------------------------------------
// converts images in HTMLImageElement into the tensors
// takes Image Id in HTML as argument
//-------------------------------------------------------------
capture(*imgId*)
{
// Reads the image as a Tensor from the <image> element.
this.picture = <HTMLImageElement> document.getElementById(imgId);
*const* trainImage = tf.browser.fromPixels(this.picture);
// Normalize the image between -1 and 1\. The image comes in between 0-255, so we divide by 127 and subtract 1.
*const* trainim = trainImage.toFloat().div(tf.scalar(127)).sub(tf.scalar(1));
return trainim;
}
Cell Images listed in Web App
微调模型
当我们准备好数据和模型时,现在是时候对模型进行微调,以便它可以对感染疟疾的细胞图像进行分类。为了减少训练过程的时间,我将总共使用 120 幅图像,5 个时期的训练过程,每个时期包含 24 幅图像。损失函数为’ 类别交叉熵 ’ 和**'一个** 坝优化器 ’ 用于学习率值相对较小的训练。我使用 Keras 提供的“on batchend**”**回调函数将每个历元的训练指标写入控制台。一旦训练完成,我们必须释放张量来释放内存。最后一步,我们将把训练好的模型保存到我们的本地存储器中,以便以后用于推理。
async fineTuneModifiedModel(*model*,*images*,*targets*)
{
*function* onBatchEnd(*batch*, *logs*)
{
*console*.log('Accuracy', logs.acc);
*console*.log('CrossEntropy', logs.ce);
*console*.log('All', logs);
}
*console*.log('Finetuning the model...');
await model.fit(images, targets,
{
epochs: 5,
batchSize: 24,
validationSplit: 0.2,
callbacks: {onBatchEnd}
}).then(*info* *=>* {
*console*.log
*console*.log('Final accuracy', info.history.acc);
*console*.log('Cross entropy', info.ce);
*console*.log('All', info);
*console*.log('All', info.history['acc'][0]);
for ( *let* k = 0; k < 5; k++)
{
this.traningMetrics.push({acc: 0, ce: 0 , loss: 0});
this.traningMetrics[k].acc=info.history['acc'][k];
this.traningMetrics[k].ce=info.history['ce'][k];
this.traningMetrics[k].loss=info.history['loss'][k];
}
images.dispose();
targets.dispose();
model.dispose();
});
}
Photo by Kym MacKinnon on Unsplash
完整代码
访问 my GitHub repositor y 获取该项目的完整代码。您可以在这个存储库的 assets 文件夹中找到图像,以及将图像加载到浏览器所需的 CSV 文件。您应该解压缩这些图像,并将它们放入您正在处理的 Angular 项目的 assets 文件夹中。
演示 WebApp
访问现场演示应用查看运行中的代码。该应用程序在 Google Chrome 浏览器中运行没有任何问题。
训练数据,是什么?谁在做?
半魔法半机器半人类?这是什么?
最著名的是“土耳其人”,上面的两张照片展示了沃尔夫冈·冯·肯佩兰在 1770 年的创作。它是在他试图给当时的奥地利女皇留下深刻印象时亮相的, 机器似乎有能力在那个时期下棋并击败一些最聪明的头脑。 它有会动的眼睛和手臂,如果对手拖得太久,它会敲击桌子。那些与这种机制对弈并输给它的对手包括拿破仑·波拿巴、埃德加·爱伦·坡、本杰明·富兰克林以及当时许多其他国际象棋大师。
土耳其人是那个时代的工程奇迹:一个人被机器打败了。这个想法本身扰乱了遇到这个神秘机器的群众,并引起了他们的惊奇。有数百本关于其功能的书籍和理论出版,激发了无数不同的发明。今天被认为是“计算之父”的查尔斯·巴贝奇在 1819 年伦敦比赛后,受到启发,开始思考机械计算机;这导致了他的数字可编程计算机的概念。
但是这个令人惊叹的机械奇观对于它的工作原理有许多怀疑者和不相信者,最著名的是埃德加·爱伦·坡。嗯…,埃德加没有错。
他提议,在为观众做开场演示时,桌子上有足够的空间让一个人隐藏和移动。
土耳其人仍然能够击败许多棋手的原因是,当时大多数有天赋的棋手无法仅靠下棋谋生,所以可以把它视为一种兼职骗局。
这与今天有什么关系?Mturk.com(MechanicalTurk)
Amazon Mechanical Turk 是一个"*众包市场,让个人和企业更容易将他们的流程和工作外包给分散的员工,他们可以虚拟地执行这些任务。***它基本上是一个让人类修复或手动输入计算机程序难以完成的任务的平台。**这可以是任何事情,从插入收据中的数据,到找到度假租赁的地址。上图和下图显示了业务平台和员工对所有可用任务/工作的看法,以及工资。Mturk 对机器学习工作流和所有类型的业务处理外包都有很大的应用;想一想按需分配微任务。
等等,谁在做这项工作?人工智能还是人类?
关于这个平台,我想到了两个问题。难道人工智能程序不应该足够智能来计算这些看起来如此简单的任务吗?还是说 Mturk 更像是一个训练那些人工智能的平台?
更好地了解人工智能和培训数据
在不深入了解人工智能的情况下,一个简单的理解是“算法流水线”。将人工智能周围的所有气泡视为输入系统的算法,这使它变得智能。
它从特定领域获取数据,执行一系列计算,并给出预测。传统算法和人工智能算法的一个关键区别是,一个是手动编程的,而另一个是自我学习的。所有这一切都是由于训练数据的缘故,它可以根据成千上万的例子区分输入和输出。
什么样的训练数据?
与 Mturk 目的相关,干净且有标签的数据是非常理想的。来自真实世界的原始数据是杂乱的,通常需要人类对其进行分类,因此算法知道要“训练”什么,人工智能系统知道要“学习”什么。即使有一个好的系统,有时事情也不会像预期的那样完美,因为没有考虑到现实生活中的影响。所以当这种情况发生时,把任务交给更聪明的东西:人类。
科技公司有一条双向道,让公众了解他们的技术背后实际是什么。谷歌的军用无人机 A.I. 公开了他们如何使用来自众包数据标签公司 CrowdFlower 的“低薪工人”,后者更名为“Figure Figure 8”。公司也可能走上欺骗的道路,就像软件公司 Expensify 所做的那样。Expensify 是一家自动化公司,致力于处理编写各种费用报告的繁琐任务,开发了他们的下一个“惊人的”SmartScan 技术来处理它。但在现实中,就像惊人的“土耳其人”欺骗一样,它在幕后秘密地只是人类:许多实际上是 Mturk 工人!
别再说 Mturk 了,你一直在帮助训练人工智能
谷歌验证码:
你知道那些小测试网站必须确保你是一个人,而不是像那些骗人的机器人一样撒谎?嗯,谷歌使用它的验证码作为收集有用数据的一个很好的方式。想想你不得不说出的那些奇怪而模糊的话;您的正确答案可以帮助使用 Google vision 和书籍进行字符识别。那些不均匀和歪斜的数字呢?这听起来像是谷歌街景和帮助确认地址的一个很好的应用程序。我觉得谷歌的验证码最近主要集中在那些小方块上,在那里你可以从选择的图像中挑选出汽车、交通灯或街道标志。我会发现谷歌不太可能不将这些数据用于他们的自动驾驶汽车项目 Waymo。
脸书:
社交网络 goliath 拥有世界上最先进的面部识别系统之一。每个人都有许多照片,他们或他们的朋友会在里面贴上自己的标签。
- 他们拥有开发一个伟大的系统所需的所有数据,该系统可以在你发布图片时自动标记你朋友的脸。
- 数据积累与输入成本为 0 美元,因为用户已经这样做了。
我们是谷歌的试验品,还是任何人的?
谷歌的深度传感研究团队需要大量的镜头,移动相机从多个角度观察静态空间。你从哪里得到的?当然,2016 年至 2017 年期间的病毒人体模型挑战。
- 该团队使用 2000 个 Youtube 挑战视频来训练他们的人工智能模型,以从运动视频中预测深度。
人工智能的神奇之处在于它是如何建立在人类智能的基础上的,但通常会设法超越它,超越人们认为可能的事情。训练数据是一个系统运行得如何的关键组成部分,可以通过互联网大量获得:这是今天人工智能繁荣的重要推动力。不要忘记,目前对你可以用作训练数据的内容没有“严格的”版权限制,…就目前而言。
作品引用:
世界各地数百万零工经济工作者现在在所谓的人群工作者网站上谋生——这些工作属于…
theintercept.com](https://theintercept.com/2019/02/04/google-ai-project-maven-figure-eight/) [## 当人工智能需要人类助手时
多年来,亚马逊的机械土耳其人(mTurk)在科技界已经是一种公开的秘密,在这个地方,初出茅庐的…
www.theverge.com](https://www.theverge.com/2019/6/12/18661657/amazon-mturk-google-captcha-robot-ai-artificial-intelligence-mechanical-turk-humans) [## 亚马逊土耳其机械公司
获得全球化、按需、24x7 的劳动力亚马逊土耳其机器人是一个众包市场,它使…
www.mturk.com](https://www.mturk.com/) [## 清理人工智能周围的水。
大数据还是大炒作?
towardsdatascience.com](/clearing-the-water-around-a-i-5ca596dad1b9) [## Expensify 的“智能”扫描技术得到了人类的秘密协助
Expensify 是一家软件公司,它自动化了编写费用报告的痛苦任务。它使用…
qz.com](https://qz.com/1141695/startup-expensifys-smart-scanning-technology-used-humans-hired-on-amazon-mechanical-turk/)
批量训练:如何拆分数据?
三种将你的数据分割成批的方法,比较时间&内存效率和代码质量。
介绍
随着数据量的增加,训练机器学习模型的一种常见方法是对批处理应用所谓的训练。这种方法包括将数据集分割成一系列较小的数据块,一次一个地传递给模型。
在本帖中,我们将提出三个想法来拆分批量数据集:
- 创造了一个“大”张量,
- 用 HDF5 加载部分数据,
- python 生成器。
出于说明的目的,我们将假设该模型是一个基于声音的检测器,但本文中的分析是通用的。尽管这个例子是作为一种特殊情况来设计的,但这里讨论的步骤本质上是对数据进行分割、预处理和迭代。它符合普通程序。不管图像文件、来自 SQL 查询的表或 HTTP 响应的数据是什么,我们主要关心的是过程。
具体来说,我们将从以下几个方面比较我们的方法:
- 代码质量,
- 内存占用,
- 时间效率。
什么是批?
形式上,批处理被理解为输入输出对(X[i], y[i])
,是数据的子集。由于我们的模型是一个基于声音的检测器,它期望一个经过处理的音频序列作为输入,并返回某个事件发生的概率。很自然,在我们的例子中,该批次包括:
X[t]
-表示在时间窗口内采样的处理过的音轨的矩阵,以及y[t]
-表示事件存在的二元标签,
其中t
表示时间窗(图 1。).
Figure 1. An example of data input. Top: simple binary label (random), middle: raw audio channel (mono), bottom: spectrogram represented as naural logarithm of the spectrum. The vertical lines represent slicing of the sequence into batches of 1 second length.
光谱图
至于声谱图,你可以把它看作是一种描述每首“曲子”在音轨中所占比重的方式。例如,当演奏低音吉他时,声谱图会显示更集中在频谱较低一侧的高强度。相反,对于女高音歌手,我们会观察到相反的情况。通过这种“编码”,谱图自然地代表了模型的有用特征。
比较想法
作为我们比较的一个共同前提,让我们简单定义以下导入和常量。
from scipy.signal import spectrogram
from os.path import join
from math import ceil
import numpy as np
FILENAME = 'test'
FILEPATH = 'data'
CHANNEL = 0 # mono track only
SAMPLING = 8000 # sampling rate (audio at 8k samples per s)
NFREQS = 512 # 512 frequencies for the spectrogram
NTIMES = 400 # 400 time-points for the spectrogram
SLEN = 1 # 1 second of audio for a batch
N = lambda x: (x - x.mean())/x.std() # normalization
filename = join(FILEPATH, FILENAME)
这里的数字有些随意。我们决定采用最低采样率(其他常见值为 16k 和 22.4k fps),并让每个X
组块成为 512 个频率通道的频谱图,该频谱图是使用沿时间轴的 400 个数据点从 1 的非重叠音频序列计算的。换句话说,每一批将是一对一个 512 乘 400 的矩阵,加上一个二进制标签。
想法 1——一个“大”张量
模型的输入是一个二维张量。由于最后一步涉及批次的迭代,因此增加张量的秩并为批次计数保留第三维是有意义的。因此,整个过程可以概括如下:
- 加载
x
-数据。 - 加载
y
标签。 - 将
X
和y
分批切片。 - 提取每一批的特征(这里是谱图)。
- 将
X[t]
和y[t]
放在一起。
为什么这不是个好主意?让我们看一个实现的例子。
def create_X_tensor(audio, fs, slen=SLEN, bsize=(NFREQS, NTIMES)):
X = np.zeros((n_batches, bsize[0], bsize[1]))
for bn in range(n_batches):
aslice = slice(bn*slen*fs, (bn + 1)*slen*fs)
*_, spec = spectrogram(
N(audio(aslice)),
fs = fs,
nperseg = int(fs/bsize[1]),
noverlap = 0,
nfft = bsize[0])
X[bn, :, :spec.shape[1]] = spec
return np.log(X + 1e-6) # to avoid -Inf
def get_batch(X, y, bn):
return X[bn, :, :], y[bn]
if __name__ == '__main__':
audio = np.load(filename + '.npy')[:, CHANNEL]
label = np.load(filename + '-lbl.npy')
X = create_X_tensor(audio, SAMPLING)
for t in range(X.shape[0]):
batch = get_batch(X, y, t)
print ('Batch #{}, shape={}, label={}'.format(
t, X.shape, y[i]))
这种方法的本质可以被描述为现在全部加载,以后再担心。
虽然创建X
一个自包含的数据块可以被视为一个优点,但这种方法也有缺点:
- 我们将所有数据导入 RAM,不管 RAM 是否能存储这些数据。
- 我们使用第一维度
X
进行批次计数。然而,这仅仅是基于一个惯例。如果下一次有人决定应该是最后一次呢? - 尽管
X.shape[0]
准确地告诉我们有多少批次,我们仍然需要创建一个辅助变量t
来帮助我们跟踪批次。这个设计强制模型训练代码遵守这个决定。 - 最后,它要求定义
get_batch
函数。其唯一目的是选择X
和y
的子集,并将它们整理在一起。它看起来充其量是不受欢迎的。
想法 2——使用 HDF5 加载批次
让我们从消除最可怕的问题开始,即将所有数据加载到 RAM 中。如果数据来自一个文件,那么只加载它的一部分并对这些部分进行操作是有意义的。
使用来自熊猫的 [read_csv](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html)
的skiprows
和nrows
参数,可以加载. csv 文件的片段。然而,由于 CSV 格式对于存储声音数据来说相当不切实际,分层数据格式(HDF5) 是更好的选择。这种格式允许我们存储多个类似 numpy 的数组,并以类似 numpy 的方式访问它们。
这里,我们假设文件包含名为'audio'
和'label'
的固有数据集。更多信息请查看 Python h5py 库。
def get_batch(filepath, t, slen=SLEN, bsize=(NFREQS, NTIMES)):
with h5.File(filepath + '.h5', 'r') as f:
fs = f['audio'].attrs['sampling_rate']
audio = f['audio'][t*slen*fs:(t + 1)*slen*fs, CHANNEL]
label = f['label'][t]
*_, spec = spectrogram(
N(audio),
fs = fs,
nperseg = int(fs/bsize[1]),
noverlap = 0,
nfft = bsize[0])
X = np.zeros((bsize[0] // 2 + 1, bsize[1]))
X[:, :spec.shape[1]] = spec
return np.log(X + 1e-6), label
def get_number_of_batches(filepath):
with h5.File(filepath + '.h5', 'r') as f:
fs = f['audio'].attrs['sampling_rate']
sp = f['audio'].shape[0]
return ceil(sp/fs)
if __name__ == '__main__':
n_batches = get_number_of_batches(filename)
for t in range(n_batches):
batch = get_batch(filename, t)
print ('Batch #{}, shape={}, label={}'.format(
i, batch[0].shape, batch[1]))
希望我们的数据现在是可管理的(如果以前不是的话)!此外,在整体质量方面,我们也取得了一些进步:
- 我们去掉了之前的
get_batch
函数,用一个更有意义的函数取而代之。它计算什么是必要的,并传递数据。简单。 - 我们的
X
张量不再需要人为修改。 - 事实上,通过将
get_batch(X, y, t)
改为get_batch(filename, t)
,我们抽象出了对数据集的访问,并从名称空间中移除了X
和y
。 - 数据集也变成了一个单独的文件。我们不需要从两个不同的文件中获取数据和标签。
- 我们不需要提供
fs
(采样率)参数。得益于 HDF5 中所谓的属性,它可以成为数据集文件的一部分。
尽管有这些优点,我们仍然有两个…不便之处。
因为新的get_batch
不记得状态。我们必须像以前一样使用循环来控制t
。然而,由于get_batch
中没有机制来告诉循环需要多大(除了添加第三个输出参数,这很奇怪),我们需要事先检查数据的大小。除了向get_batch
添加第三个输出(这会使这个函数变得很奇怪)之外,它还要求我们创建第二个函数:get_number_of_batches
。
不幸的是,它并没有使解决方案尽可能优雅。如果我们只是将get_batch
转换成一种能够保持状态的形式,我们可以做得更好。
想法 3——发电机
让我们来识别模式。我们只对一个接一个地访问、处理和传递数据片段感兴趣。我们不需要一下子全部用完。
对于这些机会,Python 有一个特殊的构造,即生成器。生成器是返回生成器迭代器的函数,迭代器不是急切地执行计算,而是在那时传递一点结果,等待被要求继续。完美,对吧?
生成器迭代器可以通过三种方式构建:
- 通过类似于列表理解的表达:例如
(i for i in iterable)
,但是使用()
而不是[]
, - 发电机功能——用
yield
代替return
,或 - 来自定义自定义
__iter__
(或__getitem__
)和__next__
方法的类对象(见文档)。
在这里,使用yield
自然符合我们需要做的事情。
def get_batches(filepath, slen=SLEN, bsize=(NFREQS, NTIMES)):
with h5.File(filepath + '.h5', 'r') as f:
fs = f['audio'].attrs['sampling_rate']
n_batches = ceil(f['audio'].shape[0]/fs)
for t in range(n_batches):
audio = f['audio'][t*slen*fs:(t + 1)*slen*fs, CHANNEL]
label = f['label'][t]
*_, spec = spectrogram(
N(audio),
fs = fs,
nperseg = int(fs/bsize[1]),
noverlap = 0,
nfft = bsize[0])
X = np.zeros((bsize[0] // 2 + 1, bsize[1]))
X[:, :spec.shape[1]] = spec
yield np.log(X + 1e-6), label
if __name__ == '__main__':
for b in get_batches(filename):
print ('shape={}, label={}'.format(b[0].shape, b[1]))
该循环现在位于函数内部。由于有了yield
语句,只有在get_batches
被调用t - 1
次后才会返回(X[t], y[t])
对。模型训练代码不需要管理循环的状态。该函数会记住调用之间的状态,允许用户迭代批处理,而不是使用一些人工的批处理索引。
将生成器迭代器比作包含数据的容器是很有用的。随着每一次迭代中批次的删除,容器在某个时候变空了。因此,索引和停止条件都不是必需的。数据被消耗,直到不再有数据,过程停止。
性能:时间和记忆
我们有意从讨论代码质量开始,因为它与我们的解决方案的发展方式紧密相关。然而,考虑资源限制同样重要,尤其是当数据量增长时。
图二。显示使用上述三种不同方法交付批次所需的时间。如我们所见,处理和移交数据所需的时间几乎相同。无论我们是加载所有要处理的数据,然后对其进行切片,还是从头开始一点一点地加载和处理,获得解决方案的总时间几乎是相等的。当然,这可能是拥有 SSD 的结果,SSD 允许更快地访问数据。尽管如此,所选择的策略似乎对整体时间性能没有什么影响。
Figure 2. Time performance comparison. The red-solid line refers to timing both loading the data to the memory and performing the computation. The red-dotted line times only the loop, where slices are delivered, assuming that data was precomputed. The green-dotted line refers to loading batches from HDF5 file and the blue-dashed-dotted line implements a generator. Comparing the red lines, we can see that just accessing of the data once it is in the RAM is almost for free. When data is local, the differences between the other cases are minimal, anyway.
当查看图 3 时,可以观察到更多的差异。考虑到第一种方法,它是所有方法中对内存需求最大的,会产生长达 1 小时的音频样本。相反,当分块加载数据时,分配的 RAM 由批处理大小决定,使我们安全地低于限制。
Figure 3. Memory consumption comparison, expressed in terms of the percentage of the available RAM being consumed by the python script, evaluated using: (env)$ python idea.py & top -b -n 10 > capture.log; cat capture.log | egrep python > analysis.log
, and post-processed.
令人惊讶的是(或者不是),第二种和第三种方法之间没有明显的区别。该图告诉我们,选择或不选择实现生成器迭代器对我们的解决方案的内存占用没有影响。
这是很重要的一点。通常鼓励使用生成器作为更有效的解决方案,以节省时间和内存。相反,该图显示,就资源而言,生成器本身并不能提供更好的解决方案。重要的是我们访问资源的速度有多快,我们一次能处理多少数据。
使用 HDF5 文件被证明是有效的,因为我们可以非常快速地访问数据,并且足够灵活,我们不需要一次加载所有数据。同时,生成器的实现提高了代码的可读性和质量。虽然我们也可以将第一种方法构建成生成器的形式,但这没有任何意义,因为如果不能加载少量数据,生成器只会改进语法。因此,最好的方法似乎是同时使用加载部分数据和生成器,这由第三种方法表示。
结束语
在这篇文章中,我们介绍了三种不同的方法来分割和处理我们的数据。我们比较了每种方法的性能和整体代码质量。我们也说过,生成器本身并不能提高代码的效率。最终的性能由时间和内存约束决定,但是,生成器可以使解决方案更加优雅。
你觉得哪个解决方案最有吸引力?
还会有更多…
我计划把文章带到下一个层次,并提供简短的视频教程。
如果您想了解关于视频和未来文章的更新,订阅我的 简讯 。你也可以通过填写表格让我知道你的期望。回头见!
使用员工的幸福指数预测工作场所的生产力
训练回归模型
Y 你已经观察到,在过去的几年里,快乐的员工是你公司的主要利润来源,在这些年里,你记下了所有员工的快乐指数和他们的工作效率。现在你有大量的员工数据躺在 excel 文件中,你最近听说 “数据是新的石油。将会胜出的公司正在使用数学。”——凯文·普兰克,安德玛创始人兼 CEO,2016。
您想知道是否也可以通过某种方式匹配这些数据来预测新员工的生产力,这些数据基于他们的幸福指数。这样你就能更容易地找出效率最低的员工(然后假设解雇他们——只是假设)。如果是这样的话,干杯!🎉—因为这篇博文将向你介绍一种机器学习算法(回归)来构建这个 AI 应用!
这篇博文的目的是…📚
提供训练回归模型的完整见解
目标观众是… 🗣 🗣 🗣
任何人都在寻找回归背后的数学的深入而易懂的解释
本教程结束时…🔚
你将会对回归的本质有一个完整的了解😃你对 ML 的理解将会提高👍是的,你将能够识别出你效率最低的员工!
现在是路线图……🚵
里程碑# 1 : 什么是回归,它与分类有何不同?
里程碑# 2.1 : 训练回归模型——决定损失函数作为回归模型的评价指标
里程碑# 2.2 : 训练回归模型——梯度下降
里程碑# 2.3 : 训练回归模型——使用梯度下降 完成简单回归模型的遍历
里程碑# 3 : 利用训练好的回归模型进行智能预测!
里程碑# 4: 最后是结束语……
让我们开始吧!
什么是回归,它与分类有何不同?
在定义回归之前,让我们先定义连续变量和离散变量。
连续变量
连续变量是可以从它的无限可能值集中取任何值的变量——可能值的数量简直是数不清的。
离散变量
离散变量是可以从其有限的可能值集中取任何值的变量,可能值的数量是可数的。
区分分类和回归
在上述讨论的背景下,我们现在可以清楚地区分分类和回归。分类是从一组有限的值(显然是我们在训练中得到的值)中预测一个值(即一个类),而回归是从一组无限的可能值中预测一个值。例如,预测个人体重的模型是回归模型,因为其预测体重值有无限种可能性。一个人的体重可能是 12.0 千克或 12.01 千克或 12.21 千克,这种可能性是无限的,因此是一个回归模型。
举几个例子来进一步阐明分类的区别&回归
Classification & Regression Examples
里程碑 1 达成!👍
训练回归模型——决定损失函数作为回归模型的评估指标
假设我们有一个员工幸福指数和员工生产力的训练数据集,将它们绘制成下图。
该图显示了训练数据的基本模式是两个变量之间的线性关系。因此,训练一个广义回归模型将对这个线性关系/函数建模,即在许多可能的线中找到一条最佳拟合线。在此之后,一个看不见的数据点(测试时间示例)将根据在训练阶段学习的线性函数/趋势预测其值。
训练回归模型的摘要概述
假设我们已经知道一条线将很好地适合给定的数据集。为了对给定数据集的直线建模,我们现在需要找到梯度(m)和 y 轴截距©的最佳值。因此,在培训期间,我们试图学习 m 和 c 的最佳可能值。一旦我们有了这些值,我们就可以简单地将给定幸福指数(即 x)的值代入学习的等式,并预测其对应的员工生产率,即 y。
Weights/Thetas — Learning Parameters
如果我们简单地使用准确性作为评估标准来评估我们模型的性能会怎么样?
假设我们训练了一个回归模型,现在我们需要评估它在测试集(理想情况下是验证集)上有多好。因此,我们将所有验证集数据点(x 值)插入到线性函数中,并预测它们对应的 y 值。
使用准确性作为回归模型的评估标准将总是导致训练、验证和测试集的准确性为 0!为什么?因为实际上来说,预测值可以非常接近真实的连续值,但很难与真实标签的完全相等。因此,由于预测标签和真实标签不匹配,准确性将始终为零。
Using Accuracy as Evaluation Metric for Regression
上面的例子表明,虽然预测值接近实际值,但并不完全相等,精度完全为 0。因此,使用准确性作为评估回归模型的评估标准是不明智的。我们需要定义另一个评估指标来评估我们模型的性能。
决定回归模型的评估标准
我们需要一个评估指标,以反映我们的训练模型的有效性,即如何推广。它不应该仅仅因为预测值和实际值不完全匹配就输出零值。相反,它应该能够以某种方式证明我们的最佳可能模型与理想模型(一个具有所有理想θ/权重的函数,但这在现实生活场景中几乎不可能)相比有多好。
定义 L1- 损失函数为评估度量
Notations for Loss Function
L1 — Loss Function
更小的损失值
如果预测值与实际值之间的总差值相对较小,则总误差/损失将是更小的值,因此表示模型良好。
更大的损失值
如果实际值和预测值之间的差异很大,损失函数的总误差/值也会相对较大,这也意味着模型没有被很好地训练。
作为均方误差的损失函数
L2 Loss Function
L2 损失/均方误差/二次损失—都是一样的
训练回归模型的目标 训练回归模型的目标是找到那些损失函数最小的权重值,即预测值和真实标签之间的差异尽可能最小。
为什么预测标签和真实标签永远不会完全匹配?
达到里程碑 2.1!👍 👍1️⃣
很多吗?休息一下再回来!
梯度下降算法
到目前为止,我们假设我们已经有了回归模型的权重。但是,实际上是找到这些权重的最佳可能值的过程说明了损失值的最小化。这一发现过程就是众所周知的算法“梯度下降”发挥作用的地方,如下所述。
梯度下降——直觉
假设你只是随机降落在一座山上,并且你是盲折叠。你的目标是尽快到达山脚。你会怎么做?一个可能的解决办法是向最陡的方向下降/移动,并在没有到达山脚时继续这样做。
下降时,你特别注意一些事情,以确保你确实到达了基点
- 因为你的目标是尽可能快地到达基点,所以你决定在开始的几个下降步骤中迈出更大的步伐。
- 因为你还想确保一旦你到达了更接近基地的地方,跳到任何上升点的机会就被最小化了。因此,你决定在后面的下降步骤中采取较小的跳跃,以确保在接近基点时不会偏离基点。
Gradient Descent — Intuition
梯度下降——数学观点
训练回归模型的目的是通过以与我们下降到山的底部时相同的方式收敛函数,找到/学习将最小化 L2 损失的权重。
定义梯度下降伪代码
深入探究 L2 损失的偏导数— 2.1.1
更新权重值— 2.1.2
Updating Weights to Minimize Loss
里程碑 2.2 达成!👍 👍2️⃣
训练回归模型——对梯度下降的完整遍历
现在我们已经熟悉了梯度下降的工作原理,让我们借助几个例子来进一步阐明它。
Gradient Descent Walk-through
上面例子中的一些假设
为了上面的例子,我们确实假设了一些事情。下图将这些假设与现实世界的情景进行了比较。
Example Assumptions
达到里程碑 2.3!👍 👍3️⃣
这标志着您的第二个里程碑已经完成!👍 ️👍
只要再多一点点,你就完成了!
使用训练好的回归模型进行智能预测!
一旦我们在训练阶段训练/调整了权重,对给定测试示例的预测就非常简单了——只需计算训练权重和给定测试示例特征的点积。
下图进一步阐明了这一点…
Generating Predictions at Run Time
里程碑 3 实现!👍 ️👍👍️
转到结论部分……
1.调整学习率
Learning Rate — Hyperparameter
2 。为什么在培训中使用 1 的附加功能
Additional Training Feature of 1’s
3.梯度下降需要特征归一化
应在数据预处理步骤中对要素进行范围归一化。否则,训练预测很可能会受到具有极端正值/负值的特征的影响。由于特征值确实会影响其对应的导数,极值可能会使学习过程变得更长,或者函数可能不会很好地收敛到基点——学习变得困难。因此,将你的特征标准化通常是个好主意。
4.用 L2 损失代替 L1 损失
你可能会奇怪,为什么我们不利用 L1 损失,而不是 L2 损失。其原因是 L1 损失的导数在 X=0 处未定义,梯度下降假设损失函数在任何地方都可以微分。因此,我们通常不喜欢使用 L1 损失。
5.关于梯度下降收敛的一点注记
达到里程碑 4!👍 👍 👍 👍
不要再做什么聪明的事情了!
你现在可以着手建立你自己的人工智能模型来预测你的员工的生产力!
这篇博文到此为止!
如果您有任何想法、意见或问题,欢迎在下面评论或联系📞跟我上LinkedIn
在 Google Colab 上训练 RetinaNet 从 KTH 手工工具数据集中检测钳子、锤子和螺丝刀。
在本文中,RetinaNet 在 Google Colab 中接受了检测钳子、锤子和螺丝刀工具的训练。该数据集取自一个名为 KTH 手工工具数据集的开放来源。它包括三种类型的手工具的图像:锤子,钳子和螺丝刀在不同的照明和不同的位置。
Example of images of KTH Handtool Dataset [https://www.nada.kth.se/cas/data/handtool/].
Jupyter 文章的笔记本代码可以在我的 github 中的中找到。
首先在本地从源下载数据集(解压文件并重命名为 KTH _ 手工具 _ 数据集),然后将文件夹下载到 google drive。之后,我们应该将 google disc 安装到 google colab:
**from** **google.colab** **import** drive
drive.mount('/content/drive')
其次,需要准备数据来训练 RetinaNet:初始数据集中的所有图像都应该保存在不同的文件夹中,每个图像都有相关的边界框。为了训练 RetinaNet,我们需要创建一个 CSV 文件:该文件中的每一行都应该包含来自数据集的图像文件的名称、每个对象的边界框坐标以及该对象的类名。下面的脚本解析所有的文件夹(有 3 个文件夹是蓝色、白色和棕色背景的图片)。
要解析的第一个文件夹是“Blue_background”。此文件夹与“棕色 _ 背景”或“白色 _ 背景”的区别在于,带有乐器的文件夹不包含“Kinect”和“网络摄像头”子文件夹。
**from** **bs4** **import** BeautifulSoup
**import** **os**
**import** **csv**
folder = '/content/drive/My Drive/KTH_Handtool_Dataset'
subfolder = ['Blue_background']
in_subfolder = ['Artificial', 'Cloudy', 'Directed']
instruments = ['hammer1', 'hammer2', 'hammer3', 'plier1', 'plier2', 'plier3', 'screw1', 'screw2', 'screw3']
**with** open('train.csv', mode='w') **as** file:
writer = csv.writer(file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
**for** folder_name **in** subfolder:
**for** in_folder_name **in** in_subfolder:
**for** instrument **in** instruments:
directory_name = os.path.join(folder, folder_name, 'rgb', in_folder_name, instrument)
directory_name_xml = os.path.join(folder, folder_name, 'bboxes', in_folder_name, instrument)
**for** filename **in** os.listdir(directory_name_xml):
label = instrument
filename_jpg = filename[:-4] + '.jpg'
filename_str = os.path.join(directory_name, filename_jpg)
handler = open(os.path.join(directory_name_xml, filename)).read()
soup = BeautifulSoup(handler, "xml")
xmin = int(soup.xmin.string)
xmax = int(soup.xmax.string)
ymin = int(soup.ymin.string)
ymax = int(soup.ymax.string)
row = [filename_str, xmin, ymin, xmax, ymax, label]
writer.writerow(row)
参数文件夹-存储所有数据的目录(提取的 KTH _ 手工具 _ 数据集文件夹的父目录)。
对于“白色 _ 背景”和“棕色 _ 背景”,脚本应该不同,因为它们在乐器的文件夹中包含子文件夹“Kinect”和“网络摄像头”(“锤子 1”、“锤子 2”、“锤子 3”、“plier1”、“plier2”、“plier3”、“screw1”、“screw2”、“screw3”)。我添加了这一行来包含这些文件夹
**for** sub_instrument **in** sub_instruments:
另一个诀窍是,kinect 的“rgb”文件夹和“bbox”文件夹有不同的名称:在一些文件夹中是“Kinect”,在另一些文件夹中是“Kinect”,因此我决定用不同的方法来解析图像目录(“rgb”文件夹)和边框目录(“bbox”文件夹)。字典用于更改名称:
dict_instr = {'Kinect': 'kinect'}
下一步:如果名称“kinect”不在 xml path 文件夹中,则它会变成小写的“Kinect”:
dir_name_xml = os.path.join(folder, folder_name, 'bbox', in_folder_name, instrument)
**if** sub_instrument **not** **in** os.listdir(dir_name_xml):
sub_instrument = dict_instr[sub_instrument]
结果,“棕色背景”和“白色背景”的脚本如下:
subfolder = ['Brown_background', 'White_background']
sub_instruments = ['Kinect', 'webcam']
dict_instr = {'Kinect': 'kinect'}
*# open file to write to the end of a file*
**with** open('train.csv', mode='a') **as** file:
writer = csv.writer(file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
**for** folder_name **in** subfolder:
**for** in_folder_name **in** in_subfolder:
**for** instrument **in** instruments:
**for** sub_instrument **in** sub_instruments:
directory_name = os.path.join(folder, folder_name, 'rgb', in_folder_name, instrument, sub_instrument)
dir_name_xml = os.path.join(folder, folder_name, 'bbox', in_folder_name, instrument)
**if** sub_instrument **not** **in** os.listdir(dir_name_xml):
sub_instrument = dict_instr[sub_instrument]
directory_name_xml = os.path.join(dir_name_xml, sub_instrument)
**for** filename **in** os.listdir(directory_name_xml):
label = instrument
filename_jpg = filename[:-4] + '.jpg'
filename_str = os.path.join(directory_name, filename_jpg)
handler = open(os.path.join(directory_name_xml, filename)).read()
soup = BeautifulSoup(handler, "xml")
xmin = int(soup.xmin.string)
xmax = int(soup.xmax.string)
ymin = int(soup.ymin.string)
ymax = int(soup.ymax.string)
row = [filename_str, xmin, ymin, xmax, ymax, label]
writer.writerow(row)
运行上面的代码后,我们应该进行预处理,因为一些 xml 文件没有相关的图像:
**import** **pandas** **as** **pd**
**import** **os.path**
list_indexes_to_drop = []
data = pd.read_csv("train.csv", header=**None**)
**with** open('train_new.csv', mode='a') **as** file:
**for** i **in** range(len(data)):
fname = data.iloc[i, 0]
**if** **not** os.path.isfile(fname):
list_indexes_to_drop.append(i)
data = data.drop(data.index[list_indexes_to_drop])
data.to_csv(path_or_buf='train.csv', index=**False**, header=**None**)
预处理后,数据应该被分割。CSV 文件中的所有元素都被随机打乱
data = pd.read_csv("train.csv", header=**None**)
data = data.sample(frac=1).reset_index(drop=**True**)
然后,通过下一个代码将数据分为训练集(80%)和测试集(20%)。
amount_80 = int(0.8*len(data))
train_data = data[:amount_80]
test_data = data[amount_80:]print(len(train_data))
print(len(test_data))
我们应该将 train_data 保存为 train_annotations.csv:
train_data.to_csv(path_or_buf='train_annotations', index=False)
下一步是从网站上下载用于训练神经网络的权重,并将其放入“权重”文件夹中:
!mkdir weights
!wget -O /content/weights/resnet50_coco_best_v2.h5 https://github.com/fizyr/keras-retinanet/releases/download/0.5.1/resnet50_coco_best_v2.1.0.h5
我们还应该创建文件夹“快照”, RetinaNet 将在训练期间保存重量,以及“tensorboard ”,其中将解决关于训练的信息:
!mkdir /content/drive/My\ Drive/kth_article/snapshots
!mkdir /content/drive/My\ Drive/kth_article/tensorboard
下一步是创建 csv 文件“classes.csv”(包含仪器名称):
dict_classes = {
'hammer1': 0,
'hammer2': 1,
'hammer3': 2,
'plier1': 3,
'plier2': 4,
'plier3': 5,
'screw1': 6,
'screw2': 7,
'screw3': 8
}
**with** open('classes.csv', mode='w') **as** file:
writer = csv.writer(file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
**for** key, val **in** dict_classes.items():
row = [key, val]
print(row)
writer.writerow(row)
我们还需要安装 RetinaNet:
!cd ~!git clone [https://github.com/fizyr/keras-retinanet](https://github.com/fizyr/keras-retinanet)
%cd keras-retinanet!git checkout 42068ef9e406602d92a1afe2ee7d470f7e9860df!python setup.py install
!python setup.py build_ext --inplace
我们需要返回到父目录:
%cd ..
下一个脚本用于训练神经网络:
!retinanet-train --weights /weights/resnet50_coco_best_v2.h5 \
--batch-size 4 --steps 4001 --epochs 20 \
--snapshot-path snapshots --tensorboard-dir tensorboard \
csv train_annotations.csv classes.csv
train_annotations.csv :包含来自训练数据的所有图像的信息的文件,每个图像的格式应按照以下模板制作:< path_to_image >,< xmin >,< ymin >,< xmax >,< ymax >,< label >,其中 xmin,ymin,xmax,ymax 为以像素为单位的包围盒,label 为 a
labels.csv :带有类标签的文件,后面应该是模板:< class_name >,< id >
经过 3 个纪元的训练,我们会得到一些准确性。我们可以在训练数据上验证模型。另一种选择是在一些真实图像上测试准确性:
Image of real plier image (IKEA plier).
要在验证集上测试模型,我们需要将权重转换为测试格式:
!retinanet-convert-model snapshots/resnet50_csv_03.h5 weights/resnet50_csv_03.h5
要检查测试集的结果:
!retinanet-evaluate csv val_annotations.csv classes.csv weights/resnet50_csv_03.h5
我们可以看到,在测试集上,经过多次训练后的结果已经很好了,平均精度为 66%:
116 instances of class hammer1 with average precision: 0.7571
113 instances of class hammer2 with average precision: 0.6968
110 instances of class hammer3 with average precision: 0.8040
123 instances of class plier1 with average precision: 0.5229
119 instances of class plier2 with average precision: 0.5567
122 instances of class plier3 with average precision: 0.8953
126 instances of class screw1 with average precision: 0.4729
152 instances of class screw2 with average precision: 0.5130
131 instances of class screw3 with average precision: 0.7651
mAP: 0.6649
同时,在真实环境中也不太好:为了检查它,我们可以在我们的图像上运行 Retinanet 的预测。
*# show images inline*
%matplotlib inline
*# automatically reload modules when they have changed*
%load_ext autoreload
%autoreload 2
*# import keras*
**import** **keras**
*# import keras_retinanet*
**from** **keras_retinanet** **import** models
**from** **keras_retinanet.utils.image** **import** read_image_bgr, preprocess_image, resize_image
**from** **keras_retinanet.utils.visualization** **import** draw_box, draw_caption
**from** **keras_retinanet.utils.colors** **import** label_color
*#from keras_retinanet.keras_retinanet.utils.gpu import setup_gpu*
*# import miscellaneous modules*
**import** **matplotlib.pyplot** **as** **plt**
**import** **cv2**
**import** **os**
**import** **numpy** **as** **np**
**import** **time**
*# use this to change which GPU to use*
gpu = 0
*# set the modified tf session as backend in keras*
*#setup_gpu(gpu)*
*# adjust this to point to your downloaded/trained model*
*# models can be downloaded here: https://github.com/fizyr/keras-retinanet/releases*
model_path = os.path.join('..', 'weights', 'resnet50_csv_04.h5')
model = models.load_model(model_path, backbone_name='resnet50')
*# if the model is not converted to an inference model, use the line below*
*# see: https://github.com/fizyr/keras-retinanet#converting-a-training-model-to-inference-model*
*#model = models.convert_model(model)*
*#print(model.summary())*
*# load label to names mapping for visualization purposes*
labels_to_names = {
0: 'hammer1',
1: 'hammer2',
3: 'hammer3',
4: 'plier1',
5: 'plier2',
6: 'plier3',
7: 'screw1',
8: 'screw2',
9: 'screw3'}
*# load image*
image = read_image_bgr('/content/img.jpg')
*# copy to draw on*
draw = image.copy()
draw = cv2.cvtColor(draw, cv2.COLOR_BGR2RGB)
*# preprocess image for network*
image = preprocess_image(image)
image, scale = resize_image(image)
*# process image*
start = time.time()
boxes, scores, labels = model.predict_on_batch(np.expand_dims(image, axis=0))
print("processing time: ", time.time() - start)
*# correct for image scale*
boxes /= scale
*# visualize detections*
**for** box, score, label **in** zip(boxes[0], scores[0], labels[0]):
*# scores are sorted so we can break*
**if** score < 0.21:
**break**
color = label_color(label)
b = box.astype(int)
draw_box(draw, b, color=color)
caption = "**{}** **{:.3f}**".format(labels_to_names[label], score)
draw_caption(draw, b, caption)
plt.figure(figsize=(15, 15))
plt.axis('off')
plt.imshow(draw)
plt.show()
我取了 21%的阈值,所以模型预测仪器的准确率是 22%。
Retinanet prediction on a real photo after 4 epochs of training on KTH_Dataset.
我们可以进一步训练模型以改进模型,并且训练模型超过 4 个时期。
结论。
本文是研究的一部分,我们想要创建一个模型,它可以识别视频中的乐器。我们使用 KTH 手工工具数据集来提高模型的准确性。实验表明,额外的图像改善了对象检测。可以对模型进行一些改进(可以使用额外的数据集来改进仪器检测,可以进行更多的时期训练)。
参考文献