コンピュータビジョンディープラーニングにおける8つのよくあるバグ

コンピュータビジョンディープラーニングにおける8つのよくあるバグ

コンピューター ビジョンのディープラーニングでよくある 8 つのバグをまとめました。誰もが多かれ少なかれこれらのバグに遭遇したことがあると思います。問題を回避するのに役立つことを願っています。

[[285233]]

人間は不完全であり、ソフトウェアでは間違いを犯すことがよくあります。コードが機能しない、アプリがクラッシュするなど、これらのエラーは簡単に見つけられる場合もあります。しかし、いくつかのバグは隠れており、さらに危険になっています。

このタイプのバグは、不確実性のためにディープラーニングの問題を解決するときに簡単に発生します。Web アプリケーションがリクエストを正しくルーティングしているかどうかを確認するのは簡単ですが、勾配降下法の手順が正しいかどうかを確認するのは簡単ではありません。しかし、多くの間違いは回避できます。

過去 2 年間のコンピューター ビジョンの仕事で私が見たり、犯したりした間違いに関する経験をいくつか共有したいと思います。私はこのトピックについて(カンファレンスで)講演しましたが、その後多くの人から「私も同じバグにたくさん遭遇しました」と言われました。この記事が、皆さんがこれらの問題の少なくとも一部を回避するのに役立つことを願っています。

1. 画像とキーポイントを反転します。

キーポイント検出の問題を考えてみましょう。データは、画像とキーポイント タプルのシーケンスのペアのように見えます。各キーポイントは x 座標と y 座標のペアです。

このデータに基本的な拡張を加えてみましょう。

  1. def flip_img_and_keypoints(img: np.ndarray, kpts:シーケンス[シーケンス[ int ]]):
  2. 画像 = np.fliplr(画像)
  3. h、w、*_ = 画像の形状
  4. kpts = [(y, w - x) y、xkpts]
  5. img、kptsを返す

正しいように見えますね?視覚化してみましょう。

  1. 画像 = np.ones((10, 10), dtype=np.float32)
  2. kpts = [(0, 1), (2, 2)]
  3. image_flipped、kpts_flipped = flip_img_and_keypoints(画像、kpts)
  4. img1 = 画像.コピー()
  5. y、xは kpts場合:
  6. 画像1[y, x] = 0
  7. img2 = image_flipped.copy()
  8. kpts_flippedy、xについて:
  9. 画像2[y, x] = 0
  10.   
  11. _ = plt.imshow(np.hstack((img1, img2)))

非対称で変ですね!極端な値をチェックしたらどうなるでしょうか?

  1. 画像 = np.ones((10, 10), dtype=np.float32)

いいえ!これは典型的な「一つ間違えた」ミスです。正しいコードは次のとおりです。

  1. def flip_img_and_keypoints(img: np.ndarray, kpts:シーケンス[シーケンス[ int ]]):
  2. 画像 = np.fliplr(画像)
  3. h、w、*_ = 画像の形状
  4. kpts = [(y, w - x - 1) y、xkpts場合]
  5. img、kptsを返す

この問題は視覚化によって発見されましたが、「x = 0」ポイントを使用した単体テストも役立ちます。面白い事実: 1 つのチームの 3 人 (私を含む) がそれぞれほぼ同じ間違いを犯しました。

2. 重要なポイントに関連する質問を続けます

上記の機能が修正された後でも、危険性は依然として存在します。今では、単なるコードではなく、セマンティクスが重要になっています。

画像を強調するには 2 つの手のひらが必要であるとします。安全そうです: 手は左右反転しています。


しかし、待ってください。キーポイントのセマンティクスについてはあまりわかっていません。このキーポイントが次のことを意味する場合:

  1. kpts = [
  2. (20, 20)、#小指
  3. (20, 200)、#小指
  4. ...
  5. ]

つまり、拡張によって実際にセマンティクスが変わります。左が右になり、右が左になりますが、配列内のキーポイント インデックスは交換されません。トレーニングに多くのノイズが導入され、メトリックが悪化します。

私たちは教訓を学ぶべきだ:

  • 拡張機能やその他の高度な機能を適用する前に、データ構造とセマンティクスを理解して考慮する
  • 実験をアトミックに保ちます。小さな変更 (新しい変換など) を追加し、そのパフォーマンスを確認し、スコアが向上した場合にのみ追加します。

3. 独自の損失関数を書く

セマンティックセグメンテーションの問題に精通している人は、IoU メトリックを知っているかもしれません。残念ながら、SGD を使用して直接最適化することはできないため、微分可能な損失関数を使用して近似するのが一般的なアプローチです。

  1. iou_continuous_loss(y_pred, y_true)を定義します。
  2. eps = 1e-6
  3. _sum(x)を定義します:
  4. x.sum (-1).sum ( -1 )を返す
  5. 分子 = (_sum(y_true * y_pred) + eps)
  6. 分母 = (_sum(y_true ** 2) + _sum(y_pred ** 2)
  7. - _sum(y_true * y_pred) + eps)
  8. (分子 / 分母).mean()を返します

良さそうです。ちょっと確認してみましょう:

  1. [3]において: ones = np.ones((1, 3, 10, 10))
  2. ...: x1 = iou_continuous_loss(ones * 0.01, ones)
  3. ...: x2 = iou_continuous_loss(ones * 0.99, ones)
  4. [4]では:x1、x2
  5. アウト[4]: (0.010099999897990103, 0.9998990001020204)

x1 では、真実とはまったく異なるものに対する損失を計算しましたが、x2 は真実に非常に近いものの結果です。予測が間違っていたため、x1 は大きくなり、x2 はゼロに近くなると予想されます。どうしたの?

上記の関数はメトリックの良い近似値です。メトリックは損失ではありません。通常、(この場合も含め) メトリックが高いほど良いです。損失を最小限に抑えるために SGD を使用する場合は、逆のことを使用する必要があります。

  1. iou_continuous(y_pred, y_true)を定義します。
  2. eps = 1e-6
  3. _sum(x)を定義します:
  4. x.sum (-1).sum ( -1 )を返す
  5. 分子 = (_sum(y_true * y_pred) + eps)
  6. 分母 = (_sum(y_true ** 2) + _sum(y_pred ** 2)
  7. - _sum(y_true * y_pred) + eps)
  8. (分子 / 分母).mean()を返します
  9. iou_continuous_loss(y_pred, y_true)を定義します。
  10. 1を返す- iou_continuous(y_pred, y_true)

これらの問題は、次の 2 つの観点から特定できます。

  • 損失の方向を確認する単体テストを記述します。つまり、真実に近いほど損失が低くなるという期待を形式化します。
  • 健全性チェックを実行して、モデルが単一のバッチで過剰適合していないかどうかを確認します。

4. Pytorchを使うとき

事前にトレーニングされたモデルがあると仮定して、推論を開始します。

  1. ceevee.baseからAbstractPredictor をインポートします
  2. クラス MySuperPredictor(AbstractPredictor):
  3. デフ__init__(self,
  4. weights_path: 文字列、
  5. ):
  6. スーパー().__init__()
  7. self.model = self._load_model(weights_path = weights_path) です。
  8. defプロセス(self, x, *kw):
  9. torch.no_grad()の場合:
  10. res = 自己.モデル(x)
  11. 戻り
  12. @静的メソッド
  13. def _load_model(重みパス):
  14. モデル = モデルクラス()
  15. 重み = torch.load (weights_path、map_location = 'cpu' )
  16. model.load_state_dict(重み)
  17. リターンモデル

このコードは正しいでしょうか? おそらくそうです! これは一部のモデルでは機能します。たとえば、torch.nn.BatchNorm2d など、モデルにドロップアウト レイヤーまたはノルム レイヤーがない場合などです。または、モデルが各画像に実際のノルム統計を使用することを要求する場合 (たとえば、多くの pix2pix ベースのアーキテクチャではそれが必要です)。

しかし、ほとんどのコンピューター ビジョン アプリケーションでは、評価モードへの切り替えという重要な点がコードで見落とされています。

動的 PyTorch グラフを静的 PyTorch グラフに変換しようとすると、この問題は簡単に識別できます。この変換には torch.jit が使用されます。

  1. [3]では: model = nn.Sequential(
  2. ...: nn.Linear(10, 10)、
  3. ...: nn.ドロップアウト(.5)
  4. ...: )
  5. ...:
  6. ...: traced_model = torch.jit.trace(モデル、torch.rand(10))
  7. /Users/Arseny/.pyenv/versions/3.6.6/lib/python3.6/site-packages/torch/jit/__init__.py:914: TracerWarning: Trace には非決定的なノードがありました。モデル.eval() を呼び出すのを忘れましたか? ノード:
  8. %12 : Float (10) = aten::dropout(%input, %10, %11)、スコープ: Sequential/Dropout[1] # /Users/Arseny/.pyenv/versions/3.6.6/lib/python3.6/site-packages/torch/nn/ functional.py:806:0
  9. これにより、トレースチェックエラーが発生する可能性がありますトレースチェックを無効にするには、check_trace= Falseを渡します。   torch.jit.trace()
  10. check_tolerance、_force_outplace、 True 、_module_class)
  11. /Users/Arseny/.pyenv/versions/3.6.6/lib/python3.6/site-packages/torch/jit/__init__.py:914: TracerWarning:トレースされた関数出力番号 1 が対応する出力と一致しませ  Python関数詳細なエラー:
  12. 許容範囲rtol=1e-05 atol=1e-05入力[5] (0.0 vs. 0.5454154014587402)およびその他5箇所 (60.00%)
  13. check_tolerance、_force_outplace、 True 、_module_class)

簡単な修正:

  1. [4]では:model = nn.Sequential(
  2. ...: nn.Linear(10, 10)、
  3. ...: nn.ドロップアウト(.5)
  4. ...: )
  5. ...:
  6. ...: traced_model = torch.jit.trace(model.eval(), torch.rand(10))
  7. # 警告はもうありません!

この場合、torch.jit.trace はモデルを複数回実行し、結果を比較します。ここでの区別は疑わしい。

ただし、torch.jit.trace はここでは万能薬ではありません。これは知っておいて覚えておくべきニュアンスです。

5. コピー&ペーストの問題

多くのものはペアになっています: トレーニングと検証、幅と高さ、緯度と経度など...

  1. def make_dataloaders(train_cfg、val_cfg、batch_size):
  2. トレーニング = Dataset.from_config(train_cfg)
  3. val = Dataset.from_config(val_cfg)
  4. shared_pa​​rams = { 'batch_size' : batch_size、 'shuffle' : True 'num_workers' : cpu_count()}
  5. トレーニング = DataLoader(トレーニング、**shared_pa​​rams)
  6. val = DataLoader(train, **shared_pa​​rams)
  7. 帰りの電車、val

愚かなミスを犯したのは私だけではありませんでした。たとえば、非常に人気のあるアルバム ライブラリにも同様のバージョンがあります。

  1. # https://github.com/albu/albumentations/blob/0.3.0/albumentations/augmentations/transformations.py
  2. def apply_to_keypoint(self、キーポイント、crop_height=0、crop_width=0、h_start=0、w_start=0、 rows =0、cols=0、**params):
  3. キーポイント = F.keypoint_random_crop(キーポイント、クロップ高さ、クロップ幅、h_start、w_start、行数、列数)
  4. scale_x = 自己幅 / クロップ高さ
  5. scale_y = self.height / crop_height
  6. キーポイント = F.keypoint_scale(キーポイント、スケールx、スケールy)
  7. キーポイントを返す

心配しないでください。修正されました。

回避するにはどうすればよいでしょうか? コードをコピーして貼り付けるのではなく、コピーして貼り付ける必要がない方法でコードを記述するようにしてください。

  1. データセット = []
  2. data_a = get_dataset(MyDataset(config[ 'dataset_a' ]), config[ 'shared_pa​​ram' ], param_a)
  3. データセット.append(data_a)
  4. data_b = get_dataset(MyDataset(config[ 'dataset_b' ]), config[ 'shared_pa​​ram' ], param_b)
  5. データセット.append(data_b)
  1. データセット = []
  2. のために 名前、zip内のパラメータ(( 'dataset_a' 'dataset_b' )、
  3. (パラメータa、パラメータb)、
  4. ):
  5. データセットの追加(get_dataset(MyDataset(config[ name ]), config[ 'shared_pa​​ram' ], param))

6. 適切なデータ型

新しい拡張機能を書いてみましょう:

  1. def add_noise(img: np.ndarray) -> np.ndarray:
  2. マスク = np.random.rand(*img.shape) + .5
  3. img = img.astype( 'float32' ) * マスク
  4. img.astype( 'uint8' )を返す

画像が変更されました。これは私たちが予想していた通りでしょうか? まあ、あまりにも変化しすぎているのかもしれません。

ここでは、float32 を uint8 に変換するという危険な操作が行われます。オーバーフローが発生する可能性があります:

  1. def add_noise(img: np.ndarray) -> np.ndarray:
  2. マスク = np.random.rand(*img.shape) + .5
  3. img = img.astype( 'float32' ) * マスク
  4. np.clip(img, 0, 255).astype( 'uint8' )を返します
  5. img = add_noise(cv2.imread( 'two_hands.jpg' )[:, :, ::-1])
  6. _ = plt.imshow(画像)

見た目がずっと良くなりましたね?

ちなみに、この問題を回避する方法もあります。車輪の再発明はせず、拡張コードを最初から書かず、既存の拡張機能である albumentations.augmentations.transforms.GaussNoise を使用します。

私はかつて、同じ原因による別のバグに取り組んだことがあります。

  1. raw_mask = cv2.imread( 'mask_small.png' )
  2. マスク = raw_mask.astype( 'float32' ) / 255
  3. マスク = cv2.resize(マスク、(64, 64)、補間=cv2.INTER_LINEAR)
  4. マスク = cv2.resize(マスク、(128, 128)、補間=cv2.INTER_CUBIC)
  5. マスク = (マスク * 255).astype( 'uint8' )
  6. _ = plt.imshow(np.hstack((raw_mask, マスク)))

ここで何が間違っていたのでしょうか? まず第一に、キュービック補間を使用してマスクのサイズを変更するのは悪い考えです。 float32 から uint8 への変換でも同じ問題が発生します。3 次補間により入力よりも大きな値が出力される可能性があり、オーバーフローが発生します。


私は視覚化を行っていたときにこの問題を発見しました。トレーニング ループ全体にアサーションを配置することもお勧めします。

7. スペルミス

完全な畳み込みネットワーク (セマンティックセグメンテーション問題など) と巨大な画像に対して推論を実行する必要があるとします。画像が非常に大きいため、GPU に収まらない場合があります。医療画像や衛星画像である可能性があります。

この場合、画像をグリッドに分割し、各パッチに対して個別に推論を実行し、最後にそれらを結合することができます。さらに、いくつかの予測クロスオーバーは境界付近のアーティファクトを滑らかにするのに役立つ場合があります。

  1. tqdmからtqdm をインポート
  2. クラス GridPredictor:
  3. 「」 「
  4. このクラスは大きな画像セグメンテーションマスクを予測するために使用できます
  5. GPUメモリ制限がある場合
  6. 「」 「
  7. def __init__(self, 予測子: AbstractPredictor,サイズ: int , ストライド: Optional[ int ] = None):
  8. self.predictor = 予測子
  9. 自己.size =サイズ 
  10. self.stride = stride (stride はない サイズ// 2
  11. def __call__(self, x: np.ndarray):
  12. h, w, _ = x.shape
  13. マスク = np.zeros((h, w, 1), dtype= 'float32' )
  14. 重み = mask.copy()
  15. itqdm(range(0, h - 1, self.stride))場合:
  16. j範囲(0, w - 1, self.stride)内にある場合:
  17. a、b、c、d = i、最小値(h、i + 自己サイズ)、j、最小値(w、j + 自己サイズ)
  18. パッチ = x[a:b, c:d, :]
  19. マスク[a:b, c:d, :] += np.expand_dims(self.predictor(patch), -1)
  20. 重み[a:b, c:d, :] = 1
  21. マスク/重みを返す

シンボルのタイプミスがありますが、コード スニペットは十分に大きいため、簡単に見つけることができます。コードを見ただけではすぐに識別できるとは思えません。ただし、コードが正しいかどうかを確認するのは簡単です。

  1. クラス Model(nn.Module):
  2. def forward (self, x):
  3. x.mean(axis=-1)を返します
  4. モデル = モデル()
  5. grid_predictor = GridPredictor(モデル、サイズ= 128、ストライド = 64)
  6. simple_pred = np.expand_dims(モデル(画像), -1)
  7. グリッド予測 = グリッド予測子(画像)
  8. np.testing.assert_allclose(シンプル_pred、グリッド_pred、atol=.001)
  9. ---------------------------------------------------------------------------  
  10. AssertionError トレースバック (最新の呼び出しが最後)
  11. <ipython-input-24-a72034c717e9> は<module>あります
  12. 9 グリッド予測 = グリッド予測子(画像)
  13. 10
  14. ---> 11 np.testing.assert_allclose(simple_pred、grid_pred、atol=.001)  
  15. ~/.pyenv/versions/3.6.6/lib/python3.6/site-packages/numpy/testing/_private/utils.pyassert_allclose(actual、desired、rtol、atol、equal_nan、err_msg、verbose)を実行します。
  16. 1513 ヘッダー = '許容値 rtol=%g、atol=%g と等しくありません' % (rtol、atol)
  17. 1514 assert_array_compare(比較、実際、望ましい、err_msg=str(err_msg)、
  18. -> 1515 verbose=詳細、header=ヘッダー、equal_nan=equal_nan)
  19. 1516
  20. 1517
  21. ~/.pyenv/versions/3.6.6/lib/python3.6/site-packages/numpy/testing/_private/utils.pyassert_array_compare(comparison, x, y, err_msg, verbose, header, precision , equal_nan, equal_inf)
  22. 839 verbose=詳細、header=ヘッダー、
  23. 840 名前 = ( 'x' 'y' )、精度=精度)
  24. --> 841 AssertionError(msg) を発生させる 
  25. 842 ValueErrorを除く:
  26. 843 インポート トレースバック
  27. アサーションエラー:
  28. 許容値 rtol=1e-07、atol=0.001等しくありません
  29. 不一致: 99.6%
  30. マックス 絶対差: 765。
  31. マックス 相対差: 0.75000001
  32. x: 配列([[[215.333333],
  33. [192.666667],
  34. [250. ]、...
  35. y: 配列([[[ 215.33333],
  36. [192.66667],
  37. [ 250. ]、...

__call__ メソッドの正しいバージョンは次のとおりです。

  1. def __call__(self, x: np.ndarray):
  2. h, w, _ = x.shape
  3. マスク = np.zeros((h, w, 1), dtype= 'float32' )
  4. 重み = mask.copy()
  5. itqdm(range(0, h - 1, self.stride))場合:
  6. j範囲(0, w - 1, self.stride)内にある場合:
  7. a、b、c、d = i、最小値(h、i + 自己サイズ)、j、最小値(w、j + 自己サイズ)
  8. パッチ = x[a:b, c:d, :]
  9. マスク[a:b, c:d, :] += np.expand_dims(self.predictor(patch), -1)
  10. 重み[a:b, c:d, :] += 1
  11. マスク/重みを返す

それでも問題がわからない場合は、線の太さ[a:b,c:d,:]+=1に注意してください。

8. イメージネット正規化

転移学習を行う必要がある場合は、通常、Imagenet をトレーニングするときと同じ方法で画像を正規化するのがよいでしょう。

すでに使い慣れている albumentations ライブラリを使用しましょう。

  1. アルバムからのインポート正規化
  2. ノルム = 正規化()
  3. 画像 = cv2.imread( 'img_small.jpg' )
  4. マスク = cv2.imread( 'mask_small.png' , cv2.IMREAD_GRAYSCALE)
  5. mask = np.expand_dims(mask, -1) # 形状 (64, 64) -> 形状 (64, 64, 1)
  6. normed = norm(画像=img、マスク=mask)
  7. img, mask = [normed[x] for x in [ 'image' , 'mask' ]]
  8. img_to_batch(x)を定義します。
  9. x = np.transpose(x, (2, 0, 1)).astype( 'float32' )
  10. torch.from_numpy(np.expand_dims(x, 0))を返します
  11. img、マスク = map(img_to_batch、(img、マスク))
  12. 基準 = F.binary_cross_entropy

ここで、ネットワークをトレーニングし、単一の画像にオーバーフィットさせます。前述したように、これは優れたデバッグ手法です。

  1. モデル_a = UNet(3, 1)
  2. オプティマイザー = torch.optim.Adam(model_a.parameters(), lr=1e-3)
  3. 損失 = []
  4. tqdm(range(20))内のtについて:
  5. 損失 = 基準(model_a(img), マスク)
  6. 損失.append(損失.item())
  7. オプティマイザ.zero_grad()
  8. 損失.後方()
  9. オプティマイザ.ステップ()
  10.   
  11. _ = plt.plot(損失)

曲率は良好に見えますが、クロスエントロピー損失値が -300 というのは予想外です。何が問題ですか?

画像の正規化は正常に機能しますが、マスクは機能しません。手動で [0,1] にスケーリングする必要があります。

  1. モデルb = UNet(3, 1)
  2. オプティマイザー = torch.optim.Adam(model_b.parameters(), lr=1e-3)
  3. 損失 = []
  4. tqdm(range(20))内のtについて:
  5. 損失 = 基準(model_b(img), マスク / 255.)
  6. 損失.append(損失.item())
  7. オプティマイザ.zero_grad()
  8. 損失.後方()
  9. オプティマイザ.ステップ()
  10.   
  11. _ = plt.plot(損失)

トレーニング ループの単純なランタイム アサーション (例: assertMask.max() <= 1) により、問題がすぐに検出されます。同様に、これは単体テストにもなります。

要約する

  • テストは必要
  • ランタイムアサーションはトレーニング パイプラインで使用できます。
  • 視覚化は一種の幸福である
  • コピー&ペーストは呪い
  • 特効薬というものはなく、機械学習エンジニアは常に注意しなければなりません(そうでなければ苦しむだけです)。

<<:  人工知能市場の需要と応用

>>:  このアルゴリズムはアーキテクチャを自動的に最適化し、エンジニアがニューラルネットワークを設計するのに役立ちます。

ブログ    

推薦する

GPT-5 も 4.5 もなく、2 か月後の OpenAI の最初の開発者会議では何がリリースされるのでしょうか?

朗報です。開発者が待ち望んでいた GPT-5 がついに登場しました。本日、OpenAIは初の開発者会...

人工知能統計調査:AIの普及により1億2000万人の労働者が再訓練を必要とする

AI の健全性と進歩に関する最近の調査、研究、予測、その他の定量的評価では、労働力の再訓練の必要性、...

AI人材の世界的な需要が急増、一部の職種では年間40万ドル近くを稼ぐ

6月19日のニュース:AI産業の急速な発展に伴い、テクノロジー業界のAI人材に対する需要も高まってい...

DeepMindとハーバード大学がAI「モルモット」を開発:餌探しからバッティングまでニューラルネットワークの謎を探る

マウスを研究するのと同じ方法で AI を研究できるでしょうか?多分。 ICLR 2020 Spotl...

スマートシティ:自動運転インフラの新たな一面

日常的なタスクの自動化は、現在多くの業界で関心を集めているコンセプトです。最も革命的なのは自動運転車...

...

...

マーケティングにおける AI についての考え方を変える 10 のグラフ

Adobe の最新の Digital Intelligence Briefing によると、トップク...

...

FacebookはAI音声アシスタントを開発しているが、財務上の将来は不透明

Facebook は近年、世論の嵐に何度も巻き込まれてきたが、技術革新に関しては決して無縁ではなかっ...

アンサンブル法からニューラルネットワークまで:自動運転技術で使用される機械学習アルゴリズムとは?

現在、機械学習アルゴリズムは、自動運転車業界で増加している問題を解決するために大規模に使用されていま...

人工知能は大量失業を引き起こすでしょうか?中国人民政治協商会議全国委員会によるこの調査は、その答えを提供している。

2019年の注目産業よく知られている5G、ブロックチェーン、インターネットに加えてもう一つは人工知...

コードスイッチングに7億5000万ドル? Facebook TransCoder AI は 1 つで十分です。

コードの移行と言語の変換は困難で費用のかかる作業です。オーストラリア連邦銀行は、プラットフォームを ...

データ変換ツールにおけるAIの未来

人工知能はデータ変換ツールに革命をもたらし、効率、精度、リアルタイム処理を向上させます。シームレスな...

ラスベガスの「チャイナナイト」:中国の人工知能が外国人に人生への疑問を抱かせ始める!

CES は世界最大かつ最も影響力のある消費者向け電子機器展示会です。米国時間1月8日、ラスベガスで...