【WPF】画像が小さい場合はそのままのサイズで表示して、大きい場合は縮小して表示したい
Imageコントロールに画像を貼り付けると大きい画像、小さい画像共に特定のサイズで表示されるのですが、表題のように表示したいと思っていたところ、そのようなプロパティがあることが分かったのでメモします。
Image1.StretchDirection = StretchDirection.DownOnly;
【Keras】ArcFaceとUmapを使って特徴量を可視化する
ディープラーニングを用いたMetric Learningの一手法であるArcFaceで特徴抽出を行い、その特徴量をUmapを使って2次元に落とし込み可視化しました。KerasでArcFaceを用いる例としてメモしておきます。
実装は以下を引っ張ってきました。元とほぼ一緒なのですが一部以下の変更を入れています。 github.com
- archs.pyの簡素化
from keras.models import Model from keras.layers import Dense, Conv2D, BatchNormalization, Activation from keras.layers import Input, MaxPooling2D, Dropout, Flatten from keras import regularizers from metrics import * weight_decay = 1e-4 def vgg_block(x, filters, layers): for _ in range(layers): x = Conv2D(filters, (3, 3), padding='same', kernel_initializer='he_normal', kernel_regularizer=regularizers.l2(weight_decay))(x) x = BatchNormalization()(x) x = Activation('relu')(x) return x def vgg8_arcface(num_features, s, m): input = Input(shape=(28, 28, 1)) y = Input(shape=(10,)) x = vgg_block(input, 16, 2) x = MaxPooling2D(pool_size=(2, 2))(x) x = vgg_block(x, 32, 2) x = MaxPooling2D(pool_size=(2, 2))(x) x = vgg_block(x, 64, 2) x = MaxPooling2D(pool_size=(2, 2))(x) x = BatchNormalization()(x) x = Dropout(0.5)(x) x = Flatten()(x) x = Dense(num_features, kernel_initializer='he_normal', kernel_regularizer=regularizers.l2(weight_decay))(x) x = BatchNormalization()(x) output = ArcFace(10, s=s, m=m, regularizer=regularizers.l2(weight_decay))([x, y]) return Model([input, y], output)
2. train.pyをtrain_arcface.pyと名前を変更し、以下のように変更する(ここでArcFaceの学習を実行します)
import os import numpy as np import keras from keras.datasets import mnist from keras.optimizers import RMSprop from keras.callbacks import ModelCheckpoint, CSVLogger, TerminateOnNaN from archs import vgg8_arcface def main(epochs, batch_size, num_features, s, m): name = 'mnist_arcface_%dd' %(num_features) os.makedirs('models/%s' %name, exist_ok=True) (X, y), (X_test, y_test) = mnist.load_data() X = X[:, :, :, np.newaxis].astype('float32') / 255 X_test = X_test[:, :, :, np.newaxis].astype('float32') / 255 y = keras.utils.to_categorical(y, 10) y_test = keras.utils.to_categorical(y_test, 10) lr = 1e-3 optimizer = RMSprop(lr=lr) model = vgg8_arcface(num_features, s, m) model.compile(loss='categorical_crossentropy', optimizer=optimizer, metrics=['accuracy']) model.summary() callbacks = [ ModelCheckpoint(os.path.join('models', name, 'model.hdf5'), verbose=1, save_best_only=True), CSVLogger(os.path.join('models', name, 'log.csv')), TerminateOnNaN()] model.fit([X, y], y, validation_data=([X_test, y_test], y_test), batch_size=batch_size, epochs=epochs, callbacks=callbacks, verbose=1) model.load_weights(os.path.join('models/%s/model.hdf5' %name)) score = model.evaluate([X_test, y_test], y_test, verbose=1) print("Test loss:", score[0]) print("Test accuracy:", score[1]) if __name__ == '__main__': epochs = 15 batch_size = 128 num_features = 30 s = 30 m = 0.5 main(epochs, batch_size, num_features, s, m)
3. train_umap.pyを追加(ここでUmapの学習を実行します)
import numpy as np import pickle from keras.datasets import mnist from keras.models import load_model, Model from metrics import * from scipy.sparse.csgraph import connected_components from umap import UMAP def main(): # dataset (X, y), (X_test, y_test) = mnist.load_data() X_test = X_test[:, :, :, np.newaxis].astype('float32') / 255 # feature extraction arcface_model = load_model('models/mnist_vgg8_arcface_30d/model.hdf5', custom_objects={'ArcFace': ArcFace}) arcface_model = Model(inputs=arcface_model.input[0], outputs=arcface_model.layers[-3].output) arcface_features = arcface_model.predict(X_test, verbose=1) arcface_features /= np.linalg.norm(arcface_features, axis=1, keepdims=True) umap = UMAP(n_components=2) umap.fit(arcface_features) # UMAPはpickleで保存する(https://github.com/lmcinnes/umap/issues/178) pickle.dump(umap, open('umap.pkl', 'wb')) if __name__ == '__main__': main()
4. test.pyを以下のように変更(ここで学習済みのArcfaceとUmapを用いて2次元特徴空間にマッピングします)
import matplotlib.pyplot as plt import matplotlib.cm as cm import numpy as np import pickle from keras.datasets import mnist from keras.models import load_model, Model from metrics import * from scipy.sparse.csgraph import connected_components def main(): # dataset (X, y), (X_test, y_test) = mnist.load_data() X_test = X_test[:, :, :, np.newaxis].astype('float32') / 255 # feature extraction arcface_model = load_model('models/mnist_vgg8_arcface_30d/model.hdf5', custom_objects={'ArcFace': ArcFace}) arcface_model = Model(inputs=arcface_model.input[0], outputs=arcface_model.layers[-3].output) arcface_features = arcface_model.predict(X_test, verbose=1) arcface_features /= np.linalg.norm(arcface_features, axis=1, keepdims=True) umap = pickle.load((open('umap.pkl', 'rb'))) embedding = umap.transform(arcface_features) plt.scatter(embedding[:, 0], embedding[:, 1], c=y_test, cmap=cm.tab10) plt.colorbar() plt.show() if __name__ == '__main__': main()
準備が出来たらtrain_arcface.py ⇒ train_umap.py ⇒ test.pyの順番に走らせると以下のような画像が出力されます。 使う場面によってはクラス分類問題に落とし込むよりこのように視覚化した方がユーザーに理解されやすいケースがあるかもしれません。
【Keras】EfficientNetのファインチューニング例
EfficientNetはAutoMLで作成された、パラメータ数の少なさに対して精度が非常に高いモデルです。 OfficialのTensorflowの実装だけでなく、PyTorchやKerasの実装も早速公開されており、使い方を知っておきたく試してみました。
実施内容
EfficientNetをファインチューニングして犬・猫分類を実施してみる
EfficientNet利用手順
① 以下のKeras版実装を利用しました。準備は"pip install -U efficientnet"を実行するだけです。 注意点としては、Kerasのバージョンが2.2.0以上であることが指定されています。 始め2.1.5で試したところ、"DepthwiseConvolutionが無い"といったエラーが出ました。 github.com ② あとは実装ですが、まずプロジェクトフォルダが以下の構成になっていることが前提です。
- train
- cats(このフォルダに学習用猫画像が入っている)
- dogs(このフォルダに学習用犬画像が入っている)
- val
- cats(このフォルダに検証用猫画像が入っている)
- dogs(このフォルダに検証用犬画像が入っている)
- adam_mult_lr.py(ファインチューニング用にカスタマイズされたAdamオプティマイザ)
- data_loader.py(fit_generatorに使うgenerator生成クラス)
- train.py(学習実行ファイル)
まずadam_mult_lr.pyですが、ここでは学習済みEfficientNetを使ってファインチューニングしようとしています。 EfficientNetの特徴抽出部のレイヤーの学習係数を小さくしたいのですが、Kerasだと簡単には出来なさそうです。 ただ幸いこのAdamを使えばレイヤー毎に学習係数を変更できるよ、というブログを見つけたのでそれをコピーしてます。
次にdata_loader.pyですが、以前の記事に書いた雛形ほぼそのものになります。 注意点としては、Keras版EfficientNetは画像がRGBであることを期待しているっぽく、 opencvを使った場合はBGRをRGBに変換するロジック追加が必要です。
最後にtrain.pyは以下になります。見るとわかるのですが、ソースはかなり単純です。
from efficientnet import EfficientNetB0 from keras.layers import GlobalAveragePooling2D, Dense, Dropout from keras.models import Model from data_loader import DataLoader from adam_lr_mult import Adam_lr_mult def prepare_new_model(input_shape, class_count): # 学習済みモデルの取り出し feature_extractor = EfficientNetB0(input_shape=input_shape, weights='imagenet', include_top=False) # 犬猫分類器を引っ付ける x = feature_extractor.output x = GlobalAveragePooling2D()(x) x = Dense(512, activation='relu')(x) x = Dropout(rate=0.25)(x) x = Dense(class_count, activation='sigmoid')(x) # 新たなモデルの定義 model = Model(inputs=feature_extractor.input, outputs=x) print(model.summary()) return model def get_adam_for_fine_tuning(lr, decay, multiplier, model): lr_multiplier = {} # 自分が引っ付けたレイヤーの学習係数は1、学習済みの部分は小さな値を設定する for layer in model.layers: if 'dense' in layer.name: lr_multiplier[layer.name] = 1.0 else: lr_multiplier[layer.name] = multiplier return Adam_lr_mult(lr=lr, decay=decay, multipliers=lr_multiplier) def train(epochs, batch_size, input_shape, class_count): # 学習用画像データローダー train_data_loader = DataLoader('train', batch_size, input_shape, do_augmentation=True) train_generator = train_data_loader.get_data_loader() # 検証用画像データローダー val_data_loader = DataLoader('val', batch_size, input_shape, do_augmentation=False) val_generator = val_data_loader.get_data_loader() # モデルの生成 model = prepare_new_model(input_shape, class_count) # ファインチューニング用Adamオプティマイザ optimizer = get_adam_for_fine_tuning(lr=1e-3, decay=1e-5, multiplier=0.01, model=model) # コンパイルして model.compile(optimizer, loss='categorical_crossentropy', metrics=['accuracy']) # fit_generatorするだけ h = model.fit_generator(train_generator, train_data_loader.iterations, epochs, validation_data=val_generator, validation_steps=val_data_loader.iterations) # 学習データ、検証データのロスとAccをファイルに出力 with open('loss.csv', 'a') as f: for loss_t, acc_t, loss_v, acc_v in zip(h.history['loss'], h.history['acc'], h.history['val_loss'], h.history['val_acc']): f.write(str(loss_t) + ',' + str(acc_t) + ',' + str(loss_v) + ',' + str(acc_v) + '\n') if __name__ == '__main__': epochs = 5 batch_size = 16 input_shape = (224, 224, 3) class_count = 2 train(epochs, batch_size, input_shape, class_count)
以下は5エポック学習した時のAccの推移ですが、こんな適当に作った物でもかなり良い結果が得られています。
試しにResnet50でも同様の実験を行いました。論文上ではEfficientNetの方が僅かに精度が高いのですが、5エポック試した範囲ではResnet50の方が僅かに精度が高い結果となりました。
【Keras】fit_generatorに使うgeneratorの雛形メモ
クラス分類用のfit_generatorに使うgeneratorの雛形をメモします。 画像が格納されているフォルダが以下のような構造であることを前提とします。
- トップフォルダ
- class1フォルダ
- class1に属する画像ファイル
- class2フォルダ
- class2に属する画像ファイル
- 以下同様
- class1フォルダ
また、opencvやデータ拡張ライブラリのalbumentationsに依存しています。 ni4muraano.hatenablog.com
data_loader.py
import os, random, cv2 import numpy as np from albumentations import Compose from albumentations.augmentations.transforms import HorizontalFlip, Normalize class DataLoader(object): def __init__(self, data_folder, batch_size, input_shape, do_augmentation, gray_scale=False): self._file_paths = [] self._annotations = [] folders = os.listdir(data_folder) folders.sort() # 画像のパスとクラスIDを取得する for class_id, class_name in enumerate(folders): folder_path = data_folder + os.sep + class_name file_paths = [folder_path + os.sep + fn for fn in os.listdir(folder_path + os.sep)] self._file_paths += file_paths self._annotations += [class_id]*len(file_paths) # クラス数 self._class_count = class_id + 1 self._batch_size = batch_size self._input_shape = input_shape self._gray_scale = gray_scale if len(self._file_paths)%self._batch_size == 0: self.iterations = len(self._file_paths) // self._batch_size self._has_extra_data = False else: self.iterations = len(self._file_paths) // self._batch_size + 1 self._has_extra_data = True self._compose = self._define_augment(input_shape, do_augmentation) def _define_augment(self, input_shape, do_augmentation): # mean, std, max_pixel_valueは適宜変更してください mean = (0.485*255, 0.456*255, 0.406*255) std = (0.229*255, 0.224*255, 0.225*255) normalize = Normalize(mean=mean, std=std, max_pixel_value=1) # データ拡張内容は適宜変更してください if do_augmentation: return Compose([normalize, HorizontalFlip(p=0.5)]) else: return Compose([normalize]) def get_data_loader(self): while True: file_paths, annotations = self._shuffle(self._file_paths, self._annotations) for iteration in range(self.iterations): if iteration == self.iterations - 1 and self._has_extra_data: shape = (len(file_paths)%self._batch_size, self._input_shape[0], self._input_shape[1], self._input_shape[2]) else: shape = (self._batch_size, self._input_shape[0], self._input_shape[1], self._input_shape[2]) # バッチサイズ分のデータを取得する X = np.zeros(shape, dtype=np.float32) y = np.zeros((shape[0], self._class_count), dtype=np.float32) for i in range(X.shape[0]): index = self._batch_size*iteration + i if self._gray_scale: image = cv2.imread(file_paths[index], cv2.IMREAD_GRAYSCALE) image = image[:,:,np.newaxis] else: image = cv2.imread(file_paths[index]) image = cv2.resize(image, (self._input_shape[1], self._input_shape[0])) image = image.astype(np.float32) # データ拡張を実行する X[i] = self._augment(image) y[i, annotations[index]] = 1 yield X, y def _shuffle(self, x, y): p = list(zip(x, y)) random.shuffle(p) return zip(*p) def _augment(self, image): dict = {'image': image} augmented = self._compose(**dict) return augmented['image']
このdata_loader.pyは以下のように使います。
from data_loader import DataLoader train_data_loader = DataLoader('train', batch_size, input_shape, do_augmentation=True) train_generator = train_data_loader.get_data_loader() h = model.fit_generator(train_generator, train_data_loader.iterations, epochs)
データ分析・AIのビジネス導入を読んでのメモ
書籍「データ分析・AIのビジネス導入」を読んだので、気をつけたいことを自分用にメモします。
失敗しない データ分析・AIのビジネス導入: プロジェクト進行から組織づくりまで
- 作者: 株式会社ブレインパッド,太田満久,井上佳,今津義充,中山英樹,上総虎智,山?裕市,薗頭隆太,草野隆史
- 出版社/メーカー: 森北出版
- 発売日: 2018/07/13
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
データ分析プロジェクトの7つのリスク
- 時間と成果が比例しない
- データの量や質が不十分
- データのフォーマット、入手元へ依存する
- データのトレンドが変化してしまう
- 分析結果が当たり前の内容、もしくは悪い意味で想定外になる
- 分析結果の解釈が難しく現場で使われない
- PoCが終わりシステム化で失敗する
機械学習システムの短所を補う工夫
- 分析遂行前に業務ユースケースや必要な入出力情報、必要精度や評価指標を明確にする
- PoCを実施し、ビジネス適用可能性、性能にまつわる潜在リスク、ビジネスインパクトを把握する
- 結果に対して人手による調整を加える余地を残す
- 必要なデータを収集、分析する環境を確保する
よくある問題点と対応
課題 | 問題点 | 必要な対応 |
---|---|---|
ビジネスの理解 | 分析官と現場担当者のコミュニケーション不足により分析のゴールがビジネスの目的と乖離する | ビジネス上の課題を理解した上で、その課題達成に向けた適切な分析設計を行う |
データの理解・準備 | データを整理・集計した結果、分析に使用できるデータが少ない/存在しないことに気づく | データの質と量を把握して、ビジネス課題の達成に向けて必要なデータを収集・加工して準備する |
評価指標の選択 | 分析官と現場の合意がないままビジネス現場の目的と乖離した評価指標が選択されてしまう | ビジネス課題の達成度を測る評価指標と、分析精度を測る指標が一致するよう、適切な評価指標を選択する |
データ分析の仕事の流れ
- プロジェクト立ち上げ | |---ゴール設定 |---アセスメント |
- PoC | |---分析設計(分析に求められる要件の確認、分析アプローチの考案) |---実施・評価 |
- ビジネス適用 | |---実地試験 |---開発 |---運用・保守
プロジェクト立ち上げ詳細
ゴール設定 | |---プロジェクト目的の設定 |---ビジネスの理解 |---分析結果の活用方法検討 |---データ利活用で解決可能な目標の設定 | アセスメント | |---活用されるデータの収集と概要把握 |---スコープの設定 |---プロジェクトメンバーの選定と役割の設定
ビジネスの理解は「何が解決すべき課題か」だけでなく、「なぜそうなっているか」「どのようにすれば改善できるか」といった仮説の検討が重要。さらにかけれるコスト、課題が解決された後のあるべき姿を考えることでプロジェクトの意義が明確になる。
良い目標設定と悪い目標設定の例
良い例 ビジネス目的、具体的な目標、手元にあるデータ、分析アプローチに妥当性がある
目的 | 目標 | 良いポイント |
---|---|---|
ECサイト内での売上向上 | データから顧客の理解を深め、レコメンドアルゴリズムを洗練させることでCTRを1%向上させる | ビジネス上の目的と目標が明確であり、かつ関係が明らか |
製造コスト削減 | 製造物の需要を予測し、必要な資材の量を計算、さらに最適化を実施することで無駄な資材購入を削減する | ビジネス目的を需要予測と資材計画の最適化という2つの目標を組み合わせて達成しようとしている |
悪い例 ビジネス目的、具体的な目標、手元にあるデータ、手法のどれかに乖離がある
目的 | 目標 | 悪いポイント |
---|---|---|
データから設計図を書き起こす人件費の削減 | 深層学習を活用した図面設計AIをつくり出す | ①目標に具体性が欠ける。②何かを作り出すタイプの課題は、記録されているデータ以外の、その業務における常識や暗黙的な知識をもった人間がこなしている業務が対象とされており、データ上の内容をいくら機械学習モデルに読み込ませても目的を達成できない場合がある |
良品・不良品を見分ける業務を自動化・効率化する | 機械学習による異常検知を精度99%で実現する | ①アルゴリズムの精度そのものが目標になっており、ビジネス目的と直接的な整合性が取れていない。②どのような種類の精度かが不明瞭 |
データ収集で気にする必要がある点 ~ 既存データを活用する場合
データ管理者の特定(プロジェクトメンバー?他部署?社外?) 収集されているデータの粒度(日毎の予測を行いたいのに週毎のデータしかない等)
データ取得で気にする必要がある点 ~ これからデータを取得する場合
データ取得の難易度(データ取得者とデータ分析者が異なることにより、解析に使えないデータが集まる) 著作権等法律面の検討
データ取得で気にする必要がある点 ~ 既存データ/新規データ両方
- データ取得のコスト
- データの量や取得期間
- 取得期間内でデータ取得の方法やフォーマットが変更されていないか
- データの変遷
- 取得時のエラーの可能性
- 取得時点から活用可能になるまでのタイムラグ
PoCフェーズ詳細
- 分析要件の確認
- アプローチ概要の決定
- データ理解
- 分析設計
- 分析実施
- 結果考察・改善方針検討
分析要件の確認詳細
- 分析結果はどのようにビジネスに活用されるのか 1.1. 結果を人が見て意思決定の参考とする 1.2. 意思決定は人が行うが、意思決定者に具体的行動を提案する 1.3. 意思決定を含めた自動化を行う
- 何が対象か(全ての商品?主力商品のみ?)
- どんな出力値が必要化(分類?予測?可視化?)
- モデルにはどの程度解釈性が必要か
- 分析結果をどのように評価するか
- 利用するデータに制約はあるか
- 処理時間に制約はあるか(学習時間・予測時間)
- 環境面の制約はあるか(オンプレミス/クラウド、PCやプログラミング言語等)
アプローチ概要の決定詳細
- 教師あり問題として扱うのか、教師なし問題としてあつかうのか ⇒ 教師なしを選択するなら、何故教師ありでないのかを説明できること
- どんなデータを用いるか
- どんな手法を用いるか
- 分析結果をどのように評価するか
- どんな環境を用いるか
- データ分析特有のリスクについての対応方針
【Python】cv2.rectangleでのエラー"TypeError: an integer is required (got type tuple)"の対処方法
物体検出を行っていて、各クラスに色を割り当ててバウンディングボックスを描画したい、という状況でした。何故か修正前コードでは表題のエラーが発生し、修正後コードのような書き換えをしなければなりませんでした。本質でない部分に大分時間を使ったので、同じエラーが起きる場合に備えてメモしておきます。
修正前コード
# 各クラスに色を割り当てる colors = np.random.uniform(0, 255, (num_classes, 3)).astype(np.int32) # クラスに応じて異なる色でバウンディングボックスを描画 for i in range(boxes.size(0)): class_id = boxes[i, 0] box = boxes[i, 1:] pt1 = (box[0], box[1]) pt2 = (box[2], box[3]) color = tuple(colors[class_id]) # 何故かここで"TypeError: an integer is required"エラーが発生 cv2.rectangle(orig_image, pt1, pt2, color, 4)
修正後コード
# 各クラスに色を割り当てる colors = np.random.uniform(0, 255, (num_classes, 3)) # クラスに応じて異なる色でバウンディングボックスを描画 for i in range(boxes.size(0)): class_id = boxes[i, 0] box = boxes[i, 1:] pt1 = (box[0], box[1]) pt2 = (box[2], box[3]) # ここを変更した color = tuple(map(int, colors[class_id])) cv2.rectangle(orig_image, pt1, pt2, color, 4)
【Python】KPSS検定で単位根の有無を調べる
以下の書籍を読んでて、単位根の有無を調べるのにKPSS検定を行うと書かれている箇所があります(pp.67)。 このPythonコードが欲しかったのでメモしておきます。 ちなみにKPSS検定の帰無仮説は単位根なし、対立仮説は単位根ありとなります。
時系列分析と状態空間モデルの基礎: RとStanで学ぶ理論と実装
- 作者: 馬場真哉
- 出版社/メーカー: プレアデス出版
- 発売日: 2018/02/14
- メディア: 単行本
- この商品を含むブログ (3件) を見る
# ホワイトノイズに対してKPSS検定を行います。帰無仮説は棄却されません import numpy as np import statsmodels.api as sm def create_white_noise(n): return np.random.rand(n) - 0.5 if __name__ == '__main__': data = create_white_noise(1000) stats, p_value, lags, crit = sm.tsa.kpss(data) # 0.1が出力される print(p_value)
# ランダムウォークに対してKPSS検定を行います。帰無仮説は棄却されます import numpy as np import statsmodels.api as sm def create_random_walk(n): w = np.random.rand(n) w = np.where(w > 0.5, 1, -1) w = w.cumsum() return w if __name__ == '__main__': data = create_random_walk(1000) stats, p_value, lags, crit = sm.tsa.kpss(data) # 大体0.01が出力されます print(p_value)
# ランダムウォークの1階差分に対してKPSS検定を行います。帰無仮説は棄却されません import numpy as np import statsmodels.api as sm import pandas as pd def create_random_walk(n): w = np.random.rand(n) w = np.where(w > 0.5, 1, -1) w = w.cumsum() return w if __name__ == '__main__': data = create_random_walk(1000) data = pd.Series(data) data = data.diff().dropna() stats, p_value, lags, crit = sm.tsa.kpss(data) # 0.1が出力されます print(p_value)