アプリとサービスのすすめ

アプリやIT系のサービスを中心に書いていきます。たまに副業やビジネス関係の情報なども気ままにつづります

学習済みのYOLOv3でオリジナルデータに転移学習(finetune)【物体検出】

オリジナルのデータセットにYOLOv3を使って物体検出した。
一から学習せずに、COCOデータセットの学習済みYOLOv3で転移学習してみたのでその備忘録


目次
1.オリジナルデータセットのclasses.txtと学習済みモデルの作成
2.訓練
3.学習結果


1.オリジナルデータセットのclasses.txtと学習済みモデルの作成

オリジナルデータセットはとあるコンペの公開画像を使用。

データセットformat(classes.txt)


[image_path, x1, y1, x2, y2, clas_id] で入力

anno_path = 'dataset_path'
fname = [path for path in os.listdir(anno_path)]
sorted_file =[path for path in natsorted(fname)]

df=[]
for idx, f in enumerate(sorted_file):
    fname, save,  b, cls_id = convert_annotation(anno_path, f)
    img = cv2.imread(fname)
    df.append([save, b[0], b[1], b[2], b[3], cls_id])

with open('classes.txt', 'w+') as f:
    for d in df:
        f.write(','.join(map(str, d)) + '\n')


classes.txt

Car
Pedestrian
Truck
Signal
Signs
Bicycle




学習済みモデル作成手順



kerasのYOLOv3を参考にdarknet53のweightの代わりに、COCO datasetの学習済みのYOLOv3のweightを使用

# Keras versionのYOLOv3をgit clone

$ git clone https://github.com/tanakataiki/keras-yolo3

# 学習済みmodelをdownloads
$ wget https://pjreddie.com/media/files/yolov3.weights

# 変換
$ python convert.py yolov3.cfg yolov3.weights yolo.h5

サイトに書いてあるとおり。






train.pyで使用

DARKNET_PATH = 'yolov3.h5'

def create_tiny_model(input_shape, anchors, num_classes, load_pretrained=True, freeze_body=2,
            weights_path=DARKNET_PATH):
    '''create the training model, for Tiny YOLOv3'''
    K.clear_session() # get a new session
    image_input = Input(shape=(None, None, 3))
    h, w = input_shape
    num_anchors = len(anchors)

    y_true = [Input(shape=(h//{0:32, 1:16}[l], w//{0:32, 1:16}[l], \
        num_anchors//2, num_classes+5)) for l in range(2)]

    model_body = tiny_yolo_body(image_input, num_anchors//2, num_classes)
    print('Create Tiny YOLOv3 model with {} anchors and {} classes.'.format(num_anchors, num_classes))

    if load_pretrained:
        model_body.load_weights(weights_path, by_name=True, skip_mismatch=True)
        print('Load weights {}.'.format(weights_path))
        if freeze_body in [1, 2]:
            # Freeze the darknet body or freeze all but 2 output layers.
            num = (20, len(model_body.layers)-2)[freeze_body-1]
            for i in range(num): model_body.layers[i].trainable = False
            print('Freeze the first {} layers of total {} layers.'.format(num, len(model_body.layers)))

    model_loss = Lambda(yolo_loss, output_shape=(1,), name='yolo_loss',
        arguments={'anchors': anchors, 'num_classes': num_classes, 'ignore_thresh': 0.7})(
        [*model_body.output, *y_true])
    model = Model([model_body.input, *y_true], model_loss)

    return model




2.訓練

optimizerとcallbackメソッド

Adam(lr=1e-3)

ReduceLROnPlateau

ModelCheckpoint

model.compile(optimizer=Adam(lr=1e-3), loss={'yolo_loss': lambda y_true, y_pred: y_pred})

reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=3, verbose=1)
checkpoint = ModelCheckpoint('drive/My Drive/ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5',
        monitor='val_loss', save_weights_only=True, save_best_only=True, period=1)


学習曲線は怖いくらい順調に下がり、ログをとってないけどほぼ下の図のように降下。汎化性能も抜群


学習曲線図

f:id:trafalbad:20200118163451j:plain


YOLOv3はネットワークの構造上、大きい物体を学習しておけば、推論時には小さい物体も検知できるので、学習時には大きい物体のみくっきり写ってる「良質かつ大量」のデータセットで学習


yolov3は構造上小さい物体も検知できる

f:id:trafalbad:20200118163430p:plain




3.学習結果

予測した画像は下の感じ。かなりいい感じに検出できてる


明るい画像



f:id:trafalbad:20200118163531j:plain



f:id:trafalbad:20200118163528j:plain


夜の暗い画像


f:id:trafalbad:20200118163550j:plain


f:id:trafalbad:20200118163554j:plain


学習時に気をつけたことは

大きい物体がくっきり写ってる良質かつ大量のデータセットで学習(小さい物体は学習させてない)


optimizerはSGDではなくAdamに変更

callbackメソッドでval_lossの調整が意外に効いた

公開されてる学習済みモデルで転移学習はコスパ的にかなり秀逸


今回はdarknet53の重みで一から学習せずにYOLOv3で転移学習してみた。faster-RCNNより小さい物体の検出対策がやりやすいので好きになった。



一から学習するより公開済みの学習ずみモデルで転移学習すれば、ほとんどのモデルで予想以上の成果でそう。


参考サイト


A Closer Look at YOLOv3

Kaggle 2018 Google AI Open Images - Object Detection Track

HOG+Adaboostで物体検出(object detection)【機械学習】

HOGとの組み合わせはSVMが有名だけど、今回はSVMじゃなく「HOG+AdaBoost」を使った。

どっちも精度は同じくらいだけど、AdaBoostの方が2回目以降精度が増すし、汎用性が高いのが特徴。


目次
・訓練データ・テストデータの収集
・前処理
HOG+AdaBoostで物体検出


訓練データ・テストデータの収集

あらかじめバウンディングボックスの座標がアノテーションされた画像から、車とそれ以外のものをクロップして

・Positiveサンプル(車):1500枚 
・negativeサンプル(車以外): 1500枚

を集めた。

車がちょうどよく入る大きさのにresize(300px)。Sliding windowサイズはリサイズしたサイズより大きめ(400~600px)にとった。

サンプルが多く、綺麗に物体が写ってるほど精度も良かった。

# trainと精度
print("HOG classifier AdaBoosting...")
ada = AdaBoostClassifier(n_estimators=100, random_state=0)
ada.fit(trainData, trainLabels)

print("HOG classifier Evaluating on test data ...")
predictions = ada.predict(testData)
print(classification_report(testLabels, predictions))

>>>>>>
"""
 Evaluating classifier on test data ...
              precision    recall  f1-score   support

           0       0.91      0.92      0.91       375
           1       0.90      0.88      0.89       301

    accuracy                           0.90       676
   macro avg       0.90      0.90      0.90       676
weighted avg       0.90      0.90      0.90       676
"""



前処理

基本的に前処理は以下の流れ

1. trainの時にHOG特徴量が検出しやすいように画像を明るくする

2. testや本番で使う画像には前処理かけない(明るくしない)



明るくする方法はいくるかあるけどgammaを使ったコントラストは上手くいかない、しかも精度を下げる結果に。

def ganmma(img, gamma = 3.0):
    lookUpTable = np.zeros((256, 1), dtype = 'uint8')
    for i in range(256):
        lookUpTable[i][0] = 255 * pow(float(i) / 255, 1.0 / gamma)

    img_gamma = cv2.LUT(img, lookUpTable)
    return img_gamma

なので明るくするために、単純にRGBごとに明るくして、不純物を極力含めないようにした。

def equalize(img):
    for j in range(3):
        img[:, :, j] = cv2.equalizeHist(img[:, :, j])
    return img






HOG+AdaBoostで物体検出

HOG特徴量を使った物体検出の仕組みは、sliding windowを順番に画像内を走らせて、検出できた領域をバウンディングボックスで囲む。



f:id:trafalbad:20200112185716p:plain

def sliding_window(image, stepSize, windowSize):
    # slide a window across the image
    for y in range(0, image.shape[0], stepSize):
        for x in range(0, image.shape[1], stepSize):
            # yield the current window
            yield (x, y, image[y: y + windowSize[1], x:x + windowSize[0]])



検出結果



Sliding windowサイズを大きめ(400〜600px)にして、検出後にHOG特徴量用の300pxにリサイズした方が検出結果がよかった。

ベストパフォーマンスはWindow size = (500, 500) にResize size = (300, 300)




Window size = (500, 500) Resize size = (300, 300)のとき(ベストパフォーマンス)
f:id:trafalbad:20200112185839j:plain





Window size = (400, 400) Resize size = (300, 300)のとき
f:id:trafalbad:20200112185902j:plain




Window size = (600, 600) Resize size = (300, 300)のとき
f:id:trafalbad:20200112185918j:plain







物体検出以外の使い方



HOGでの物体検出は検出領域をバウンディングボックスで囲む以外にも、「エッジ検出」したり、「輪郭抽出」したりといろんな使い方ができる。

HOGでのsliding windowでの用途は多様なので、yolov3とかfaster-RCNNの物体検出ニューラルネットワークの「補助的な役割」として使うくらいがお利口な使い方かと思った。


バウンディングボックスで囲む部分のエッジ検出

def rgb_to_gray(src):
    b, g, r = src[:,:,0], src[:,:,1], src[:,:,2]
    return np.array(0.2989 * r + 0.5870 * g + 0.1140 * b, dtype='uint8')

def edge_detection(img):
    img_hist_eq = img.copy()
    for i in range(3):
        img_hist_eq[:, :, i] = cv2.equalizeHist(img_hist_eq[:, :, i])
    # img_gray = cv2.cvtColor(img_hist_eq, cv2.COLOR_BGR2GRAY) 
    img_gray = rgb_to_gray(img_hist_eq)
    canny_img = cv2.Canny(img_gray, 50, 110)
    backtorgb = cv2.cvtColor(canny_img,cv2.COLOR_GRAY2RGB)
    return backtorgb


f:id:trafalbad:20200112185949j:plain






バウンディングボックスで囲む部分の輪郭抽出

def extract_contours(img):
    imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    ret,thresh = cv2.threshold(imgray,127,255,0)
    image, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

    contour_img = cv2.drawContours(img, contours, -1, (0,255,0), 3)
    return contour_img

f:id:trafalbad:20200112190007j:plain






発展系 joint-HOG


単なるHOG特徴量だけを使うのではなく、ピクセルごとのHOG特徴量(の勾配)の組み合わせを使った「joint-HOG」もある。



joint-HOGの検出例
f:id:trafalbad:20200112190051p:plain


下のように、仕組みを真似して自分なりに組んでみたけど特に精度が良くなるわけではなかった。

from skimage.feature import hog
from skimage import data, exposure

# 輝度勾配を計算
def hog_gradient_img(image):
    fd, hog_image = hog(image, orientations=8, pixels_per_cell=(16, 16),
                        cells_per_block=(1, 1), visualize=True)

    hog_image_rescaled = exposure.rescale_intensity(hog_image, in_range=(0, 10))
    return hog_image_rescaled


# 勾配画像からjoint HOGを作成
def joint_hog(hog_img, threshold = 0.1):
    joint_fd = []
    fd = hog_img.flatten()
    for y in fd:
        for x in fd:
            if y-x > threshold:
                bit = 0
            else:
                bit = 1
            joint_fd.append(bit)
    return np.array(joint_fd)

組み方がよくないのは確かなので、正規のやり方でやればいい結果が出ることは間違いない。


HOGは決まったサイズの画像にしか適用できない上に、sliding windowを使うと計算コストもバカ高いので、物体検出はやっぱニューラルネットワーク一択だなと実感した。

ただニューラルネットワークのための画像前処理としては十分使えるのは間違いない。
joint-HOGは時間がなくてガチなのは組めなかったけど、qiitaとかで注目されてきたらやってみたい




参考サイト



【joint-HOG
第20回LSIデザインコンテスト・イン沖縄 設計仕様書 - 3-1
python-hog(github)

Joint HOG 特徴を用いた 2 段階 AdaBoost による車両検出

局所特徴量と統計学習法による物体検出



HOG物体検出参考サイト】
Opencvチュートリアル

python-bow-hog-object-detection

GitでPR(pull request)を投げるまでと、チームでgitでのコード管理ログ

gitコマンドでbranchを作成してPR(Pull Request)を投げるなど、チームでコード管理したのでその備忘録。


git cloneしてPRを投げるまでの branchの流れ
f:id:trafalbad:20200107205057j:plain





目次
1. PRを投げるまで
2.PR作成作業

PRを投げるまで

# コンソールのコマンドいじるファイル操作
$ source ~/.bashrc

macOSならこのファイルにいろいろ書き込むことでコンソールのコマンドを自分好みにカスタマイズできる。

# まずgitでコードをクローンする
git clone [URL] && cd [dirctory]

# 新しいブランチ作成(必ず[origin/master]を入れる)
$ git checkout -b refactorer origin/master
(git checkout -b [new branch] [current branch])

>>>
Branch 'refactorer' set up to track remote branch 'master' from 'origin'.
Switched to a new branch 'refactorer'

変更が反映された。




# 今いるbranchを確認
$ git branch
>>>
   master
* refactorer


# ステージング(git commitするときのファイルのリスト)の変更 →index.htmlを変更。commmit すると変更される

$ git add index.html



# git addでの変更内容の確認
$ git status

>>>
On branch refactoring_hagi
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified:  index.html

no changes added to commit (use "git add" and/or "git commit -a")


# コミット
$ git commit -m “コメント”

>>>
Your name and email address were configured automatically based
on your username and hostname. Please check that they are accurate.
You can suppress this message by setting them explicitly. Run the
following command and follow the instructions in your editor to edit
your configuration file:

git config --global --edit

After doing this, you may fix the identity used for this commit with:

git commit --amend ―reset-author

1 files changed, 183 insertions(+), 184 deletions(-)
rewrite index.html(88%)


# originにrefactorerをpushする
$ git push origin refactorer




新しいbranchができたので、PRを作成
f:id:trafalbad:20200107205151p:plain




※※※※※branch 削除用コマンド

git branch -D [ブランチ名]




PR作成作業


GUIでbranchを変更する場所



f:id:trafalbad:20200107205225p:plain

「default」がついてる branchがgit cloneしてきたときの branchになる。



Pull Request(PR)出すページ



f:id:trafalbad:20200107205249j:plain


・「Able to merge」が出てれば、即mergeできる

・PRの対象者は「Reviewers」から選択・変更できる





Reviewersを変更


f:id:trafalbad:20200107205313p:plain


ちなみに

・ファイルごとにPR投げる

ファイルごとに branchを作る必要あり


PRは、ファイルの変更ごとに投げたり、全体の変更を一括で変更するため投げたりできる。







gitでコード管理するときのserverとlocalとのおとまかな関係図
f:id:trafalbad:20200107205341j:plain


.gitはローカルにある隠しファイル的な存在で、表には出てこない。


・fetchコマンドはローカルにある".git"を変更

・mergeコマンドはPR後に行うやつで、serveも".git"の変更を反映する




branchを作ることでチームでのコード管理が簡単にできた。


参考サイト


よく使うgitコマンド

Gitでローカルブランチを削除する

Pythonの命名規則まとめ

sagemakerのセグメンテーションを使って物体の長さを測るモデル構築から、運用MLops構築までの記録【機械学習、AWS】

今回取り組んだプロジェクトは、

・sagemakerの組み込みアルゴリズムのセグメンテーションを使って、物体の長さを求めるモデルの作成

・CloudFrontで作ったフロント側から画像をPOST→lambda関数→sagemaker→推論→長さ測定→結果を表示する流れのMLops構築


の2つをメインにやったので、その概要まとめた。

目次
・長さ計測ロジック
・sagemaker MLopsの構築
・学んだ技術&メモ、まとめ
・sagemaker segmentation ガイド





長さ計測ロジック

sagemakerの組み込みアルゴリズムでセグメンテーションしたあと、物体の各A~E点を求め、「横と高さ」の「縦の長さ」を求めるロジックを実装した。

機械学習モデルはセグメンテーションを使い、長さを測るのはロジックはpythonで実装。
ロジック構築には物体のA〜E点の各点を求めて計算式から長さを求めた。

下のように各点をどのように求めるか、点を引いたり線を引いたり、試行錯誤。
f:id:trafalbad:20191224004411p:plain



求めた各点から下の計算式とかで横の長さを求めた。

横の長さ計算式
AB : [real AB](横の長さ) = CD : [real CD]

f:id:trafalbad:20191224004219p:plain



縦の長さ計算式
AE : [real AE](縦の長さ) = AB : [real AB]
f:id:trafalbad:20191224004320p:plain


そのあとAEからN(定数:あらかじめ決まった値)を引いて縦の長さを求めた


sagemaker MLopsの構築

前の記事でも書いたように、
CloudFrontからデータをPOST→lambda→sagemakerで推論→長さを受け取るMLopsを構築した。

MLops全体像
f:id:trafalbad:20191224004445j:plain



MLops全体の流れ
f:id:trafalbad:20191224004504p:plain


以下はMLops構築で重要だった部分。





CloudFrontのデータを送るWebUI部分



CloudFrontのWebUIはJavaScriptで実装。その時データはajaxでPOSTした。Fetch APIでもOK。

CloudFront側から送るjavascriptajaxの部分。送るデータは画像なので、バイナリ形式の画像でPOST。

// ajaxの部分
$.ajax({
type:'POST', // 使用するHTTPメソッド (GET/ POST)
url:APIurl, // 通信先のAURL
data:JSON.stringify(jsons), // 送信するデータ
contentType: "application/json"})


cssにはpx以外にもブラウザに合わせて長さを%で変更できるhw, vwという長さの単位があったり、画像を表示する「canvasタグ」を複数使い分けるのには、結構発想が必要だったりデザイン面でも苦労が多かった。





handler.py内部の動作


lambda内での動作はいちいちエラーをCloudwatchにみにいくのは、タイムロスがデカ過ぎるので、
sagemakerのjupyter notebookで動作確認してから、試行錯誤したりとなるべく効率化。

lambdaやAPI gateway構築は、ネット上に転がってるサイト通りには行かない場面も多く、エラー解決に手間取った




lambdaに外部モジュールを読み込ませるとき


handler.pyで使うライブラリをlambdaに読み込ませる必要がある。

s3やコンソールから読み込ませると面倒なので、requirement.txtを作りデプロイ時(sls deploy)時に同時にパケージをupload

参考サイト
Serverless Frameworkのプラグインを利用した外部モジュールの管理
(サイト内のimport requirementは必要なかった)




lambda内でファイルを置ける/tmpファルダの活用




handler.pyないの内部処理で「~.jpg」が必要だったので、その時に「/tmp/~.jpg」と実装すれば、lambda内の/tmpファルダを利用できる


参考サイト
AWS Lambda Python でtmpディレクトリにファイル作成してS3にアップロード


権限とかはエラーが出るたび付与して解決。
これで sagemakerを入れたMLopsが完成した。
f:id:trafalbad:20191224004445j:plain




serverless frameworkとかクラウドを使った、実用レベルのMLops構築ははじめてでかなり学ぶことが多かった。

このMLops作成に過程は前の2記事にまとめた。

AWSでCloudFrontからlambdaとsagemakerを通り、推論結果を受け取るMLopsの構築【機械学習】 - アプリとサービスのすすめ

CloudFrontからS3にuploadしたコンテンツ(サイト)にアクセスする手順まとめ【AWS】 - アプリとサービスのすすめ




学んだ技術&メモ、まとめ

システム全体像やコードの流れの理解

大規模なプログラムを構築するときは、全体像を把握しておくことはかなり便利だった。



システム全体像把握ツール

draw.io

フローチャート

・シーケンス図

特にdraw.ioやフローチャート、シーケンス図で、システム全体の流れ、構成図そして全体像を把握しておくことで、細部までコードの動きをイメージできるので、コードが素早く書ける。

f:id:trafalbad:20191224004534j:plain






gitでのコード管理

gitを使ったコード管理も本格的にやった。とくにGit submoduleとか、いろんなgitコマンドでコード管理した。






今後学ぶべきことメモ
基本情報技術者試験の基礎内容の勉強

フローチャート、シーケンス図を使っての効率化

もっと汎用的な他人のコードを学んで、積極的に使うこと


今回はモデル構築から、それを使うMLops構築まで全体を通してやったプロジェクトでかなり大変だったけどリターンのスキルはデカく、かなり勉強になった。



sagemaker segmentation ガイド

Amazon SageMaker 開発者ガイド

組み込みセマンティックセグメンテーションアルゴリズムルール

p410

f:id:trafalbad:20200124111701p:plain

train ディレクトリと validation ディレクトリ内のすべての JPG イメージには、train_annotation ディ レクトリと validation_annotation ディレクトリ内に同じ名前の対応する PNG ラベルイメージがあ ります。この命名規則は、トレーニング中にアルゴリズムがラベルをそれに対応するイメージに関連付け るのに役立ちます。

AWSでCloudFrontからlambdaとsagemakerを通り、推論結果を受け取るMLopsの構築【機械学習】

前回作ったCloudFrontのサーバからlambdaにアクセス、sagemakerのエンドポイントから推論結果を受け取り、表示するまでのMLopsをAWSで作成した。


CloudFrontも含めたMLops全体像

f:id:trafalbad:20191221173533j:plain




今回の推論部分のMLops
f:id:trafalbad:20191221173643p:plain

その備忘録&作業手順をまとめてく。


目次
・Serverless frameworkでlambda関数の構築
API gateway作成 & POST用のURL取得



Serverless frameworkでlambda関数の構築

1.serverless frameworkでlambda関数の作成


serverless frameworkのインストールにnpmコマンドが必要なのでインストール。
バージョンはv4以上。

# check version
npm ―version && aws ―version

# serverless framework インストール
sudo npm install -g serverless

#  IAM を severless framework に登録
sls config credentials --provider aws --key ****** --secret ******

・コマンド集

# 動作確認
serverless -v
# sls commandのバージョン確認
sls -v

# IAMロール確認
cat ~/.aws/credentials


# lambda関数作成
sls create -t aws-python -p [関数名]
>>>
Serverless: Successfully generated boilerplate for template: “aws-python"

# lambda用ファイルのhandler.py と serverless.yamlができてるか確認
$ ls slstest/
>>>
handler.py    serverless.yml


2.handler.py & serverless.yamlを実装




handler.py(lambda関数の内部処理を行うためのファイル)
handler.pyの動作確認は、sagemakerのjupyter notebookで動作確認して効率化した。

ファイル内のlambda_handler関数部分の引数「event、context」は予め決まってる引数。eventにPOSTしたデータが入る。

def lambda_handler(event, context):
     # 内部処理
 return  response


・serverless.yml (lambda関数の環境設定用ファイル)
下記サイトを参考に必要最小限のことだけ書いた

service: ***-webui  # update this with your service name

provider:
  name: aws
  runtime: python3.6
  stage: demo
  region: ap-northeast-1
  profile: dev  # your aws profile
  apiName: ***-webui   # Use a custom name for the API Gateway API
  memorySize: 3008   #  Default is 1024(MB)
  timeout: 900 # The default is 6 seconds. Note: API Gateway current maximum is 30 seconds
  role: arn:aws:iam::******/*********
  deploymentBucket:
    name: ***-webui

functions:
  predict:
    handler: handler.lambda_handler
    name: ***-webui

plugins:
  - serverless-python-requirements
  
custom:
  pythonRequirements:
    dockerizePip: true
# plugin (serverless-python-requirements)                     install
$ sls plugin install -n serverless-python-requirements





・参考にしたサイト

AWS Lambdaで運用した実績から得られた、serverless frameworkのオススメ設定とプラグインの知見


serverless.ymal用API Gatewayチュートリアル




3.デプロイ



sls deployコマンド一発でlambda関数が出来上がり。

#デプロイ
$ cd ***webui/ && sls deploy -v
>>>
Serverless: Stack create finished...
Serverless: Invoke aws:info
Serverless: [AWS cloudformation 200 0.104s 0 retries
Service Information
service: ****-webui-api
stage: demo
〜〜〜
Prefix: ‘serverless/*****webui-api/demo’ })





4.basic認証をCloudFrontに設定


前記事でCloudFrontのサイトに特定の人だけアクセスできるように、basic認証をかける。


Point1.バージニア北部にbasic認証用のlambda@edgeのlambda関数を作り、ARNを記録しておく


下の記事通りにlambda@edgeを作ったあと、そのARNをCloudFrontで設定する。

Lambda@Edge 用の IAM アクセス権限とロールの設定


東京リージョンのlambda関数内の「IAMロールで信頼関係」を編集。lambdaとlambda@edgeを追加。


Point2.CloudfrontのBehaviorの「Lambda Function Associations」にさっきのlambda@edgeのARNを貼り付ける

ARN
arn:aws:lambda:us-east-1:********basic_auth:1



そのあと、CloudFrontから「Domain Name」のURLでCloudFrontのサイトにアクセスすると、ユーザー名と、パスワードを聞かれるのでlambda@edge内の関数に記述した内容を入れてアクセス完了。



*****lambdaのライブラリ変更方法

lambda関数内でランタイムを別のライブラリ(nodejs10.xとか)に変更するとき
→アクションから「新しいバージョン発行」を選択



Point3.CloudFrontにアクセスする時URLの後ろに「/index.html」をつける必要があるがそれを省く

CloudFrontのコンソール=>Distribution Settings=>Default Root Objectに”index.html"と設定しておくこと。

これでroot URL(ドメイン+/で終わるURL)でアクセスした時にroot URLに「.index.html」をつけなくて済む
f:id:trafalbad:20191221175416j:plain


詳細はこの記事を参照
CloudfrontとS3でBasic認証をかける




Point4.lambda関数作成のときの参考サイト
Severless Framework
SageMakerとServerlessを組み合わせて、お手軽にscikit-learnの機械学習APIを作る

今から始めるServerless Frameworkで簡単Lambda開発環境の構築

デプロイ時のエラー対策
serverless deploy fails with ROLLBACK_COMPLETE error








API gateway作成 & POST用のURL取得

次はAPI Gatewayを作成。



ServerlessframeworkでもAPI Gatewayは作れるけど、いろいろ詳細な設定したかったので、コンソールからGUIで作成。

このサイトがかなり参考になった。
Lambda + API Gateway入門。画像のDL



1.API Gatewayの作成の重要部分


・OPTION, POSTメソッド全体図
f:id:trafalbad:20191221200023p:plain

f:id:trafalbad:20191221200031p:plain



・POST-統合レスポンス
f:id:trafalbad:20191221175058p:plain


・POST-メソッドレスポンス
f:id:trafalbad:20191221175118p:plain


・OPTION-統合レスポンスのヘッダーのマッピング
f:id:trafalbad:20191221175151j:plain




2.マッピングテンプレート


マッピングテンプレートを使うとCloudFrontからPOSTしたデータの形式を自由にカスタマイズできる。

コード例

{
"base64Image": "$input.body"
}

# handler.py内では => event[“base64Image”]=POSTされたデータ

これでhandler.pyでCloudFrontからPOSTしたデータが

{“base64Image”:[データの中身]}

の辞書形式でlambdaに届く。

今回の自分のevent(json)の中身は、

{'LEF': “base64のバイナリ画像”, ‘RIG': “base64のバイナリ画像”}
参考サイト
API Gatewayのマッピングテンプレートの設定例



3.CORSの設定


javascriptとかでよく見かけるめんどくさいアクセス関係の処理がCORS。

まず、ブラウザがSafariならの「開発」→「クロスオリジンの制限を無効にする」をチェック

あとは、API Gatewayで直接設定できる。
f:id:trafalbad:20191221175333j:plain



CORES対策参考サイト
API Gateway CORS: no 'Access-Control-Allow-Origin' header
なんとなく CORS がわかる...はもう終わりにする。


API GAtewayをデプロイすれば、API Gatewayga完成。POST用URLが取得できる。

これで、CloudFrontからPOSTしたデータをsagemakerエンドポイントで推論して結果を受け取るネットワークが完成した。





冒頭でも示したけど、今回の使ったMLopsの全体像はCloudFront側のwebUIも含めるとこんな感じ。
f:id:trafalbad:20191221173533j:plain



かなり混み合った機械学習の基盤構築にはじめて取り組んだ。正直簡単なwebサイトならAWSで管理できるようになった気がする。

あとはWebUI側の動作とかlambda関数内の動作、このMLopsでやったことは次の記事でまとめようと思う

CloudFrontからS3にuploadしたコンテンツ(サイト)にアクセスする手順まとめ【AWS】

s3にuploadしたコンテンツ(html, css, javascriptで構成したwebUIのコード)にアクセスする手順をまとめた。

これで、S3バケット上のコンテンツを、シンプルにCloudFrontのみからアクセスできるように設定できる。

なので、node.jsとか余計なサーバを用意せずにAWSだけで簡単にサイトを表示できる。


主にこの図のWebUIの部分

f:id:trafalbad:20191209103101p:plain



表示するサイト

f:id:trafalbad:20191209103228j:plain



1.S3バケットの作成

s3バケットを作成、webUIコンテンツをuploadする。

バケット名は「webui-*****

でWebUIファイルをupload。




2. CloudFrontの設定

→ CloudFrontからのみ、アクセスできるようにする

コンソールからCloudFrontへいき、「Create Distribution」作成

Webリージョンの「Get Started」をクリック
「Origin Domain Name」にさっき作ったS3を指定。


「Restrict Bucket Access」を選択し、オリジンアクセスアイデンティティを使用する設定にするため 『yes』
下の2つもyes 。


f:id:trafalbad:20191209152426p:plain


他はデフォルトでCreate Distributionをクリック。






3.CloudFrontのキャシャを無効化

上の「Invalidations」から更新するファイルを追加する。

今回は一括で指定できる「/*」を追加。これでs3のファイルが更新されるたびにキャッシュが消えて、サイトが更新される。

f:id:trafalbad:20191221183106j:plain

またキャッシュスピードを速くするため、「Behavior」編集時にTTLを設定して、全部値を0にする。



f:id:trafalbad:20191220141638p:plain

参考サイト:Amazon CloudFrontのキャッシュ無効化(TTL設定)




4.CloudFrontでorigin access identifyの確認


Security から「origin access identify」でorigin access identifyの一覧が表示される。ここでorigin access identify IDを確認しておく

comment
access-identity-webui-********.s3.amazonaws.com

ID
E************

S3 canonical userid
71************




5. S3でバケットにorigin access identifyが付与されてるか確認

S3 アクセス権限からバケットポリシーを見ると指定したs3バケットにorigin access identifyが付与されている

バケットポリシーの中身


この3つの該当部分
arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity E************”

Action": "s3:GetObject"

"Resource": "arn:aws:s3:::webui-*****/*"

{
    "Version": “2010-12-01“,
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": "1",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity E************”
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::webui-*****/*"
        }
    ]
}


6.CloudFrontでBehaviorの作成

cloudfrontで「create behavior」をクリック。Path Patternで「/*.html」と入力し、「create behavior」を作成

f:id:trafalbad:20191209152354p:plain

CloudFrontのstatusが「In Progress」から「Deploy」に変わったらOK.




7.CloudFront経由でアクセスできるか確認

コンソールからCloudFrontをクリックし、アクセス用URLを確認


Domain Name    d24*******.cloudfront.net


ブラウザで 「d24*******.cloudfront.net/index.html」
を開く


s3にuploadしたサイトが表示された。

f:id:trafalbad:20191209103357j:plain





Node.jsとかでサーバを立ち上げなくてもAWSでサイトが簡単に表示できた。独自ドメインとかを取得するならRoute53や代替ドメインCDN設定とかが必要になる。

けどシンプルにs3のコンテンツをCloudFrontで表示するだけで、CloudFront以外からアクセスできないし、AWSだけで完結するのでサーバ代わりとしては十分すぎる



参考
CloudFront + S3-特定バケットに特定ディストリビューションのみからアクセスできるよう設定する

S3の特定バケットへのアクセスを特定のCloudFrontからのみ許可する

Unetを使った海氷領域セグメンテーションコンペの記録【機械学習】

SARという特殊な衛星画像から海氷領域を見つけるsignateのコンペで、その技術・やったこと等のログ。


コンペデータ構成

画像
train画像:hh 80枚、hv80枚、アノテーション画像80枚
Test画像 hh 40枚、hv 40枚、アノテーションなし



ラベル構成
Not sea ice領域0
Sea ice 領域1, 4, 7, 9
11
12



海氷領域の例
f:id:trafalbad:20191201153246j:plain


陸領域の例
f:id:trafalbad:20191201153302j:plain



湖は画像内でほとんんどなく誤検出の対象が陸だった。誤検出をいかに抑えながらセグメントできるかが焦点。

誤検出はHOG+SVMopencvの検出系メソッドを使う、アノテの誤検出領域を染める等、「誤検出領域を強調してセグメンテーションしないようにする」とか後になって、いろいろ手法を思いついた。

コンペ中はそういう工夫よりネットワークをいじることに時間を費やしてしまったのがミス。







使ったNN、パラメータ、前処理等

・Residual構造のUnet

→Wide Residual構造のSEblock, Squeeze-and-ExcitationをSEblockに追加

→resblockでベストプラクティスと言われてる形だと上手くいかなかったので普通のResblock(左のやつ)を使用

f:id:trafalbad:20191201153211j:plain

x = Activation('relu')(init)
x = Conv2D(nb_filter * k, (3, 3), padding='same', use_bias=False)(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
x = Dropout(0.4)(x)
x = Conv2D(nb_filter * k, (3, 3), padding='same', use_bias=False)(x)
x = BatchNormalization()(x)

x = layers.add([init, x])


Wide Residual Network:幅を広くすることでモデルの表現力を上げる、計算を並行して行うことで計算時間の短縮、Dropoutを入れることで過学習を抑制

Squeeze-and-Excitation:パラメータ数の増加を抑える







アノテーション


grayscaleで0,1でピクセル間の確率を出す手法

f:id:trafalbad:20191201153347j:plain

# 0, 11, 12 は黒 (0)、それ以外は白(255)
dst = np.where((img == 0) | (img >= 11), 0, 255)





・Loss


binary cross entropy(bce) dice loss




・optimizer


RMSprop(lr=1e-3), Adam(lr=1e-3)





・Augmentation


flip系、回転系など位置変換系のみ使用。

grayscaleなのでピクセル間の関係性を壊すコントラストや色彩変換系は使わなかった。

Imagegeneratorでも位置変換の引数のみを指定





TTA(Test Time Augmentation)


augmentationでflip系を使ってたので使えなかった。augmentationの時にflip系を使わないなら必須




過学習対策



・Random Erase(マスクを傷つけたのか不明)

・validationデータ増やす

・L2正規化

・augmentation

・Dropout層をencoderのconv層前とResblock内に追加



・epoch、batch sizeパラメータ



イテレーション数 3000

・batch size:20

イテレーション数は「データ数/バッチサイズ」より3000くらいにした方が安定してlossが減少。



画像前処理

ヒストグラム平均化を使った


効果あったのがStretching、CLAHE (Stretchingの方がCLAHEより精度がいい)。


f:id:trafalbad:20191201153117p:plain



stretchingはいい具合に画像のピクセルをおおそ15単位ごとに分散して平均化してくれるからhhとhvで特性が顕著になりやすいため精度が良くなった


CLAHEピクセルが広範囲で、hh,hvが似たようなかんじのピクセル構成になり、hh, hvの特性を活かせないため精度に影響が少なかった。

あとリサイズするときはピクセル構成を維持するためにcv2.resize()に "interpolation=cv2.INTER_NEAREST" を指定した。

# stretching
p2, p98 = np.percentile(hh_image, (2, 98))
img_rescale = exposure.rescale_intensity(image, in_range=(p2, p98))


# CLAHE (Contrast Limited Adaptive Histogram Equalization)
param = 20.0
clahe = cv2.createCLAHE(clipLimit=param, tileGridSize=(8,8))
cl1 = clahe.apply(image)



# リサイズ
cv2.resize(image, (H, W), interpolation=cv2.INTER_NEAREST)




反省・考察

上手く行かなかったこと


・海氷領域以外の誤検知抑制のために「IOUに閾値を設ける」「誤検出領域の強調をする」とかの工夫をしなかった


・テスト画像を学習させてから、訓練画像を再学習させるfinetuneの活用が下手だった


過学習にてこずってネットワークを改造に時間をかけてしまったこと。普通にdropout層増やせばある程度は解決した。


・maxpooling層をresnet34+unetで除外したら精度が上がった事例があったので除外したけど、loss の収束スピードが鈍くなるし精度はあんまり変わらなく、少し良くなった程度。dropout層の追加で補えた





反省点


・下準備がおろそかだったこと


・自分で考えてしまい、先人の真似するという根本的なことを序盤からしなかった(先人の知恵を借りず独力でやってしまった)


アルゴリズムを多く選びすぎて選択のパラドックスに陥った。アルゴリズムは1、2に絞ってあとは工夫で補うべきだった






順位27位。誤検出をうまく抑えながらhh, hvの特性をうまく利用しながらセグメンテーションしなければならなったけど終盤はあまり時間がなくうまく回せなかった。

f:id:trafalbad:20191201153435j:plain





次に生かすこと



・保守的なモデルと挑戦的なモデル2つのみを使う、あとは工夫で補う


・スタートは大会優勝者の王道的な手法を真似をするのことから始める


・とにかく「下準備を十分する」、「質重視で精度検証」などをすること。データの追従もせず投稿量ばかり増える愚策は避ける


・ノートやエクセルでデータ記録、追従できるようにすること


・一回投稿するまでの流れをコード化して一回投稿することをはじめにしておき、PDCAサイクルを回せるようにしておく


・小データ数で精度が出てからやる等、学習の効率化をはかる

データ追従に役立つツール&サイト

・draw.io
draw.io を使ってみた
draw.io

パワポ、エクセル

シーケンス図(ULM)

フローチャート