stMind

about Tech, Computer vision and Machine learning

SSDとCRNNで簡易OCR

画像から文字列を認識するOCRシステムを、SSDとCRNNで。

github.com

最初に、SSDで文字列領域を矩形として抽出、矩形領域に対してCRNNで文字列を出力する構成です。

SSDはTensorflow Object Detection APIで、COCOだけでは十分な精度が出なかったので、SynthTextとCOCOを使って自前学習、CRNNはPytorchで実装されているモデルを利用しました。(学習は、EC2のP2インスタンスで約1.5日かかりました)

検出のSSDモデルは傾きとスケール変化には弱かったり、CRNNはアルファベットのみの学習モデルで記号は認識できない等の制約がありますが、簡易的なOCRであれば簡単に作れそうです。

論文紹介 : Composing Text and Image for Image Retrieval

CVPR2019で発表された、元画像と元画像をターゲット画像に変換するためのクエリ文を組み合わせた画像検索方式。以下、論文について簡単にまとめてみます。

目的

f:id:satojkovic:20191104162223p:plain

これは図を見ると理解しやすいと思います。エッフェル塔の画像と「人が写ってない、夜間」というクエリ文を組み合わせて検索すると、塔がズームされた夜のエッフェル塔画像が結果として得られるようにする。

こういった検索をしたいケースというのは、結構あるように思います。新しいコートを買おうとして検索、画像検索結果を眺めていると良さそうな物を見つける。色は良いけど丈がもう少し短ければなぁと思って「丈短め」と検索、みたいなことが出来ると望んだ結果を得ることが容易になりそうです。

提案方式

f:id:satojkovic:20191104183546p:plain

これも図を見て全体像を把握するとわかりやすいと思います。まず最初に、ResNet-17を用いてクエリ画像(参照画像)の特徴マップ \phi _ {x} \in \mathbb{R} ^ {W \times H \times C}を得ます。また、クエリ文もLSTMを用いて特徴ベクトル \phi _ {t} \in \mathbb{R} ^ {d}に変換します。そして、この二つを統合した特徴を用いてターゲット画像を検索するのですが、ここでTIRGという新しい統合方式を提案しています。

TIRG(Text Image Residual Gating)

特徴ベクトルの統合は、以下のように計算されます。


\begin{eqnarray}
\phi _ {xt} ^ {rg} = w _ {g} f _ {gate} ( \phi _ {x}, \phi _ {t} ) + w _ {r} f _ {res} ( \phi _ {x}, \phi _ {t} )
\end{eqnarray}

 f _ {gate} f _ {res}は図中のgated featureとresidual featureに対応していて、 w _ {g} w _ {r}は学習により得られるパラメータです。

この数式の直感的な理解には、ResNetのResidual Blockを考えると良いようです。求めたいクエリ画像に対する変換結果(ResBlockで言う H (x)に相当)は、クエリ画像に基づいた特徴マップ f _ {gate}(Identity mappingの xに相当)をReferenceとして、クエリ文に基づいた特徴マップ f _ {res} (残差の F(x) に相当)で補正することで得られるというのが提案方式のアイデアです。

また、 f _ {gate} f _ {res}は次のように計算されます。

 
\begin{aligned}
f _ {gate} ( \phi _ {x}, \phi _ {t} ) &= \sigma (W _ {g2} \ast RELU (W _ {g1} \ast [ \phi _ {x}, \phi _ {t} ]) ) \odot \phi _ {x} \\
f _ {res} ( \phi _ {x}, \phi _ {t} ) &= W _ {r2} \ast RELU ( W _ {r1} \ast ( [ \phi _ {x}, \phi _ {t} ] ) 
\end{aligned}

 \sigmaシグモイド関数 \odotはelement wiseの積、 \astは2d convolutionとbatch normalizationを表し、 W3x3のconv filterです。 f _ {gate}の方は、クエリ画像の特徴マップを変換するのではなく、シグモイド関数のゲートで元画像の情報をどの程度保持するかを制御するようになっています(0に近いと元画像の特徴マップをほとんど残さないが、1だとそのまま)。

学習

TIRGを使って求めた特徴ベクトルとターゲット画像の特徴ベクトルを近づけ、非類似画像の特徴ベクトルから遠ざけるように学習する。学習に用いるのは、Softmax Cross-Entropy Loss。


\begin{eqnarray}
L = \frac{-1}{MB} \sum _ {i=1} ^ {B} \sum _ {m=1} ^ {M} log \{ \frac{exp \{ \kappa (\psi _ {i}, \phi _ {i} ^ {+})  \} }{ \sum _ { \phi _ {j} \in \mathcal{N} _ {i} ^ {m} } exp \{ \kappa (\psi _ {i}, \phi _ {j} ) \} }  \}
\end{eqnarray}

 Bはmini batchのサイズで、 Mは少しわかりにくいですが、mini batchの中で1つのpositiveなサンプル \phi _ {i} ^ {+} K-1個のnegativeなサンプル \phi _ {1} ^ {-}, \cdots , \phi _ {K-1} ^ {-}を持つ集合 \mathcal{N} _ {i}を作成するプロセスの繰り返し回数となっています。あと、類似度カーネル \kappaですが、内積とnegative L2ノルムを使ったそうです。

Experiments

3つのデータセットを使った評価が行われています。メインの評価指標はrecall at rank k(R@K)。R@Kは、こちらのPDFを見るとわかりやすいです。

https://ils.unc.edu/courses/2013_spring/inls509_001/lectures/10-EvaluationMetrics.pdf

数値比較の詳細は論文を参照。

Fashion200k

画像につけられたdescription文が、1語違いになっているペアをクエリ画像とターゲット画像とし、異なる1語をクエリ文とする。

method R@1 R@10 R@50
TIRG 14.1 42.5 63.8

既存手法と比べて、R@1で8pt近く向上。TIRG以外の統合方式に対して比較した場合でもR@1、R@10、R@50のいずれにおいてもBestな結果となった。結果画像の一部は以下の通り。

f:id:satojkovic:20191117195031p:plain

MIT-States

60k枚の画像と、"red tomato"や"new camera"のような物体に対する名詞と形容詞のラベルがついたデータセット。Image retrievalのタスクにおける評価では、同じ物体ラベルで異なる形容詞を持つペアをクエリ画像とターゲット画像とし、ターゲット画像の形容詞をクエリ文として評価。

method R@1 R@10 R@50
TIRG 12.2 31.9 43.1

R@1ではBestではなかったが、R@10とR@50ではTIRG以外の統合方式の中でBestな値となった。

f:id:satojkovic:20191117200710p:plain

CSS

この論文で新しく導入されたデータセット。既存データセットにはない、複雑な修正クエリ文を含む。ちなみに、データセットはコードと併せて公開する予定だそう。

method 3D to 3D 2D to 3D
TIRG 73.7 46.6

f:id:satojkovic:20191117202103p:plain

最後に

実際に検索システムに導入されると、ユーザーが本当に検索したい情報に辿り着く可能性が高まる、面白いアイデアだと思いました。クエリ画像とクエリ文からターゲット画像を検索するというものですが、例えば洋服を探しているときは、最初に「グレーのコート」などと検索して、その結果を見ながら、修正クエリ文を入れるというステップになると思うので、一連の検索のコンテキストや、「ボタンは黒」みたいな空間的な修正対象の位置なども使えるとより良い結果が得られるかもしれません。(空間的な情報の有無は論文内でもAblation studyで論じられてます)

画像キャプション(im2txt)

github.com

色々と参考にしながら、画像キャプションのモデルを学習、テストした。

  • im2txtはtensorflowのリポジトリに含まれる画像キャプションのモデル
  • encoder-decoder系のアーキテクチャで、画像をエンコードした固定長ベクトルを入力し、自然言語のキャプションにデコードする構成
  • ya_im2txtではエンコーダーとしてInceptionV3、デコーダーはLSTMで単語のベクトル表現はGloveを使用
  • 画像をうまく説明するキャプションが生成される一方で、失敗も多い。また、vocabularyを制限しているので表現力は低い(同じようなキャプションが生成されることが多い)

f:id:satojkovic:20190929174019p:plainf:id:satojkovic:20190929174028p:plain

A simple yet effective baseline for 3d human pose estimation

https://arxiv.org/pdf/1705.03098.pdf

画像から3次元の関節を推定する論文。ただし、入力は画像ではなく2次元の関節座標で、そこから3次元の関節を回帰する。

2次元から3次元へのマッピングをDNNとして学習する手法は、これまでにも提案されてきているが、それらに共通するのは、3次元座標を2次元(画像や関節座標)から直接推定することはそもそも困難という認識である。本論文では、Dropout、Batch NormalizationとReLU、Redisual Connectionを用いたシンプルなDNNにより、SOTAな手法に負けない精度で3次元座標の推定が可能であることを示している。

Network design and solution methodology

\displaystyle f^* = \min_f \frac{1}{N} \sum_{i=0}^{N} {\cal L} (f( {\bf x_i} ) -  {\bf y_i})

3次元の関節座標へ変換するfを定義し、変換後の関節座標と正解関節座標の差を最小にするf^{\ast}を求める。f^{\ast}は、DNNでモデル化し、次の図のような構成を提案している。

f:id:satojkovic:20190911081741p:plain

図には書かれてないが、二つのlinear layerが入力直後と出力直前に存在し、また図のブロックは二層存在するので、linear layerは合計で6となる。学習パラメータ数は4Mから5M。

Residual connections

Residual connectionsを設けることで、エラーが10%も低下した。

Batch normalizationとdropout

Linear-ReLuとResidual connectionsを用いたシンプルなネットワークの場合、2次元座標のGTで学習すれば良い精度を得られるが、 detectorの2次元座標で学習した場合や、2次元座標のGT で学習してdetectorの2次元座標で推論した場合には精度低下が見られた。Batch normとdropoutを併用することで、これらのケースでの精度を改善することが出来た。

Max-norm constraint

Batch normと組み合わせて、各層の重みのノルムの上限を1.0に正規化することで、汎化性能の向上が見られた。

Data preprocessing

  • 平均を引いて、標準偏差で割る一般的な正規化を入力データと出力データに適用。3次元座標は、hip jointを中心とした相対的な座標(カメラ座標系)。
  • 任意の座標系での3次元座標を2次元座標から求めることは現実的ではないので、カメラを中心としたカメラ座標系が自然な選択となる。3次元座標のGTに対応する2次元座標は、カメラの内部パラメータを用いた変換により求めることが可能。
  • 2次元座標は、Stacked Hourglass(SH)を用いて取得。Human3.6Mデータセットでは人物矩形が含まれているので、矩形の中心に対して440x440の領域をクロップ、256x256にリサイズしてSHに入力する。
  • 既存手法ではCPMを用いていて、2次元座標のGTとの誤差はSHの平均15pixelよりも小さい(平均10pixel)。しかし、MPIIデータセットではわずかに精度が良いこと、10倍の処理速度、の2点の理由でSHを採用している。
  • Human3.6Mデータセットで、SHのFine tuningも行った(SH detections FT)。
  • SHは基本的にはデフォルトの設定だが、メモリの制限でbatch sizeを6から3に変更。学習率は 2.5 x 10^{-4}で、40000iter実行。

Training details

学習時のハイパーパラメータは表のようになっている。

epochs 200
optimizer Adam
learning rate 0.001 (exponential decay)
Initialization Kaiming initialization

Evaluation

評価に使ったデータセットは、Human 3.6MとMPII。Human3.6Mはその名の通り、3.6millionの画像があり、2d / 3d jointsやカメラパラメータのground truth を含む大規模なデータセット。一方のMPIIはもう少し小規模。

Results

  • GTを用いた評価
    • GTの2次元座標で学習、テストした場合、既存手法より43%の改善(37.10mmの誤差)。
    • GTにガウシアンノイズを付加してテストした場合、当然ながら性能は低下していく(それでも比較手法よりもエラーは小さい)。
    • SHを用いてテストした場合でも、既存手法よりも20%程度改善(60.52mmの誤差)
  • detectorの2次元座標を用いた評価
    • SHで求めた2次元座標を用いて学習、テストした場合、既存手法のSOTAな結果よりも4.4mmの改善。SHをHuman3.6MでFTした場合は、9.0mmの改善。
    • 後処理(詳細は読み取れず)を加えた場合、HumanEvaで評価した場合も、既存手法よりもわずかに悪くなっている動作もあるが、総じて比較手法よりも改善されている
  • ネットワーク構成の評価
    • Data preprocessingを行わない場合が性能低下が一番大きく、その次に性能低下が大きいのはBatch normをなくした場合。各構成を含む場合が一番性能は高い。
    • ブロック数を4、8と増やしても性能向上はあまりなく、ブロック数2で頭打ちしている。

最後に

Linear-ReLu + Batch norm + Dropout + Residual connectionのシンプルな基本構成で、既存手法を超える性能を達成しているのは驚き。論文タイトルにもあるように、これらからの研究のベースラインになる(改善が色々とできそう)手法だと思われるし、detectorの2次元座標があれば手軽に3次元座標が得られるので、応用もしやすそうである。

Tensorflow Object Detection APIを使ってロゴ検出

以前、ナンバープレート認識モデルを参考にして、ロゴ認識CNNを作りました。

stmind.hatenablog.com

前回のモデルは独自モデルだったのと、物体候補領域はSelective Searchで実行していたため計算時間が大きかったので、one stageで標準的なモデルを使用するように変更してみました。

Tensorflow Object Detection APIにあるSSDモデルで、Flickr Logos 27 datasetを学習してみたところ、割と簡単にロゴ検出ができました。

f:id:satojkovic:20190830120140p:plain

f:id:satojkovic:20190830134607p:plain

f:id:satojkovic:20190830135612p:plain

検出した結果を見ると、誤検出はない一方で、未検出が結構多い印象です。領域サイズが小さかったり、向きや傾きが大きい場合に未検出が増えている様子なので、Data augmentationを見直すなどは必要になる感じですね。

github.com

GoogLeNet(Inception V1) and Inception V3 memo

CNNの中でもよく使われるアーキテクチャの一つであるGoogLeNet。GoogLeNetの層を構成するのがInceptionで、今までにv1からv4までの改良が行われていて、またresidual blockを導入したinception-resnetも提案されています。

Mediumの解説記事を参考に、基本となるGoogLeNet(V1)とKerasなどのフレームワークでもよく使われるV3を簡単にメモ。

A Simple Guide to the Versions of the Inception Network | by Bharath Raj | Towards Data Science

Review: GoogLeNet (Inception v1)— Winner of ILSVRC 2014 (Image Classification) | by Sik-Ho Tsang | Coinmonks | Medium

Inceptionのバージョンについて

Inception V4の論文では、以下のように定義されているので、ここでもそれを参照します。

  • GoogLeNet(Inception V1)
    • [CVPR2015] Going Deeper with Convolutions
  • Inception V2
    • [ICML2015] Batch normalization: Accelerating deep network training by reducing internal covariate shift
    • GoogLeNetに対してBNを導入したもの
  • Inception V3

Inception V1

課題

  • 注目する対象というのは、画像中で任意の場所に様々なサイズで存在
  • 認識するためにフィルタサイズが重要になるが、適切に設定するのは難しい
  • Deepなネットワークは過学習しやすく、計算量も大きい

V1のアイデア

  • どれか一つのサイズを選ぶのではなく、同じ階層で色々なサイズのフィルタを同時に使う!
    • 1x1、3x3、5x5の畳み込みフィルタと3x3のmax pooling
    • 最後にChannle方向にmapを重ねる(concatenate)
  • ただし、このままだと計算量が大きいので、1x1でmap数を削減してから、3x3と5x5のフィルタを適用する
    • 例えば、Inception 3a層では、28 x 28 x 192の入力に対して、28 x 28 x 256の出力を生成する。その内訳は、
      • 1x1 conv→64個のfeature mapを出力
      • 1x1 conv + 3x3 conv→96個のfeature mapにしてから128個のfeature mapを出力
      • 1x1 conv + 5x5 conv→16個のfeature mapにしてから32個のfeature mapを出力
      • 3x3 max pool→32個のfeature mapを出力
    • max poolingの場合は、pooling後に1x1 convを適用

  • Inception V1を9ブロック組み合わせたのがGoogLeNetで22層のアーキテクチャ
    • Max Pooling + Convだと27層
  • Deepなネットワークなので、勾配消失の問題が起こりやすい。そのため、中間層(4aと4d)に補助的な識別器を追加して、ロスを計算するようにする
# The total loss used by the inception net during training.
total_loss = real_loss + 0.3 * aux_loss_1 + 0.3 * aux_loss_2

Inception V3

課題

  • モデルサイズを大きくして精度向上をしつつ、パラメータ数削減による計算効率化を行う
    • パラメータ数削減は、Mobile用途や超大規模データを扱うシナリオでは重要

V3のアイデア

  • Factorization
    • 5x5 convを2層の3x3 convに分解(factorize)=> Figure 5のInception Block(Inception A)
      • 5x5に対し、3x3 + 3x3の2層で約28%のパラメータ数削減
    • n x nのconv層を、1 x nのconv層とn x 1のconv層に分解 => Figure 6のInception Block(Inception B)
      • 3x3の場合は、1x3 convと3x1 convに分解することで、33%程度の削減
    • n x nのconv層を、1 x nのconv層とn x 1のconv層の並列に分解 => Figure 7のInception Block(Inception C)
  • Auxiliary Classifiers
    • 補助的な識別器は、学習初期にはロスが収束することにあまり寄与せず、学習後期になってようやく精度向上に寄与することがわかった
      • 低層部を取り除いても最終精度はあまり変わらない
    • 補助的な識別器は4ブロックのInception Bのあとに1つだけ
      • Batch Normalizationを入れることで性能向上
  • Efficient Grid Size Reduction
    • d x d x kから(d/2) * (d/2) * 2kの特徴マップを得ようとする場合、2つのやり方がある
      • 1 x 1 x 2kのconvolutionをしてから、poolingする→この場合だと、(d x d x k) * (1 x 1 x 2k) = 2 d2 k2が支配的なoperation
      • 逆にpoolingをしてから、1 x 1 x 2kのconvolution→この場合、(d/2 x d/2 x k) * (1 x 1 x 2k) = 2 (d/2)2 k2が支配的なoperationで1/4の計算コスト
    • 先にpoolingをした場合、計算コストは小さくなるが、Representational bottleneckとなって、表現力が低下してしまう
      • d2 kに対して (d/2)2 k
    • strideが2のconvolutionとpoolingを並列に行い、出力をchannel方向にconcatする

42層のモデルで、GoogLeNetに対して2.5倍程度の計算コストで、Top1 errorを5.6%ほど改善している。

Inception V3のネットワーク構成

Inception V3のネットワーク構成図(https://ai.googleblog.com/2016/08/improving-inception-and-image.html

intが戻り値の関数でエラーを返すにはどうすればいいか

stackoverflow.com

例えば、ファイルに含まれる数の合計を計算する場合。

int DoSum(const std::string& file);

ファイルに含まれるのが正の値だけであれば、負の値でエラーを示すことができるが、正も負も含まれる場合には適用できない。

3つのやり方

1. 戻り値はエラーにして、合計は引数で指定された領域に格納
int DoSum(const std::string &file, int &sum)
{
    /* return value of zero indicates a sum has been computed
          other values indicate an error status
    */
}
2. エラーと合計を含むデータ構造を定義して戻り値とする
struct ReturnData
{
      int sum;
      int error_indicator;
};

struct ReturnData DoSum(const std::string &file)
{
}
3. 戻り値は合計で、エラーは例外で扱う
int DoSum(const std::string &file)
{
     /*  do calculations */
     if (error_has_occurred)
         throw some_appropriate_exception();
     else
         return calculated_sum;
}

1と2の場合、エラーが致命的でなければ呼び出し側で何もする必要がない一方で、エラー処理をしなくても動くので変な結果で処理が進んでしまうことがある。 3の場合、例外をcatchしなくても処理が異常終了するので変な結果が処理が進むことはないのに対し、この関数を使う全ての呼び出し側でcatchして適切に扱うようにしないといけない。

Googleのコード規模でということなので、自分たちのサイズで考える必要がありますが、既存のコードに対して、例外を導入するコストが高いので、 Google C++ Style Guideでは例外は使わないとしています。 ただ、自分たちの問題で考えてみても、関係する全てのコードで新しく例外処理を実装するのはやはり大変なので、1か2を使うことが多い印象です。