8个计算机视觉深度学习中常见的Bug
副标题[/!--empirenews.page--]
给大家总结了8个计算机视觉深度学习中的常见bug,相信大家或多或少都遇到过,希望能帮助大家避免一些问题。 人是不完美的,我们经常在软件中犯错误。有时这些错误很容易发现:你的代码根本不能工作,你的应用程序崩溃等等。但是有些bug是隐藏的,这使得它们更加危险。 在解决深度学习问题时,由于一些不确定性,很容易出现这种类型的bug:很容易看到web应用程序路由请求是否正确,而不容易检查你的梯度下降步骤是否正确。然而,有很多错误是可以避免的。 我想分享一些我的经验,关于我在过去两年的计算机视觉工作中看到或制造的错误。我(在会议上)谈到过这个话题(https://datafest.ru/ia/),很多人在会后告诉我:“是的,我也有很多这样的bug。”我希望我的文章可以帮助你至少避免其中的一些问题。 1. 翻转图片以及关键点. 假设在关键点检测的问题上。数据看起来像一对图像和一系列的关键点元组。其中每个关键点是一对x和y坐标。 让我们对这个数据进行基础的增强: def flip_img_and_keypoints(img: np.ndarray, kpts: Sequence[Sequence[int]]): img = np.fliplr(img) h, w, *_ = img.shape kpts = [(y, w - x) for y, x in kpts] return img, kpts 看起来是正确的,嗯?我们把它可视化。 image = np.ones((10, 10), dtype=np.float32) kpts = [(0, 1), (2, 2)] image_flipped, kpts_flipped = flip_img_and_keypoints(image, kpts) img1 = image.copy() for y, x in kpts: img1[y, x] = 0 img2 = image_flipped.copy() for y, x in kpts_flipped: img2[y, x] = 0
_ = plt.imshow(np.hstack((img1, img2))) 不对称,看起来很奇怪!如果我们检查极值呢? image = np.ones((10, 10), dtype=np.float32) 不好!这是一个典型的off-by-one错误。正确的代码是这样的: def flip_img_and_keypoints(img: np.ndarray, kpts: Sequence[Sequence[int]]): img = np.fliplr(img) h, w, *_ = img.shape kpts = [(y, w - x - 1) for y, x in kpts] return img, kpts 我们通过可视化发现了这个问题,但是,使用“x = 0”点进行单元测试也会有所帮助。一个有趣的事实是:有一个团队中有三个人(包括我自己)独立地犯了几乎相同的错误。 2. 继续是关键点相关的问题 即使在上面的函数被修复之后,仍然存在危险。现在更多的是语义,而不仅仅是一段代码。 假设需要用两只手掌来增强图像。看起来很安全:手是左,右翻转。 但是等等!我们对关键点的语义并不很了解。如果这个关键点的意思是这样的: kpts = [ (20, 20), # left pinky (20, 200), # right pinky ... ] 这意味着增强实际上改变了语义:左变成右,右变成左,但我们不交换数组中的关键点索引。它会给训练带来大量的噪音和更糟糕的度量。 我们应该吸取一个教训: 在应用增强或其他花哨的功能之前,了解并考虑数据结构和语义 保持你的实验原子性:添加一个小的变化(例如一个新的变换),检查它如何进行,如果分数提高才加进去。 3. 编写自己的损失函数 熟悉语义分割问题的人可能知道IoU指标。不幸的是,我们不能直接用SGD来优化它,所以常用的方法是用可微损失函数来近似它。 def iou_continuous_loss(y_pred, y_true): eps = 1e-6 def _sum(x): return x.sum(-1).sum(-1) numerator = (_sum(y_true * y_pred) + eps) denominator = (_sum(y_true ** 2) + _sum(y_pred ** 2) - _sum(y_true * y_pred) + eps) return (numerator / denominator).mean() 看起来不错,我们先做个小的检查: In [3]: ones = np.ones((1, 3, 10, 10)) ...: x1 = iou_continuous_loss(ones * 0.01, ones) ...: x2 = iou_continuous_loss(ones * 0.99, ones) In [4]: x1, x2 Out[4]: (0.010099999897990103, 0.9998990001020204) 在 x1中,我们计算了一些与ground truth完全不同的东西的损失,而 x2则是非常接近ground truth的东西的结果。我们预计 x1会很大,因为预测是错误的, x2应该接近于零。怎么了? 上面的函数是对metric的一个很好的近似。metric不是一种损失:它通常(包括这种情况)越高越好。当我们使用SGD来最小化损失时,我们应该使用一些相反的东西: def iou_continuous(y_pred, y_true): eps = 1e-6 def _sum(x): return x.sum(-1).sum(-1) numerator = (_sum(y_true * y_pred) + eps) denominator = (_sum(y_true ** 2) + _sum(y_pred ** 2) - _sum(y_true * y_pred) + eps) return (numerator / denominator).mean() def iou_continuous_loss(y_pred, y_true): return 1 - iou_continuous(y_pred, y_true) 这些问题可以从两个方面来确定: 编写一个单元测试,检查损失的方向:形式化的期望,更接近ground truth应该输出更低的损失。 运行一个健全的检查,让你的模型在单个batch中过拟合。 4. 当我们使用Pytorch的时候 假设有一个预先训练好的模型,开始做infer。 from ceevee.base import AbstractPredictor class MySuperPredictor(AbstractPredictor): def __init__(self, weights_path: str, ): super().__init__() self.model = self._load_model(weights_path=weights_path) def process(self, x, *kw): with torch.no_grad(): res = self.model(x) return res @staticmethod def _load_model(weights_path): model = ModelClass() weights = torch.load(weights_path, map_location='cpu') model.load_state_dict(weights) return model 这个代码正确吗?也许!这确实适用于某些模型。例如,当模型没有dropout或norm层,如 torch.nn.BatchNorm2d。或者当模型需要为每个图像使用实际的norm统计量时(例如,许多基于pix2pix的架构需要它)。 但是对于大多数计算机视觉应用程序来说,代码忽略了一些重要的东西:切换到评估模式。 如果试图将动态PyTorch图转换为静态PyTorch图,,这个问题很容易识别。 torch.jit用于这种转换。 In [3]: model = nn.Sequential( ...: nn.Linear(10, 10), ...: nn.Dropout(.5) ...: ) ...: ...: traced_model = torch.jit.trace(model, torch.rand(10)) (编辑:均轻资讯网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |