笔记:机器学习的工作流程

1.机器学习工作流程介绍

本文学习如何思考机器学习问题。机器学习(ML)不仅关于数学和建模,而且关于选择问题的设置,识别客户需求和长期目标。本文的组织如下:

1.通过识别关键利益相关者,选择正确的度量指标,建立机器学习问题的框架。
2.因为ML是关于数据的,将讨论与数据相关的不同挑战。
3.在解决ML问题时如何组织数据集,以确保创建的模型在新数据上表现良好。
4.如何利用不同的工具来确定模型的局限性。
在本文中,将多次使用德国交通标志识别基准(German Traffic Sign Benchmarks,GTSRB)进行练习。

2.建立问题的框架
除非你是在参加机器学习比赛,否则模型的性能很少是你唯一关心的事情。例如,在自动驾驶汽车系统中,模型的推断时间(提供预测所需的时间)也是一个重要的因素。一个每秒可以消化5个图像的模型比一个每秒只能处理一个图像的模型要好,即使第二个图像的性能更好。在这种情况下,推断时间也是选择我们的模型的一个指标。

理解数据管道(pipeline)非常重要,因为它将推动模型开发。在某些情况下,获取新数据相对容易,但对它们进行标定(例如通过关联类名)可能代价很高。在这种情况下,可能希望创建一个需要较少数据的模型,或者可以处理未标定数据的模型。


 

3. 确定主要利益相关者

作为一名机器学习工程师,你很少会成为自己产品的最终用户。因此,您需要确定您试图解决的问题的不同涉众。为什么?因为这将推动您的模型开发。

4.选择度量指标

每一个机器学习问题都需要它自己的度量标准,而一些度量标准,如准确性,可能适合于许多问题,需要记住错误预测的后果。考虑以下情况:当构建一个垃圾邮件分类算法,应该以很少的False Positives(假阳性/误报)为目标,因为不希望算法将一些潜在的重要邮件分类到垃圾邮件文件夹。然而,False Negative (假阴性)只是一个位于收件箱中的垃圾邮件,它可以由用户手动删除。

 5.分类和目标检测指标

Precision(精确度):在分类为特定类别的元素中,做对了多少个?例如,将6张包含汉堡的图片分类,其中只有5张确实包含汉堡。精度是5/6。

召回率(Recall):正确分类的图像数量除以图像总数。例如,有40张汉堡的图片,正确地分类了其中的15张。召回率(recall)是15/40。对目标检测使用相同的精确度和召回率定义,但考虑的是每个图像中目标的实例数。

Accuracy(准确性):(仅用于分类问题)正确分类的图像数量占图像总数的比例。

6.练习1
结构

在本练习中,需要使用以下文件:

Ground truth data: data/ground_truth.json包含真实数据的标签(边界框和类)。该文件包含20个观察项,每个观察项是一个字典,带有以下字段。

{filename: str, boxes: List[List[int]], classes: List[int]}

边界框使用[x1, y1, x2, y2]格式。

预测数据data/predictions.json包含预测数据的标签(边界框和类)(可以将其视为模型的输出)。格式类似于真实数据。
最后,utils.py文件包含helper函数。其中一个check_results将验证IoU计算,另一个get_data将把真实值和预测数据加载到Python字典中。

第一部分-计算IoU
目标

在本练习的第一部分中,任务是实现一个函数,该函数计算两个边界框之间的iou。

细节
iou.py中的calculate_ious函数接受两个数组,其中包含边界框坐标作为输入。两个数组都是1x4 numpy数组。数组使用以下格式:

[x1, y1, x2, y2]

其中x1 < x2, y1 < y2。(x1, y1)为左上角的坐标,(x2, y2)为边界框右下角的坐标。

例如

iou = calculate_iou(np.array([0, 0, 100, 100]), np.array([101, 101, 200, 200]))

提示
请记住,边界框可能不相交,在这种情况下,IoU应该等于0。

通过运行python iou.py,检查实现情况。

第二部分-计算精确度/召回率
目标
要求对给定的一组预测和真实值计算精确度和召回率。使用0.5 IoU的阈值来确定一个预测是否为真。

细节

precision_recall.py中的precision_recall函数以一个IoU值组成的 ious NxM数组以及两个列表pred_classes和gt_classes作为输入,该列表包含M个预测类ids和N个真实类ids。

IoU数组包含N个真实值边界框和M个预测值边界框之间的成对IoU值,如下:

ious[x, y] = calculate_iou(groundtruth[x], predictions[y])

 例如

precision, recall = precision_recall(np.array([[0.5, 0.1],[0.8, 0.1]), 
                                     np.array([1, 2]), 
                                     np.array([1, 0]))

提示
需要计算False Negatives的数量来计算召回率。可以使用IoU数组来查找未预测的真实值边界框。

通过运行python precision_recall.py,检查程序实现情况。
参考代码

iou.py

import numpy as np

from utils import get_data, check_results


def calculate_iou(gt_bbox, pred_bbox):
    """
    calculate iou 
    args:
    - gt_bbox [array]: 1x4 single gt bbox
    - pred_bbox [array]: 1x4 single pred bbox
    returns:
    - iou [float]: iou between 2 bboxes
    """
    xmin = np.max([gt_bbox[0], pred_bbox[0]])
    ymin = np.max([gt_bbox[1], pred_bbox[1]])
    xmax = np.min([gt_bbox[2], pred_bbox[2]])
    ymax = np.min([gt_bbox[3], pred_bbox[3]])

    intersection = max(0, xmax - xmin + 1) * max(0, ymax - ymin + 1)
    gt_area = (gt_bbox[2] - gt_bbox[0]) * (gt_bbox[3] - gt_bbox[1])
    pred_area = (pred_bbox[2] - pred_bbox[0]) * (pred_bbox[3] - pred_bbox[1])

    union = gt_area + pred_area - intersection
    return intersection / union


def calculate_ious(gt_bboxes, pred_bboxes):
    """
    calculate ious between 2 sets of bboxes 
    args:
    - gt_bboxes [array]: Nx4 ground truth array
    - pred_bboxes [array]: Mx4 pred array
    returns:
    - iou [array]: NxM array of ious
    """
    ious = np.zeros((gt_bboxes.shape[0], pred_bboxes.shape[0]))
    for i, gt_bbox in enumerate(gt_bboxes):
        for j, pred_bbox in enumerate(pred_bboxes):
            ious[i,j] = calculate_iou(gt_bbox, pred_bbox)
    return ious


if __name__ == "__main__": 
    ground_truth, predictions = get_data()
    # get bboxes array
    filename = 'segment-1231623110026745648_480_000_500_000_with_camera_labels_38.png'
    gt_bboxes = [g['boxes'] for g in ground_truth if g['filename'] == filename][0]
    gt_bboxes = np.array(gt_bboxes)
    pred_bboxes = [p['boxes'] for p in predictions if p['filename'] == filename][0]
    pred_boxes = np.array(pred_bboxes)

    ious = calculate_ious(gt_bboxes, pred_boxes)
    check_results(ious)

precision_recall.py

import numpy as np

from iou import calculate_ious
from utils import get_data


def precision_recall(ious, gt_classes, pred_classes):
    """
    calculate precision and recall
    args:
    - ious [array]: NxM array of ious
    - gt_classes [array]: 1xN array of ground truth classes
    - pred_classes [array]: 1xM array of pred classes
    returns:
    - precision [float]
    - recall [float]
    """
    xs, ys = np.where(ious>0.5)

    # calculate true positive and true negative
    tps = 0
    fps = 0
    for x, y in zip(xs, ys):
        if gt_classes[x] == pred_classes[y]:
            tps += 1
        else:
            fps += 1

    matched_gt = len(np.unique(xs))
    fns = len(gt_classes) - matched_gt

    precision = tps / (tps+fps)
    recall = tps / (tps + fns)
    return precision, recall


if __name__ == "__main__": 
    ground_truth, predictions = get_data()

    # get bboxes array
    filename = 'segment-1231623110026745648_480_000_500_000_with_camera_labels_38.png'
    gt_bboxes = [g['boxes'] for g in ground_truth if g['filename'] == filename][0]
    gt_bboxes = np.array(gt_bboxes)
    gt_classes = [g['classes'] for g in ground_truth if g['filename'] == filename][0]


    pred_bboxes = [p['boxes'] for p in predictions if p['filename'] == filename][0]
    pred_boxes = np.array(pred_bboxes)
    pred_classes = [p['classes'] for p in predictions if p['filename'] == filename][0]

    ious = calculate_ious(gt_bboxes, pred_boxes)
    precision, recall = precision_recall(ious, gt_classes, pred_classes)
    print(f'Precision: {precision}')
    print(f'Recall: {recall}')

7.数据采集与可视化

在许多情况下,需要收集自己的数据,但在某些情况下,能够利用开放源代码数据集,例如谷歌Open Image Dataset。但是,请记住最终目标以及将在何处部署或使用算法。

由于所谓的域差(domain gap),在特定数据集上训练的算法,在另一个数据集上可能表现不佳。例如,根据特定摄像头收集的数据训练的行人检测算法,可能无法根据另一个摄像头捕获的图像准确检测行人。

8.练习2 -可视化
目的

对于本练习,需要在visualization.py中实现一个函数,在一组图像上可视化真实值边框。需要使用与每个边界框相关联的类id来显示用颜色编码的边界框。需要在一个图中显示所有数据。应该以可视化为目标,因为清晰的数据可视化对于传达信息至关重要。

细节
标签(边界框和类)的位于data/ground_truth.json文件。它包含20个观察项,每个观察项是一个字典,带有以下字段。
 

{filename: str, boxes: List[List[int]], classes: List[int]}

边界框使用[x1, y1, x2, y2]格式。图像(png文件)位于data/ Images文件夹中。每个相关联的图像都可以与其带文件名的标签匹配。

utils.py文件包含一个帮助函数get_data,可以导入该函数来加载真实值和预测。尽管该练习只需要真实值。

提示
可以使用matplotlib补丁来创建边框可视化。可以通过边界框添加类名来改进上述可视化。

再进一步
想想看,可以在同一幅图像上以一种清晰的方式显示真实值和预测。

参考代码

import glob
import json
import os

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from PIL import Image

from utils import get_data


def viz(ground_truth):
    """
    create a grid visualization of images with color coded bboxes
    args:
    - ground_truth [list[dict]]: ground truth data
    """
    paths = glob.glob('data/images/*')

    # mapping to access data faster
    gtdic = {}
    for gt in ground_truth:
        gtdic[gt['filename']] = gt

    # color mapping of classes
    colormap = {1: [1, 0, 0], 2: [0, 1, 0], 4: [0, 0, 1]}

    f, ax = plt.subplots(4, 5, figsize=(20, 10))
    for i in range(20):
        x = i % 4
        y = i % 5

        filename = os.path.basename(paths[i])
        img = Image.open(paths[i])
        ax[x, y].imshow(img)

        bboxes = gtdic[filename]['boxes']
        classes = gtdic[filename]['classes']
        for cl, bb in zip(classes, bboxes):
            y1, x1, y2, x2 = bb
            rec = Rectangle((x1, y1), x2- x1, y2-y1, facecolor='none', 
                            edgecolor=colormap[cl])
            ax[x, y].add_patch(rec)
        ax[x ,y].axis('off')
    plt.tight_layout()
    plt.show()


if __name__ == "__main__": 
    ground_truth, _ = get_data()
    viz(ground_truth)

9.探索性数据分析(EDA)

机器学习算法可能对域转换非常敏感。这种域转换可以发生在不同的级别:

天气/光照条件:例如,只在晴天图像上训练的算法在雨天或夜间数据时表现不佳。
传感器:一个传感器的变化或不同的处理方法会产生域转换。
环境:例如,在低强度交通数据上训练的算法在高强度交通数据上表现不佳。
广泛的探索性数据分析(EDA)是任何ML项目成功的关键。为什么?因为在此阶段,ML工程师将熟悉数据集并发现数据的任何潜在挑战。EDA是项目中如此重要的一部分,以至于ML的工程师单单在它上面要花几天的时间。对于视觉问题,它需要查看数据集中的1000个图像!
10.交叉验证

​​ML算法的目标是部署在生产环境中。例如,在最终项目中创建的目标检测算法可以直接部署在自动驾驶汽车中。但在部署这种算法之前,需要确保它在遇到的任何环境中都能表现良好。换句话说,要评估模型的推广能力。

介绍三个新概念:

过拟合:当模型不能很好地推广时
偏方差权衡:为什么很难建立一个平衡的模型
交叉验证:评估模型推广效果的一种技术
过度拟合

当一个模型过拟合时,它就失去了推广的能力。当选择的模型过于复杂,开始提取噪声而不是有意义的特征时,就会发生这种情况。例如,当一个汽车检测模型开始提取数据集中的汽车的品牌特定特征(如汽车标识)而不是更广泛的特征(车轮、形状等)时,它就是过拟合。

过拟合提出了一个非常重要的问题。如何知道模型是否能恰当地一般化?事实上,当单个数据集可用时,要知道我们创建的模型是过度拟合,还是仅仅表现良好,将是一个挑战。

现在,使用术语训练数据,来描述用于教学和创建算法的数据,并为任何新的、未曾看到的数据测试数据

偏差和方差权衡

偏差-方差权衡说明了机器学习中最重要的挑战之一。如何创建一个性能良好的模型,同时保持其推广到新的、未曾见的数据的能力?算法对这些数据的性能可以通过测试误差来量化。测试误差可以进一步分解为偏差和方差。

偏差量化了模型对训练数据的拟合质量。低偏差意味着模型在训练数据集上的错误率很低。

方差量化了模型对训练数据的敏感性。换句话说,如果用另一个训练数据集替换现在的训练数据集,那么训练错误率会发生多大的变化?较低的方差说明模型对训练数据不敏感,具有较好的推广性。

验证集和交叉验证

交叉验证是一套技术,以评估模型的推广和缓解过拟合挑战的能力。在本文中,利用验证集方法,将可用数据分成两部分:

    一个训练集,用于创建算法(通常80% -90%的可用数据)
    一个验证集,用于评估(可用数据的10-20%)
以及如何利用这种方法来缓解过拟合问题。

其他交叉验证方法也存在,如LOO (Leave One Out)k-fold交叉验证,但它们不适合深度学习算法。在这里(Cross Validation)阅读更多关于这两种技巧的内容。

10.TFRecord

TF记录是TensorFlow的自定义数据格式。即使它们在技术上不需要用TensorFlow训练模型,但它们可能非常有用。对于一些已经存在的TensorFlow API,需要TF Record格式来训练模型。

Waymo开放数据集vs. TensorFlow目标检测API
虽然每个文件都使用.tfrecord文件,但每个文件的结构都有差异。因此,即将到来的练习将从Waymo开放数据集取一个.tfrecord,并将其转换为TensorFlow目标检测API可用的新.tfrecord。

虽然在练习中也有链接,但需要一些资源才能更容易地做到这一点。

    首先,这个存储库 (GitHub - Jossome/Waymo-open-dataset-document: The official document of the dataset is too general. There are many missing details. This document aims at making it more convenient to use the dataset.提供了一些关于Waymo开放数据集本身的额外信息。
    其次,这个TF目标检测API(用于从.xml转换为.tfrecord)的教程 (Training Custom Object Detector — TensorFlow 2 Object Detection API tutorial documentation还展示了一些适用于我们的例子的步骤。
此练习需要对上述文档(可能还有其他文档)进行一些研究,以获得转换后的文件。

额外的资源
使用来自TensorFlow文档的TFRecord和tf.train.Example(https://www.tensorflow.org/tutorials/load_data/tfrecord)
在接下来的练习时,可以参考上述文档。

11.练习3 -创建tf记录

目的
本练习的目的是熟悉tf记录格式。具体来说,是将Waymo Open Dataset中的数据转换为Tensorflow目标检测API使用的tf记录格式。作为一名机器学习工程师,需要将数据集从一种格式转换为另一种格式,这是一个很好的例子。

细节
这里(https://waymo.com/open/data/perception/阅读更多关于Waymo Open Dataset数据格式的信息。每个tf记录文件包含了汽车整个行程的数据,这意味着它包含了来自不同摄像头的图像以及激光雷达数据。因为希望保持数据集较小,所以执行create_tf_example函数来创建清理过的tf记录文件。

说明-使用Waymo Open Dataset github存储库来解析原始tf记录文件。建议遵循本教程(GitHub - waymo-research/waymo-open-dataset: Waymo Open Dataset来更好地理解数据格式,然后再开始这个练习。

提示
文档(GitHub - Jossome/Waymo-open-dataset-document: The official document of the dataset is too general. There are many missing details. This document aims at making it more convenient to use the dataset.提供了数据集结构的概述。

稍后,利用Tensorflow目标检测API来训练目标检测模型。在API教程中,可以找到create_tf_example的示例(Training Custom Object Detector — TensorFlow 2 Object Detection API tutorial documentation

注意,运行该代码将需要使用/home/workspace目录中包含的example .tfrecord。

参考代码

import io
import os

import tensorflow.compat.v1 as tf
from PIL import Image
from waymo_open_dataset import dataset_pb2 as open_dataset

from utils import parse_frame, int64_feature, int64_list_feature, bytes_feature \
    bytes_list_feature, float_list_feature


def create_tf_example(filename, encoded_jpeg, annotations):
    """
    convert to tensorflow object detection API format
    args:
    - filename [str]: name of the image
    - encoded_jpeg [bytes-likes]: encoded image
    - annotations [list]: bboxes and classes
    returns:
    - tf_example [tf.Example]
    """
    encoded_jpg_io = io.BytesIO(encoded_jpeg)
    image = Image.open(encoded_jpg_io)
    width, height = image.size

    mapping = {1: 'vehicle', 2: 'pedestrian', 4: 'cyclist'}
    image_format = b'jpg'
    xmins = []
    xmaxs = []
    ymins = []
    ymaxs = []
    classes_text = []
    classes = []
    filename = filename.encode('utf8')

    for ann in annotations:
        xmin, ymin = ann.box.center_x - 0.5 * ann.box.length, 
                     ann.box.center_y - 0.5 * ann.box.width
        xmax, ymax = ann.box.center_x + 0.5 * ann.box.length,  
                     ann.box.center_y + 0.5 * ann.box.width
        xmins.append(xmin / width)
        xmaxs.append(xmax / width)
        ymins.append(ymin / height)
        ymaxs.append(ymax / height)    
        classes.append(ann.type)
        classes_text.append(mapping[ann.type].encode('utf8'))

    tf_example = tf.train.Example(features=tf.train.Features(feature={
        'image/height': int64_feature(height),
        'image/width': int64_feature(width),
        'image/filename': bytes_feature(filename),
        'image/source_id': bytes_feature(filename),
        'image/encoded': bytes_feature(encoded_jpeg),
        'image/format': bytes_feature(image_format),
        'image/object/bbox/xmin': float_list_feature(xmins),
        'image/object/bbox/xmax': float_list_feature(xmaxs),
        'image/object/bbox/ymin': float_list_feature(ymins),
        'image/object/bbox/ymax': float_list_feature(ymaxs),
        'image/object/class/text': bytes_list_feature(classes_text),
        'image/object/class/label': int64_list_feature(classes),
    }))
    return tf_example


def process_tfr(path):
    """
    process a waymo tf record into a tf api tf record
    """
    # create processed data dir
    file_name = os.path.basename(path)

    logger.info(f'Processing {path}')
    writer = tf.python_io.TFRecordWriter(f'{dest}/{file_name}')
    dataset = tf.data.TFRecordDataset(path, compression_type='')
    for idx, data in enumerate(dataset):
        frame = open_dataset.Frame()
        frame.ParseFromString(bytearray(data.numpy()))
        encoded_jpeg, annotations = parse_frame(frame)
        filename = file_name.replace('.tfrecord', f'_{idx}.tfrecord')
        tf_example = create_tf_example(filename, encoded_jpeg, annotations)
        writer.write(tf_example.SerializeToString())
    writer.close()


if __name__ == "__main__": 
    parser = argparse.ArgumentParser()
    parser.add_argument('-p', '--path', required=True, type=str,
                        help='Waymo Open dataset tf record')
    args = parser.parse_args()  
    process_tfr(args.path)

其它资源

 https://www.tensorflow.org/tutorials/load_data/tfrecord

12.Model Selection

ML工程师往往对创造新模型非常兴奋,然而,在深入到ML工作流的这一步之前,必须通过建立基线(baselines)来设定现实的期望

下基线可以了解最低的预期性能。如果得到的指标低于这样的基线,那么就发出一个危险的信号,应该担心培训管道(pipeline)出了问题。例如,对于一个分类问题,随机猜测基线是一个很好的下限。给定C类,你的算法的准确性应该高于1/C。

上基线可以了解期望的最大性能。如果客户要求提供一种100%正确分类图像的算法,可以放心地让他们知道这不会发生。人类的表现是一个很好的上限基线。对于分类问题,应该尝试手动分类100个图像,以了解算法可以达到什么样的性能水平。

模型选择是ML工作流的一个动态部分,它需要多次迭代。除非对该任务有一些先验知识,否则建议从简单的模型开始,并迭代复杂性。请记住,在此阶段验证集应该保持不变!

13.误差分析

验证集度量指标是模型全局性能的一个很好的指示器,但是我们经常需要更好的理解。例如,像精确度这样的指标不会告诉你某一类物体是否总是被错误分类。由于这些原因,必须在迭代模型之前进行深入的错误分析。

基于度量指标或损失值对预测进行排序,始终是识别错误模式的一种有用方法。

14.总结

机器学习的工作流程如下:

确定问题的框架:理解利害关系,并定义相关的指标。
理解数据:执行探索性数据分析,并从数据集中提取模式。
对模型进行迭代:创建验证集,建立基线,并对模型进行从简单到复杂的迭代。

15.术语

Accuracy(准确性):分类度量指标。
Bias-Variance tradeoff(偏差-方差权衡):如何创建一个性能良好的ML模型,同时保持对新数据的推广能力的挑战。
Cross validation(交叉验证):评估模型的一组技术
Domain gap(域差):数据集分布之间的差异。
Exploratory Data Analysis(探索性数据分析(EDA)):分析一个新的数据集并提取任何与ML问题相关的信息的过程。
Inference time(推断时间):模型输出预测所需的时间。
Overfitting(过拟合):当一个模型在数据集上有很好的性能,但不能很好地推广时
Precision(精度):正确分类实例的数量除以这些实例的数量。
Recall(召回率):正确分类的图片数量除以图片总数。
TfRecord: Tensorflow自定义数据格式。
Training set(训练集):用于训练ML模型的数据集。
Validation set(验证集):用于评估ML模型性能的数据集。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值