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

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

エイリアスを設定してlinuxのショートカットコマンドを作成する(linuxカスタマイズ)

エイリアスを設定して、長ったらしいlinuxコマンドのショートカットコマンドを、自分なりにカスタマイズして作成したのでその備忘録。

これで面倒なコマンドはいちいちググらずに済む。


f:id:trafalbad:20200210103255j:plain


目次
1. .bashrcと.zshenv にエイリアスを記述する
2. .bash_profileで.bashrc(と.zshenv)を読み込む設定をする
3. 実験
4. まとめ



1. .bashrcと.zshenvにエイリアスを記述する

自分のPCの場合、anaconaをインストールしてたためか、エラー

zsh: command not found


が出るので「.zshenv」ファイルにも同じことを書く必要があった。

まず試しにファイル数をカウントするコマンドで

alias count='ls -1 | wc -l'

を書いてみる。

# .bashrc(と.zshenv)ファイルをvimで開く
$ vi ~/.bashrc  (vi ~/.zshenv)


# 「i」で入力モードになるので入力モードで
$ alias count='ls -1 | wc -l'

と入力後「esc+:wq」で入力モードを終了。





2. .bash_profileで.bashrc(と.zshenv)を読み込む設定をする

エイリアスは.bash_profileに.bashrc(と.zshenv)を読んでもらう必要がある。


.bash_profileはターミナルが起動すると読み込まれる仕組みなので、ターミナルを起動するたびに読み込んでもらう。つまりショートカットコマンドを永久に保存できる。

$ vi ~/.bash_profile
# 以下を入力
source ~/.bashrc
source ~/.zshenv


と記述。この変更自体も反映させなければいけないので、ターミナルから以下を実行

$ source ~/.bash_profile


自分のPCはanacondaの変なコマンドが書いてあるので、消去してまっさらな状態で上のコマンドだけ書いて実行。







3.実験

実験1



# desktopに移動コマンドのショートカット作成
$ vi ~/.bashrc (vi ~/.zshenv)
alias desk='cd && cd desktop'

# 反映
$ source ~/.bash_profile

# 実行
$ desk
$ pwd 
>>>> /Users/~/desktop

移動できた





実験2



# ファイル数カウントコマンドのショートカット作成
$ vi ~/.bashrc (vi ~/.zshenv)
$ alias count='ls -1 | wc -l’

# 反映
$ source ~/.bash_profile

# 実行
$ count
>>>> 6


できた。





4.まとめ

エイリアス設定の利点はたくさんあるけど、下の2つがでかい。

・いちいち調べてたlinuxコマンドを調べる手間が減った
・長いlinuxコマンドを入力せずに簡単に実行できた。

これが生産性向上かと思う瞬間だった気がした。


# vi ~/.bashrcと~/.zshenvの中身
alias count='ls -1 | wc -l'
alias desk='cd && cd desktop'
alias down='cd && cd downloads'
# vi ~/.bash_profileの中身
source ~/.bashrc
source ~/.zshenv




参考サイト



ターミナルをいじってるとzshが「command not found」と叫ぶので、叫ばないようにした

conda activate の CommandNotFoundError への対処方法

【初心者向け】エイリアスの設定方法

学習済みの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作成作業
3.キレーなコードの書き方

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を作ることでチームでのコード管理が簡単にできた。







キレーなコードの書き方

・関数は動詞+名
→何がされて何が返ってくるかを名前を反映させる


・変数名の付け方は「be 動詞 = 主語」
→ 例 is_completed_lv = 処理(主語)


・同じ動作が多い場合は減らす


・Ifの多さ
ネストが3は深いので4以降NG。関数化すればネストはなくなる


・1箇所変更するだけで、何箇所も同時に変更できる書き方
→ もし10箇所エラー、 1箇所直すだけで済む



・キレー・読みやすい
→元々英語で読むため、コードを読んでどんな処理が行われているかわかること



参考サイト


よく使う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 ラベルイメージがあ ります。この命名規則は、トレーニング中にアルゴリズムがラベルをそれに対応するイメージに関連付け るのに役立ちます。

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)

フローチャート

prednetで未来の画像予測、天気画像の予測コンペのログ【機械学習、python】

signateの雲画像予測コンペに初参加したので、そのログ。
個人の備忘録と技術メモのつもりで簡単にやったことつづってきます。

まず結果は17位。1位との精度差は10%くらいだった。初参加にしては上々なできかな。レベル高くて驚いた。

コンペのデータ構成

データは雲画像とmetaデータの2種類。metaデータは湿度、温度、風の向きとかの格子状の2次元データ(グレースケール画像)。


【データの特徴把握のため使った方法】

・月ごとの雲量をヒストグラムで可視化

・TSNEで雲量を分布を可視化

・類似画像検索でmetaデータと雲画像と相関関係の深いデータを調べた

・天気関係の本で気象庁がどうやって雲を予測しているかとか、雲の発生する流れの理解


metaデータの中で、湿度(RH)と風(VGRD)が雲画像と一番関係しているけど、活用方法が思いつかなったので保留した。

使うアルゴリズムは、動画のフレーム予測を予測するGAN系ネットワーク(VideoGAN, Recycle-GAN)とかを調べた中で、convLSTMの発展系のprednetが一番使えそうなので、改良して使用。

f:id:trafalbad:20191117144850j:plain



prednetを改良

以下の点でprednetを改良

・効率的に学習させるためgeneratorを改良

・prednet にresnetを追加することでextrapolationや精度をよくした。

・訓練回数を増やしてなるべく精度を上げた

・prednetの活性化関数のA_activation='relu'なのを、差分をとりたいなら"tanh"にすると有効だった。

・画像を簡単にするよりも、なるべく多くの画像群での一連の動作を何回も学習させた方が効果的。


resnetを入れて、時刻tの予測画像をt+1の予測画像にaddした。そうすることで一から学習することなく、前の画像をもらいながら予測できる。


【extrapolationで24時間分の画像予測部分】

f:id:trafalbad:20191117145006j:plain


公開されてたprednetは複雑でネットワークがかなり改造しにくかったので、もっと改造しやすいprednetを実装すればよかった。


【convLSTM系ネットワーク改造例】
f:id:trafalbad:20191117145027j:plain



【他にも色々試して無駄だったこと】

・画像を簡単にする(黒の要素が強い0付近のピクセルは全部ゼロにして雲を強調する)

opencvのaddWeightメソッドで合成する

・cycleGANで予測した画像の補正とかUnetで予測するとかのアイデアと実装してしまったこと


精度を上げるには、metaデータをもっと使ったり、過去データを上手く活用する必要があったかもしれない。




結果まとめ

【一位の人の画像】

f:id:trafalbad:20191117162711j:plain

等高線の軌跡があるので、metaデータと雲量の相関関係を出して、関係のあるmetaデータを予測して、雲と関係の高い値のところをヒートみたいに染めるとかの手法でも使ったのか?

【予測する前の3h分の画像のgif(GroundTruth)】

f:id:trafalbad:20191117162731g:plain





【そこから自分が予測した24h分の画像のgif】

f:id:trafalbad:20191117162751g:plain





はじめてのコンペだったので、下調べに時間使いすぎたり、上手く精度を上げる方法がわからなかった。

投稿した回数は数回で、結果は17位。1位との精度差は10%くらいだった。


とにかく「まず投稿→feedback→改善→投稿」の繰り返しでPDCAサイクルを効率よく回せるかが、精度上げてくののカギっぽい。上位の人ほど投稿数が多い傾向にあった。

ちなみに自分は少ししか投稿しなかったので、何が精度アップに直結するのか把握する余裕がなかったのが後悔した点。


初参加で学んだこと

・下準備や画像生成に直結しない、無駄な画像の特徴把握に時間使いすぎて、1回目の投稿がかなり遅れたこと。

・とりあえずゴミのような駄作でも一度submitまでやってしまうこと。PDCAサイクルが回しやすくなる。

・アイデアは先人の知恵を奪うこと

・なるべく高得点を出せるパターンを見つけてPDCAサイクルを回して改善していく

・とりあえずやったデータやアイデアのログは後から見返せるように、dropboxに保存したり、ノートに記録しておいた。

・いい手法を思いつくには大量に良質なインプットして考えまくるのは変わらない

・今の手法がいいか迷ったらとにかくsubmitして得点だし、feedbackを得ること




はじめてのコンペでわからないことも多かったので、勝つより、経験を積むための初戦だった気がする(もちろん勝つ気でいたけど、当たり前的にレベルが高かった)。

こんなに考えたのは機械学習の中でも1、2位を争うくらいだったので、スキルも上がった。何より発想力、技術力がメッチャ上がるし、気軽に始められるし、勝てればリターンでかいし、いいことずくめだと思った。
(基本、土日も考えるので終わった後の疲労感はやばい)


参考サイト

PredNetを用いた混雑レーダーの未来予測

PredNetのGitHub