SmartMailHack。 ロゴ分類問題の1位のソリューション

2週間前、Mail.Ru GroupオフィスでのSmartMailHack学生ハッカソンは終了しました。 ハッカソンでは、3つのタスクの選択肢が提示されました。 2番目のタスクの勝者からの記事はすでにハブにありますが、最初のタスクで勝ったソリューションをチームに説明したいと思います。 すべてのコード例は、PythonおよびKeras(ディープラーニングの一般的なフレームワーク)で作成されます。

画像







タスクの説明



タスクは、さまざまな企業のロゴを分類することでした。 トレーニングデータセットは、161クラス(160の異なる企業+ラベル「その他」)にマークアップされた6139個の画像で構成されていました







ロゴ付きの画像の例













他のいくつか













トレーニング例の数の分類







データには2つの主な問題がありました。まず、通常の.jpegファイルと.pngファイルに加えて、データセットには.svg、.ico、さらには.gifがありました。







電車/アドビgif







OpenCVはjpegとpngのみを読み取るため、ハッカソン内で他のライブラリを処理する時間がないため、真っ先に行きました-ImageMagickを使用してすべてをjpegに変換し、gifから最初のフレームのみを残しました。

2番目の問題-サイズの大きな画像の広がり-はcv2.rescale()という行によって解決されました。これも明らかに最適なオプションではありませんが、高速で動作しています。







def _load_sample(self, sample_path): # try to load all files with opencv image = cv2.imread(sample_path) if image is not None: shape = image.shape # normal 3-channel image if shape[-1] == 3: image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # grayscale image -> RGB if len(shape) == 2 or shape[-1] == 1: image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) else: tqdm.write(f"Failed to load {sample_path}") return image def _prepare_sample(self, image): image = cv2.resize(image, (RESCALE_SIZE, RESCALE_SIZE)) return image
      
      





ロードと準備を行うImageLoaderクラスのメソッド







モデルの品質が評価されたターゲットメトリックは、F2メジャーです(通常のF1メジャーとは精度の前の係数が異なります)。







モデルと転移学習



過去6年間の画像の分類では、「古典的な」ツールは深い畳み込みニューラルネットワークです。 他の何かを試すことは意味がないことがすぐに明らかになったので、1つの質問がありました。畳み込みアーキテクチャをゼロからトレーニングするか、転移学習を使用するかということです。 2つ目は、keras.applicationsのImageNetモデルで事前にトレーニングされた動物園を使用して選択しました。







転移学習の主なアイデアは、いくつかの大規模なデータセット(この場合はImageNet、120万画像、1000クラス)で事前トレーニングされたニューラルネットワークを取得し、「ヘッド」(畳み込み層の後に来る完全に接続された分類器)を置き換えてから、既にモデルを再トレーニングすることですターゲットデータセット。 多くの場合、特にプリトレーニングとターゲットが実行されたデータセットが多かれ少なかれ類似している場合、最初からトレーニングするよりも優れたモデルが得られます。







 class PretrainedCLF: def __init__(self, clf_name, n_class): self.clf_name = clf_name self.n_class = n_class self.module_ = CLF2MODULE[clf_name] self.class_ = CLF2CLASS[clf_name] self.backbone = getattr(globals()[self.module_], self.class_) i = self._input() print(f"Using {self.class_} as backbone") backbone = self.backbone( include_top=False, weights='imagenet', pooling='max' ) x = backbone(i) out = self._top_classifier(x) self.model = Model(i, out) for layer in self.model.get_layer(self.clf_name).layers: layer.trainable = False @staticmethod def _input(): input_ = Input((RESCALE_SIZE, RESCALE_SIZE, 3)) return input_ def _top_classifier(self, x): x = Dense(512, activation='elu')(x) x = Dropout(0.3)(x) x = Dense(256, activation='elu')(x) x = Dropout(0.2)(x) out = Dense(self.n_class, activation='softmax')(x) return out
      
      





事前に訓練されたネットワークに基づいて分類子を収集するクラス







このようなモデルはさまざまな方法で再トレーニングできますが、標準的なアプローチを使用しました。最初に、事前トレーニング済みパーツは「凍結」され(トレーニング中に重みは変化しません)、完全に接続された分類器のみがいくつかの時代にトレーニングされ、その後すべてのレイヤーが解凍され、収束までモデルがトレーニングされます。 このようなスキームの背後にある直感は、最初は、ランダムに初期化された「ヘッド」からネットワークの主要部分に流れる大きな勾配により、畳み込み部分の重みが大きく変化し、事前トレーニングの効果が無効になることです。







ハッカソン



このハッカソンの特徴(たとえば、Kaggleでの競技とは異なります)は、パブリックリーダーボードがないことです。テストセット全体はプライベートであり、ハッカソンファイナルの2時間前に発行されました。 データセットの発行後1時間以内に2回送信することができました。その後、リーダーボードが表示され、もう1回送信すると結果を改善することができます。







ハッカソン中に何らかの方法でモデルの品質を評価するために、利用可能なデータセットを20/10/70の割合で3つの部分に分割しました。それぞれ、トレーニング、検証、テストです。 Kaggleの経験から、アンサンブルは通常、単一モデルよりもパフォーマンスが高いことがわかっています。







畳み込みネットワークのトレーニングにおける別の重要なポイントは、増強です:ランダムな回転、ノイズの追加、色の変化などによる利用可能な画像に基づいた新しいトレーニング例の生成 どれだけ正しかったのかわかりませんが、最終的にはガウスぼかしとガンマ変化のみを使用して、ターンとフリップを放棄することにしました。







 def _augment_sample(self, image): # gamma if np.random.rand() < 0.5: gamma = np.random.choice([0.5, 0.8, 1.2, 1.5]) image = adjust_gamma(image, gamma) # blur if np.random.rand() < 0.5: image = cv2.GaussianBlur(image, (3, 3), 0) return image
      
      





トレーニング例の増強を実装する方法







私たちはいくつかのマシンでネットワークを研究しました:Google Cloud上のTesla P100でインスタンスを撮影し、別のチームメンバーはTitan Xを搭載した研究室のコンピューターにアクセスし、残りはGoogle Colaboratory(Googleの無料のTesla K80)を使用しました。 テストデータの時点までに、約15の保存されたモデル(異なるパラメーターResNet-50、Xception、DesneNet-169、InceptionResNet-v2でトレーニングされ、各起動からいくつかのモデルが保存されました-前の時代のモデル+より正確なモデル検証)個人の20%テストでF2の平均値が〜0.8である。 それはすべて良さそうに見えましたが、推論の時間は限られており、すべての予測を生成して1つの提出物にまとめることは思ったよりも困難であることが判明しました。







推論中の問題



サイズのテストデータセットは、トレーニング1-6875ファイルとほぼ一致しており、クラスラベルを予測する必要がありました。 すべてのモデルを順番に写真を実行してブレンド(予測結果の平均化)を行うことを考えましたが、問題は非常に最初のステップであるjpegへの変換で始まりました。 トレーニングデータセットがスクリプトによって冷静に変換された場合、何らかの理由でテスト1ですべてが故障しました。一部のファイルは変換後に破損し、データの読み込み中に生成サブミットスクリプトがクラッシュしました。 これを処理している間、最初の1時間から約45分が経過し、その時間までに最初の送信を行う必要がありましたが、この間、変換の問題は解決しませんでした。 少なくとも何かを送信する必要があったので、壊れたファイルがそれほど多くなく、結果に大きな影響を与えないことを期待して、データロードに松葉杖を挿入し、すべての非カウント例をゼロにします:







 def _load_sample(self, sample_path): # try to load all files with opencv image = cv2.imread(sample_path) if image is not None: shape = image.shape # normal 3-channel image if shape[-1] == 3: image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # grayscale image -> RGB if len(shape) == 2 or shape[-1] == 1: image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) else: image = np.zeros((RESCALE_SIZE, RESCALE_SIZE, 3)) return image
      
      





その後、まったく何も残っていなかったので、最後にトレーニングしたモデルであるInceptionResNet-v2(個人テストを確認する時間すらありませんでした)からすぐに予測を取得し、結果にもかかわらず、提出物を収集して主催者に送信しました。すでにわずかに延期された期限まで。 数分後に私たちが1位になったことを見て、大きなマージン(0.77対0.673)で、私たちは少しリラックスして、残りの50分間は技術的な問題を確実に解決し、私たちが教えたものすべてをアンサンブルすることにしました







この時点で、変換に関する問題が解決したかのように(コードから松葉杖をまだ削除していませんでした)、保存したモデルを順番に読み込み、個別の送信を生成し始めました。 2番目のネットワークでは、ファイルからのモデルのロード、予測の段階、およびファイルへの出力を考慮して、すべてを確実に追い出すことができないことが明らかになりました。1つのモデルで約5〜7分かかりました。モデルは、後で学びましたが、スタックオーバーフローでは、モデル構造と重量を分離することをお勧めします。したがって、読み込みが速くなります)、最終提出には、最初の提出後に信じていた2つのInceptionResNet-v2と、最高の3つのXceptionの予測のみが含まれますテスト品質について。 ハッカソンファイナルの10分前にモデルが機能し、多数の音声でミックスしたいという予測を持つ5つの個別のcsvファイルを得ました(各ファイルについて、ほとんどのモデルが投票したラベルが選択されます)。 jupyterを開き、csv-shkiをロードします... submit.pyのどこかにエラーがあったことを理解しています:このラベルが参照しているファイルを示すことなく、予測されたラベルのみがファイルに保存されました。 コード内ですべてが常にソートされ、ファイル処理の順序が最初から最初まで変わらず、以前の送信を見つけ、そこからファイル名の列を選択し、16:05に正確に新しいマークをすばやく平均することを望んでいました(主催者はさらに5分を与えました)最終提出物を送信します。 後で判明したように、jupyter-laptopでのこのコーディングは実際には速度には必要ないことが判明しました。結果は約0.04%改善され、0.7739になりました。 2位のチームは4%を追加して0.7137を獲得しましたが、それでも1位で大きなマージンを残しています。







まとめ



Mail.Ruオフィスで開催された興味深い週末でした。ハッカソンと並行して、機械学習のテーマに関する2つの興味深い講義がありました(もちろん、多くのコーヒーとクッキー)。 また、ハッカソンでは、明らかなステップが多くの問題を引き起こす可能性があるという貴重な経験を得ています。







問題に挑戦する時間がありましたが、時間がなかったので、最も深刻なアイデアは、画像をモデルの入力サイズに思いやりなく拡大縮小するのではなく、ランダムクロップで学習することでした。 ハッカソンの途中でこれを実装しようとしましたが、このネットワーク構成では収束がまったく停止し、エラーを探す時間がありませんでした。 おそらく、適切な実装により、モデルの品質が向上します。チームの私の同僚は、実装で作物のXceptionをトレーニングできましたが、最終的な送信にこのネットワークを使用することはできませんでした。







ハッカソンのコードはすべて公開されており、 githubリポジトリで利用できます。









チーム「MADGAN」:










All Articles