旅行好きなソフトエンジニアの備忘録

プログラミングや技術関連のメモを始めました

【Python】 AUC計算方法のメモ

PythonでAUCを計算する方法を探していたのですが、下記がコードも掲載されており詳しかったです。

qiita.com

from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import roc_curve, auc

classifier = RandomForestClassifier()
predictions = classifier.predict_proba(X)
precision, recall, thresholds = roc_curve(Y, predictions[:,1])
score = auc(precision, recall)

【ディープラーニング】 "37 Reasons why your Neural Network is not working"のまとめ

下記の記事がためになったため、まとめてみました。 blog.slavv.com

まず調べる事

  • 上手くいくことが分かっている事から始める。例えば画像であればVGGを使ってみる等。ロスも独自の定義ではなく標準のものを使う

  • 正則化やデータ拡張等のオプションを全て止めてみる

  • ファインチューニングを行おうとしているのであれば、元のモデルと前処理の方法が同じか確認する

  • 入力が正しいか確認する

  • 小さなデータセット(2~20)で始めて過学習させる。その後徐々にデータを増やす

  • 正則化、データ拡張、カスタムロス、複雑なモデル等、止めたオプションを徐々に加えていく

次に調べる事

1. データセットの問題

  1. 入力の中身が正しいか調べる

  2. でたらめな入力を入れてみて症状が変わらなければネットワーク側の問題かもしれない

  3. データローダーをチェックする。入力は問題無くてもネットワークに渡す過程に間違いがあるかも。ネットワーク側で受け取った入力をprintしてみる

  4. 入力と出力の対応が取れているか確認する。誤って入力だけシャッフルして入力と出力の対応がでたらめになるケースがある

  5. 入力と出力に関係が存在するか確認する。入力と出力の関係がランダムの場合は学習できない

  6. 出力のラベルミスが無いか確認する

  7. データをシャッフルしてみる

  8. ロス関数を工夫する等、不均衡データ問題を解消する(8 Tactics to Combat Imbalanced Classes in Your Machine Learning Dataset - Machine Learning Mastery

  9. 十分な学習データがあるか?一般的にクラス分類では各クラスに1000個はデータが必要と言われている

  10. バッチ内に複数のラベルが含まれているか確認する

  11. 大きすぎるバッチサイズは汎化性能を劣化させる([1609.04836] On Large-Batch Training for Deep Learning: Generalization Gap and Sharp Minima)(メモ:ここまで大きなバッチサイズで学習させることは無いから気にしなくて大丈夫そう)

Addition1. ネットワークの正しさを検証するため、MNIST, CIFAR10等既知のデータセットでまず試す

2. データ正規化/拡張

  1. 入力を標準化してみる

  2. データ拡張した画像枚数が多すぎないか?データ拡張は正則化の効果があり、L2正則化ドロップアウトと組み合わせることでアンダーフィットする可能性がある

  3. 転移学習を行う場合、事前トレーニングしたモデルの前処理と同じ前処理を施しているかを確認する

  4. 学習用/検証用/テスト用画像への前処理を確認する。統計処理は学習用データに対して行い、その値を検証用/テスト用画像に用いる

3. 実装の問題

  1. 問題設定を簡単にする。例えばモデルがクラスと座標を予測しようとしているのであれば、クラスのみを予測するよう変更してみる等

  2. Look for correct loss “at chance"(メモ:意味が分からず)

  3. ロス関数にバグが無いか調べる(独自ロス関数を作成した場合)

  4. ロス関数に正しい入力が渡っているか調べる

  5. 複数のロス関数を適用している場合、それらの重みを調べてみる

  6. ロス値以外のメトリクスを観察してみる

  7. 自作のレイヤーがあればそれを調べてみる

  8. 意図せず重みを更新しない設定のレイヤーが無いか調べてみる

  9. 問題が複雑すぎる場合、レイヤーの数を増やしてみる

  10. 次元のミスを疑ってみる。入力サイズが(64, 64, 64)といった場合、どれが何を表すのかは容易に間違えてしまう

  11. 自作の最適化を適用している場合、そこを見直してみる

4. トレーニングの問題

  1. 小さなデータセットで試してモデルが過学習を起こすか確認する(過学習しない場合は何かがおかしい)

  2. 重みの初期化方法を確認する。良く分からない場合、"Xavier"か"He"の初期化を利用する。

  3. ハイパーパラメータを変えてみる

  4. 正則化を緩める。ドロップアウト、バッチノーマライゼーション、L2正則化等を弱めてみる。“Practical Deep Learning for Corders”では、まずはアンダーフィットをさせないようアドバイスしている

  5. 時間をかける。もしロス値が下がり続けているようであれば学習を続けてみる

  6. 学習からテストモードへ変更してみる。ディープラーニングフレームワークの中には学習モードとテストモードでバッチノーマライゼーションドロップアウトの振る舞いが変わるものがある

  7. 学習を視覚化してみる。TensorboardやCrayonを利用してみる。活性化関数にあまりに大きな値が流れ込んでいる場合はバッチノーマライゼーションやELUsを試す。

  8. 異なる最適化手法を適用する

  9. 大きすぎる勾配、もしくは勾配消失が起こっていないか確認する

  10. 学習係数を変えてみる

  11. NaNが出たら、①学習の初期段階でNaNが出たら学習係数を下げてみる ②ゼロ割もしくは自然対数に0以下が入力されていないか確認する ③ここを参考にする ④レイヤー毎にどこでNaNが発生したか確認する

【料理】 だしの素で簡単!そうめんチャンプルー

ここを参考に作りました。

cookpad.com

材料(2人分)

  • そうめん   2束
  • しめじ    1パック
  • 長ネギ    1本
  • ツナ     1缶
  • 醤油     大さじ1.5
  • ごま油    大さじ1
  • 塩      ひとつまみ
  • 顆粒だしの素 小さじ0.5

作り方

  1. 鍋に湯を沸かす
  2. 長ネギは1cmの斜め切りにする
  3. しめじの石づきを切り落とし、小房に分ける
  4. そうめんを半分に折って湯で沸かす
  5. そうめんを沸かしている間に醤油以下を混ぜておく
  6. そうめんが沸いたら流水でぬめりを取り水を張ったざるに入れておく
  7. フライパンにツナを油ごと入れて火をつける
  8. 長ネギを入れて炒める
  9. しめじを入れて炒める
  10. 混ぜた調味料を入れる
  11. 水気を切ったそうめんを入れ、調味料がなじんだら火を止める
  12. 味見をして物足りなければ塩コショウをふる

【料理】 ズッキーニのシャキッとマヨ炒め

ここを見て作りました。

cookpad.com

材料 (2人分)

  • ズッキーニ 1本
  • エノキ 1/3袋
  • ベーコン 2枚
  • マヨネーズ大匙 1と1/2
  • しょうゆ小匙 1弱
  • 塩少々

作り方

  1. ズッキーニを厚さ8ミリくらいの輪切りにする。 エノキは根元を切って、小房に分ける。 ベーコンは一口大に切る。
  2. フライパンにマヨネーズ大さじ1と1/2を入れて、火をつける。 マヨネーズが溶けたら、ズッキーニを入れる。

  3. ズッキーニをひっくり返してから、塩少々をふる。 ベーコンとエノキを入れる。

  4. エノキに火が通ったら、しょうゆを回しかける。 サッと混ぜたら完成!

【ディープラーニング】 セマンティックセグメンテーション手法のまとめ

ディープラーニングを利用したセマンティックセグメンテーションについてまとめてあるページを見つけたのでメモします(A 2017 Guide to Semantic Segmentation with Deep Learning)。

2017年12月10日追記 上記リンクを日本語訳した記事があったため、下記リンクを参照した方が良いです。 postd.cc

セマンティックセグメンテーションとは?

どんなアプローチがあるの?

  • 初期の頃は"patch classification"が用いられており、全結合層があるため入力画像のサイズは固定だった

  • FCNが発表されてからはFCNがメジャーになった。FCNにより任意の画像サイズを扱うことが出来るようになり、"patch classification"よりも非常に高速という特徴がある

  • CNNをセグメンテーションに用いることの欠点としてプーリング層の存在がある。プーリング層があることでピクセルの細かい位置情報に捉われることがなくなるが、セグメンテーションは位置情報が重要

  • 位置情報の問題に対処するために2つのアーキテクチャがある。 一つはエンコーダーデコーダー型でU-Netが有名。もう一つはdilated/atrous convolutions

  • conditional random fieldによる後処理と併用すると、スコアが1~2%上昇する

読むべき論文

  1. [1411.4038] Fully Convolutional Networks for Semantic Segmentation

  2. [1511.00561] SegNet: A Deep Convolutional Encoder-Decoder Architecture for Image Segmentation

  3. [1511.07122] Multi-Scale Context Aggregation by Dilated Convolutions

  4. [1412.7062] Semantic Image Segmentation with Deep Convolutional Nets and Fully Connected CRFs

  5. [1606.00915] DeepLab: Semantic Image Segmentation with Deep Convolutional Nets, Atrous Convolution, and Fully Connected CRFs

  6. [1611.06612] RefineNet: Multi-Path Refinement Networks for High-Resolution Semantic Segmentation

  7. [1612.01105] Pyramid Scene Parsing Network

  8. [1703.02719] Large Kernel Matters -- Improve Semantic Segmentation by Global Convolutional Network

  9. [1706.05587] Rethinking Atrous Convolution for Semantic Image Segmentation

読むべき論文追記(精度は劣るが実行スピードが速い)

  1. ENet
    [1606.02147] ENet: A Deep Neural Network Architecture for Real-Time Semantic Segmentation

  2. ICNet
    [1704.08545] ICNet for Real-Time Semantic Segmentation on High-Resolution Images

  3. ERFNet
    http://www.robesafe.uah.es/personal/eduardo.romera/pdfs/Romera17iv.pdf

【Python】 KerasでU-Net構造ネットワークによるセグメンテーションをする

ここ(Daimler Pedestrian Segmentation Benchmark)から取得できるデータセットを使って、写真から人を抽出するセグメンテーション問題を解いてみます。U-Netはここ( U-Net: Convolutional Networks for Biomedical Image Segmentation )で初めて発表された構造と思いますが、セグメンテーション問題にMax Poolingを使うのは良くないといった話があったり、Batch Normalization等も使いたいということで、pix2pixのGeneratorとして利用されているU-Net構造のネットワークを利用します。

github.com


実行環境は以下になります。

  • Windows10 64bit
  • Python3.5.2
  • Keras2.0.4
  • BackendはTensorflow CPU版

まずはU-Netモデルを作るクラスです(unet.py)。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from keras.models import Model
from keras.layers import Input
from keras.layers.convolutional import Conv2D, ZeroPadding2D, Conv2DTranspose
from keras.layers.merge import concatenate
from keras.layers import LeakyReLU, BatchNormalization, Activation, Dropout

class UNet(object):
    def __init__(self, input_channel_count, output_channel_count, first_layer_filter_count):
        self.INPUT_IMAGE_SIZE = 256
        self.CONCATENATE_AXIS = -1
        self.CONV_FILTER_SIZE = 4
        self.CONV_STRIDE = 2
        self.CONV_PADDING = (1, 1)
        self.DECONV_FILTER_SIZE = 2
        self.DECONV_STRIDE = 2

        # (256 x 256 x input_channel_count)
        inputs = Input((self.INPUT_IMAGE_SIZE, self.INPUT_IMAGE_SIZE, input_channel_count))

        # エンコーダーの作成
        # (128 x 128 x N)
        enc1 = ZeroPadding2D(self.CONV_PADDING)(inputs)
        enc1 = Conv2D(first_layer_filter_count, self.CONV_FILTER_SIZE, strides=self.CONV_STRIDE)(enc1)

        # (64 x 64 x 2N)
        filter_count = first_layer_filter_count*2
        enc2 = self._add_encoding_layer(filter_count, enc1)

        # (32 x 32 x 4N)
        filter_count = first_layer_filter_count*4
        enc3 = self._add_encoding_layer(filter_count, enc2)

        # (16 x 16 x 8N)
        filter_count = first_layer_filter_count*8
        enc4 = self._add_encoding_layer(filter_count, enc3)

        # (8 x 8 x 8N)
        enc5 = self._add_encoding_layer(filter_count, enc4)

        # (4 x 4 x 8N)
        enc6 = self._add_encoding_layer(filter_count, enc5)

        # (2 x 2 x 8N)
        enc7 = self._add_encoding_layer(filter_count, enc6)

        # (1 x 1 x 8N)
        enc8 = self._add_encoding_layer(filter_count, enc7)

        # デコーダーの作成
        # (2 x 2 x 8N)
        dec1 = self._add_decoding_layer(filter_count, True, enc8)
        dec1 = concatenate([dec1, enc7], axis=self.CONCATENATE_AXIS)

        # (4 x 4 x 8N)
        dec2 = self._add_decoding_layer(filter_count, True, dec1)
        dec2 = concatenate([dec2, enc6], axis=self.CONCATENATE_AXIS)

        # (8 x 8 x 8N)
        dec3 = self._add_decoding_layer(filter_count, True, dec2)
        dec3 = concatenate([dec3, enc5], axis=self.CONCATENATE_AXIS)

        # (16 x 16 x 8N)
        dec4 = self._add_decoding_layer(filter_count, False, dec3)
        dec4 = concatenate([dec4, enc4], axis=self.CONCATENATE_AXIS)

        # (32 x 32 x 4N)
        filter_count = first_layer_filter_count*4
        dec5 = self._add_decoding_layer(filter_count, False, dec4)
        dec5 = concatenate([dec5, enc3], axis=self.CONCATENATE_AXIS)

        # (64 x 64 x 2N)
        filter_count = first_layer_filter_count*2
        dec6 = self._add_decoding_layer(filter_count, False, dec5)
        dec6 = concatenate([dec6, enc2], axis=self.CONCATENATE_AXIS)

        # (128 x 128 x N)
        filter_count = first_layer_filter_count
        dec7 = self._add_decoding_layer(filter_count, False, dec6)
        dec7 = concatenate([dec7, enc1], axis=self.CONCATENATE_AXIS)

        # (256 x 256 x output_channel_count)
        dec8 = Activation(activation='relu')(dec7)
        dec8 = Conv2DTranspose(output_channel_count, self.DECONV_FILTER_SIZE, strides=self.DECONV_STRIDE)(dec8)
        dec8 = Activation(activation='sigmoid')(dec8)

        self.UNET = Model(input=inputs, output=dec8)

    def _add_encoding_layer(self, filter_count, sequence):
        new_sequence = LeakyReLU(0.2)(sequence)
        new_sequence = ZeroPadding2D(self.CONV_PADDING)(new_sequence)
        new_sequence = Conv2D(filter_count, self.CONV_FILTER_SIZE, strides=self.CONV_STRIDE)(new_sequence)
        new_sequence = BatchNormalization()(new_sequence)
        return new_sequence

    def _add_decoding_layer(self, filter_count, add_drop_layer, sequence):
        new_sequence = Activation(activation='relu')(sequence)
        new_sequence = Conv2DTranspose(filter_count, self.DECONV_FILTER_SIZE, strides=self.DECONV_STRIDE,
                                       kernel_initializer='he_uniform')(new_sequence)
        new_sequence = BatchNormalization()(new_sequence)
        if add_drop_layer:
            new_sequence = Dropout(0.5)(new_sequence)
        return new_sequence

    def get_model(self):
        return self.UNET


次に前処理関連の関数です(main.py)。

IMAGE_SIZE = 256

# 値を-1から1に正規化する関数
def normalize_x(image):
    image = image/127.5 - 1
    return image


# 値を0から1に正規化する関数
def normalize_y(image):
    image = image/255
    return image


# 値を0から255に戻す関数
def denormalize_y(image):
    image = image*255
    return image


# インプット画像を読み込む関数
def load_X(folder_path):
    import os, cv2

    image_files = os.listdir(folder_path)
    image_files.sort()
    images = np.zeros((len(image_files), IMAGE_SIZE, IMAGE_SIZE, 3), np.float32)
    for i, image_file in enumerate(image_files):
        image = cv2.imread(folder_path + os.sep + image_file)
        image = cv2.resize(image, (IMAGE_SIZE, IMAGE_SIZE))
        images[i] = normalize_x(image)
    return images, image_files


# ラベル画像を読み込む関数
def load_Y(folder_path):
    import os, cv2

    image_files = os.listdir(folder_path)
    image_files.sort()
    images = np.zeros((len(image_files), IMAGE_SIZE, IMAGE_SIZE, 1), np.float32)
    for i, image_file in enumerate(image_files):
        image = cv2.imread(folder_path + os.sep + image_file, cv2.IMREAD_GRAYSCALE)
        image = cv2.resize(image, (IMAGE_SIZE, IMAGE_SIZE))
        image = image[:, :, np.newaxis]
        images[i] = normalize_y(image)
    return images


最後にこれらを利用するメイン関数です(main.py)。これを動作させるためには以下のファイル/フォルダ構造を想定しています。
- main.py
- unet.py
- trainingData(トレーニング用データを格納するフォルダ)
- testData(テスト用データを格納するフォルダ)
更にtrainingDataフォルダ、testDataフォルダには最初に紹介したデータセットにあるleft_imagesフォルダとleft_groundTruthフォルダがそれぞれ置かれているものとします。

  • 2017/08/10 関数predictのモデル生成部の不具合を修正
  • 2017/11/08 import osを書き忘れている不具合を修正
import os
import numpy as np
from keras.optimizers import Adam
import keras.backend as K
from keras.callbacks import ModelCheckpoint, EarlyStopping
from unet import UNet

# ダイス係数を計算する関数
def dice_coef(y_true, y_pred):
    y_true = K.flatten(y_true)
    y_pred = K.flatten(y_pred)
    intersection = K.sum(y_true * y_pred)
    return 2.0 * intersection / (K.sum(y_true) + K.sum(y_pred) + 1)


# ロス関数
def dice_coef_loss(y_true, y_pred):
    return 1.0 - dice_coef(y_true, y_pred)


# U-Netのトレーニングを実行する関数
def train_unet():
    # trainingDataフォルダ配下にleft_imagesフォルダを置いている
    X_train, file_names = load_X('trainingData' + os.sep + 'left_images')
    # trainingDataフォルダ配下にleft_groundTruthフォルダを置いている
    Y_train = load_Y('trainingData' + os.sep + 'left_groundTruth')

    # 入力はBGR3チャンネル
    input_channel_count = 3
    # 出力はグレースケール1チャンネル
    output_channel_count = 1
    # 一番初めのConvolutionフィルタ枚数は64
    first_layer_filter_count = 64
    # U-Netの生成
    network = UNet(input_channel_count, output_channel_count, first_layer_filter_count)
    model = network.get_model()
    model.compile(loss=dice_coef_loss, optimizer=Adam(), metrics=[dice_coef])

    BATCH_SIZE = 12
    # 20エポック回せば十分
    NUM_EPOCH = 20
    history = model.fit(X_train, Y_train, batch_size=BATCH_SIZE, epochs=NUM_EPOCH, verbose=1)
    model.save_weights('unet_weights.hdf5')


# 学習後のU-Netによる予測を行う関数
def predict():
    import cv2

    # testDataフォルダ配下にleft_imagesフォルダを置いている
    X_test, file_names = load_X('testData' + os.sep + 'left_images')

    input_channel_count = 3
    output_channel_count = 1
    first_layer_filter_count = 64
    network = UNet(input_channel_count, output_channel_count, first_layer_filter_count)
    model = network.get_model()
    model.load_weights('unet_weights.hdf5')
    BATCH_SIZE = 12
    Y_pred = model.predict(X_test, BATCH_SIZE)

    for i, y in enumerate(Y_pred):
        # testDataフォルダ配下にleft_imagesフォルダを置いている
        img = cv2.imread('testData' + os.sep + 'left_images' + os.sep + file_names[i])
        y = cv2.resize(y, (img.shape[1], img.shape[0]))
        cv2.imwrite('prediction' + str(i) + '.png', denormalize_y(y))


if __name__ == '__main__':
    train_unet()
    predict()
上手く抽出出来ている例(元画像/正解画像/予測画像)

f:id:ni4muraano:20170805002131p:plain:w100f:id:ni4muraano:20170805002139p:plain:w100f:id:ni4muraano:20170805002145p:plain:w100
f:id:ni4muraano:20170805002242p:plain:w100f:id:ni4muraano:20170805002252p:plain:w100f:id:ni4muraano:20170805002300p:plain:w100
f:id:ni4muraano:20170805002316p:plain:w100f:id:ni4muraano:20170805002323p:plain:w100f:id:ni4muraano:20170805002330p:plain:w100

上手くいかなかった例(元画像/正解画像/予測画像)

f:id:ni4muraano:20170805002518p:plain:w100f:id:ni4muraano:20170805002524p:plain:w100f:id:ni4muraano:20170805002527p:plain:w100
f:id:ni4muraano:20170805002541p:plain:w100f:id:ni4muraano:20170805002545p:plain:w100f:id:ni4muraano:20170805002547p:plain:w100
f:id:ni4muraano:20170805002603p:plain:w100f:id:ni4muraano:20170805002605p:plain:w100f:id:ni4muraano:20170805002609p:plain:w100

上手くいっていない例を見ると、学習データに類似パターンが無さそうなケースでした。学習していないことを予測できないのは仕方無いので、学習用データに十分なバリエーションを持たせて学習させることが重要と思いました。