のんびりしているエンジニアの日記

ソフトウェアなどのエンジニア的な何かを書きます。

Albumentationの拡張方法

皆さんこんにちは
お元気ですか。週末はALASKA2の準備をはじめてみました。

さて、今回のAlbumentationの拡張方法を書いておきます。
AlbumentationはKaggleなどの画像コンペでよく利用されるData Augmentation(データ拡張)のライブラリです。
Albumentationは様々なシチュエーションを想定して継承元のクラスがいくつかあります。
これらを継承し実装することで新しいAugmentationのクラスを開発できます。

今回はどうすればAlbumentationで新しいData Augmentationのクラスを作成できるのかを記載します。

Albumentationの構造

Albumentationは次の2つの基底となるクラスが存在します。

  • ImageOnlyTransform・・画像のみに処理を適用する
  • DualTransform・・画像+物体検出(+その他)に処理を適用する

新しいData Augmentationのクラスを作成する場合これらを継承して作ります。
具体的に作成する必要があるのは次のメソッドです。
ただし、コンペで利用する場合、全てを実装する必要はなく、この中から必要なものを実装すれば良いでしょう。

メソッド 説明
__init__ パラメータ
get_params パラメータを取得する。ランダムで引数を取得するのはこの箇所
apply 画像に対して適用する処理を実装する
apply_to_mask マスクに対して適用する処理を実装する(Segmentation系限定)
apply_to_masks 複数のマスクに対して適用する処理を実装する(Segmentation系限定)
apply_to_bbox バウンディングボックスに対して適用する処理を実装する(物体検出系限定)
apply_to_keypoints キーポイントに対して適用する処理を実装する

Albumentstionの新しいクラスを作る

では、Albumentationのクラスを作成してみましょう。
今回はRandomErasingと呼ばれる画像の一部分を置き換える(削除する)拡張です。
applyのみ実装すれば可能ですので、最初のサンプルとしては便利かと思います。

実装は次の通りです。

class RandomErasing(ImageOnlyTransform):
    def __init__(self, always_apply=False, p=0.5, sl=0.02, sh=0.4, r1=0.3, mean=[0.4914, 0.4822, 0.4465]):
        super(RandomErasing, self).__init__(always_apply=always_apply, p=p) # (1) 継承元のクラスへ渡すパラメータ(=おまじまい)
        self.mean = mean # (2)パラメータ
        self.sl = sl
        self.sh = sh
        self.r1 = r1

    def apply(self, img, **params):
        # (3) 実行
        for attempt in range(100):
            area = img.shape[0] * img.shape[1]

            target_area = random.uniform(self.sl, self.sh) * area
            aspect_ratio = random.uniform(self.r1, 1 / self.r1) 

            h = int(round(math.sqrt(target_area * aspect_ratio)))
            w = int(round(math.sqrt(target_area / aspect_ratio)))

            if w < img.shape[1] and h < img.shape[0]:
                x1 = random.randint(0, img.shape[0] - h)
                y1 = random.randint(0, img.shape[1] - w)
                if len(img.shape) == 3:
                    img[x1:x1 + h, y1:y1 + w, 0] = self.mean[0]
                    img[x1:x1 + h, y1:y1 + w, 1] = self.mean[1]
                    img[x1:x1 + h, y1:y1 + w, 2] = self.mean[2]
                else:
                    img[x1:x1 + h, y1:y1 + w] = self.mean[0]
                return img

        return img
(1) 継承元のクラスへパラメータを渡す事が必要です。

always_applyはこのパラメータがTrueの場合に常に適用することを示し、pはこのData Augmentationを実行する確率を示します。

(2) パラメータの設定

コンストラクタ側にパラメータを設定しています。
これらの変数はapplyメソッドで参照します。

(3) 実行

RandomErasingの実装部分になります。
ここからはRandomErasingの実装になるので詳細は割愛しますが、通常の実装をこなせば問題ありません。
このメソッドの返り値は処理後の画像です。

後は通常通りAlbumentationに組み込んであげれば、Albumentationの世界で動作します。

最後に

私も頻繁にこのライブラリを利用しますが、やはり、Albumentation上で処理できるとやはり便利です。
新しいアルゴリズムを簡単に試すことも可能なのでぜひ、試してみてください。

PyTorchの研究開発を加速する「pytorch-pfn-extras」を紹介します

皆さんこんにちは
お元気ですか。ついにクーラーが必要になってきました。
電気代が心配ですがなんとかなるでしょう。

本日はPyTorchの研究開発を加速する「pytorch-pfn-extras」を紹介します。

pytorch-pfn-extras

pytorch-pfn-extrasとは

PyTorchを使った研究開発の促進のために開発されているライブラリです。
こちらの開発元はChainerを開発していたPreferred Networks社によるものです。

github.com

Chainerの頃にはあったTrainerに似ている構成(厳密にはクラス構成が異なる)やIgnite連携が用意されており、便利に使えるのでは?と思っています。

インストール

pip install pytorch-pfn-extras

その他igniteのモジュール利用する場合は追加でライブラリが必要です(インストール方法参照)

MNISTサンプル

実装はこちらを参考にしてください、必要なポイントを解説します。
github.com

ニューラルネットワークの定義

ニューラルネットワークの定義はPyTorchの実装とほぼ同じです。
しかし、一つ違う点として、LazyConv2DやLazyLinearと呼ばれるモジュールをppeは独自実装しています。
PyTorchのConv2DやLinearの場合、入力するチャネルや次元数の指定が必要ですが、Lazy-の場合はその指定を省けます。

class Net(nn.Module):
    def __init__(self, lazy):
        super().__init__()
        if lazy:
            self.conv1 = ppe.nn.LazyConv2d(None, 20, 5, 1)
            self.conv2 = ppe.nn.LazyConv2d(None, 50, 5, 1)
            self.fc1 = ppe.nn.LazyLinear(None, 500)
            self.fc2 = ppe.nn.LazyLinear(None, 10)
        else:
            self.conv1 = nn.Conv2d(1, 20, 5, 1)
            self.conv2 = nn.Conv2d(20, 50, 5, 1)
            self.fc1 = nn.Linear(4*4*50, 500)
            self.fc2 = nn.Linear(500, 10)

学習部

ExtensionsManagerと呼ばれるクラスに拡張機能を管理するものがあります。
このManagerに設定することで、いつ、学習を止めるか、レポーティングを行うかなど、設定が可能です。
また、共通的に書かないといけないiteration回数を止める処理などを書く必要がほぼなくなります。

Chainer時代のTrainerだと、学習のメソッドを拡張するなりしなければなりませんが昨今の学習時にいじる系統(MixUpなど)だと不便になってくると思っていました。
train関数に学習中の処理も実装できるのでより一層、研究向きとしては便利になった印象です。

    manager = ppe.training.ExtensionsManager(
        model, optimizer, args.epochs,
        extensions=my_extensions,
        iters_per_epoch=len(train_loader),
        stop_trigger=trigger)

def train(manager, args, model, device, train_loader, optimizer):
    while not manager.stop_trigger:
        model.train()
        for batch_idx, (data, target) in enumerate(train_loader):
            with manager.run_iteration():
                data, target = data.to(device), target.to(device)
                optimizer.zero_grad()
                output = model(data)
                loss = F.nll_loss(output, target)
                ppe.reporting.report({'train/loss': loss.item()})
                loss.backward()
                optimizer.step()

拡張機能

extensionsのモジュールでは指定すれば様々なことが可能です。
このあたりの名前はChainerを継承しているので、元Chainer Userとしてはわかりやすい印象。
重みのスナップショット、統計情報の計算、ログの記録などを可能としています。
個人的にMLFlowあたりと連携してくれるととても嬉しいのですが、対応していただける日は来るのだろうか(「作って」みたいな要望が来そうだ‥)。

    my_extensions = [
        extensions.LogReport(),
        extensions.ProgressBar(),
        extensions.observe_lr(optimizer=optimizer),
        extensions.ParameterStatistics(model, prefix='model'),
        extensions.VariableStatisticsPlot(model),
        extensions.Evaluator(
            test_loader, model,
            eval_func=lambda data, target:
                test(args, model, device, data, target),
            progress_bar=True),
        extensions.PlotReport(
            ['train/loss', 'val/loss'], 'epoch', filename='loss.png'),
        extensions.PrintReport(['epoch', 'iteration',
                                'train/loss', 'lr', 'model/fc2.bias/grad/min',
                                'val/loss', 'val/acc']),
        extensions.snapshot(),
    ]

最後に

PyTorchの便利モジュールが存在すること。
また、色々な拡張機能により、Kaggleの実装など、よりシンプルにかけそうだといった印象で実利用時(これから)に期待を持っています。
研究開発がメインな身としてはより開発が進むと嬉しいと感じています。

Bengali.AIの解説をYoutubeでやってみました

皆さんこんにちは
お元気ですか。動画編集処理スキルがなさすぎて非常に困惑しております。

さて少し前のコンペティションになりますが、Bengali.AIのコンペティションの解説をYoutubeでやってみました。
※Bengali.AIのコンペのページはこちらです。

www.kaggle.com

コロナで集まって反省会するのも難しいご時世ですので、このYoutubeの流れに乗ってみるのもありかなと思ってはじめてみました。
堅苦しく集まって見るよりも、なにかの暇つぶしのお供ぐらいで見てもらえればと思います。

Bengaliはベンガル語の手書き文字画像から3種からなるベンガル語の構成要素を当てるコンペです。
ベンガル語は3つの構成要素から成り立ち、組み合わせで文字が完成します。
学習には存在しない文字があり、工夫しがいのあるコンペだったように思えます。(おかげで、Shake upが激しいコンペの一つになりました)

SlideshareYoutubeは次の通りです。

Slideshare

www.slideshare.net

Youtube
www.youtube.com

気が向いたら他のコンペもやりますので、要望があればぜひ、教えて下さい。(小麦は多分、やります。Tweet?知らない子ですね)
では。また、お会いしましょう。

CMakeを使ってビルドのみしている人のためのCMake入門

皆さんこんにちは
お元気ですか。ついに自宅のパスタがなくなりそうです。

さて、本日のテーマはcmakeです。
大半の人のcmake、実はこんな感じなのではないでしょうか。

  • インストール時にお世話になる(おまじない)けど、何もわからない(文字通り)
  • 仮に見たとしても何をやっているのかわからない。

私もある程度、これらに該当する人なわけです。
しかし、このおまじない系はまると長時間はまってとても困るといった問題がありました。
ということで改めてcmakeをきちんと勉強しておこうと思った次第です。

CMake

CMakeが何かはWikipediaにも書いてあります。

CMakeはコンパイラに依存しないビルド自動化のためのフリーソフトウェアであり、様々なオペレーティングシステムで動作させることができる。CMakeは階層化ディレクトリや複数のライブラリを利用するアプリケーションをサポートするよう設計されている。

環境によってもコンパイル方法(g++/clangなど)が異なるため、それぞれで設定を書く必要があります。
また、C++特有の依存関係を記述していくのは大変です。
それらを一括してCMakeは管理してくれます。

インストール

パッケージ管理ソフト(apt-get/homebrew/yum)でインストール可能です。

Ubuntu
sudo apt-get install cmake
Mac(Homebrew)
brew install cmake
CentOS
sudo  yum install cmake

CMakeを使ってC++コンパイルを行ってみる。

構成の準備

次のサイトのサンプルを参考にします。
derekmolloy.ie

helloworld.cpp/CMakeList.txtは同じディレクトリに配置してください。

1. helloworld.cpp

#include<iostream>

int main(int argc, char *argv[]){
   std::cout << "Hello World!" << std::endl;
   return 0;
}

プログラムはただの「Hello World!」出力用のプログラムです。

2. CMakeLists.txt

cmake_minimum_required(VERSION 2.8.9)
project (hello)
add_executable(hello helloworld.cpp)

cmake_minimum_requiredはcmakeの最低要求のバージョン
projectはプロジェクト名を示します。(挙動には関係ありません。)
今回の場合、add_executableはhelloworld.cppからhelloのファイルを作ります。

CMakeを試す

試してみましょう、次のコマンドで実行します。

mkdir build && cd build
cmake ..

buildディレクトリ作成もおまじないのようにさらっと記述されています。
これは、cmakeした結果のファイルが生成されるので、それ用です。
構成管理ツールでの管理を考えた時に、buildディレクトリを無視すればよいだけなので、管理しやすそうです。

cmakeが完了するとCMakeCache.txtなど、様々なファイルが生成されます。
これらはmakeを使う時に必要なファイル群です。

最後にmakeを実行すれば、実行ファイルの生成がされます。
後は実行ファイルをそのまま実行できます。

その他CMakeについて

CMakeでよく利用されるOption

特によく利用されるOptionは-Dです。
cmakeで環境を示す変数を指定しますが、-D Optionを付与することで、それよりも優先させることができます。
複数の環境を利用していると、デフォルトよりも特定の環境を指定したいこともあります。

その場合に-D Optionを利用して強制的に書き換えることもあります。

どんな変数が設定できるのかは「cmake .. -L」を実行すれば、確認可能です。

CMakeで作成されるMakefileのコマンド

CMakeで作成されるMakefileでよく利用されるコマンドをここでは紹介します
CMakeで作られたMakefileのおまじないの一種として「make -j8」やら「make install」といったコマンドがあります。

「make」では、ソースコードのビルドが行われます。殆どの場合はC++コンパイルですね。
また、「make install」を行うことで、規定のディレクトリに配置されます。これによって外部からでも呼び出し可能になります。

途中までのビルド結果を消したい場合は「make clean」を利用すれば良いでしょう。

ちなみに、よく使うOption「make -j」は並列数を指定できます。許せる限りのCPUコア数を数値として入れると良いでしょう。
ただし、コア数に対してジョブが多すぎると逆効果になるため、慎重に。(一度メモリ不足か何かで誤ってえらい目にあったことがあります)

最後に

おまじない(※僕だけかもしれない)を真面目に解説してみました。
一度はまったときにあれっと思うので、このようなことを勉強しておくのも時には必要でしょう。

その場合にぜひ、見てほしいと思います。

NGBoostを使ってみた

皆さんこんにちは。
お元気ですか。自粛ムードはまだまだ続きそうですね。

本日は少し前の発表された勾配ブースティングの手法NGBoostを紹介しようと思います。

NGBoostについて

NGBoostとは?

NGBoostは予測の不確実性をも推定する勾配ブースティングのアルゴリズムです。
従来までは、例えば温度を推定する場合、30度しか出ませんでした。
しかし、このNGBoostでは、どの程度30度らしいかも推定できます。
Kaggleよりも業務でフォールバックの機能として使えるので、便利で使い方の幅も感じるところです。

github.com

インストール

他のライブラリ同様、pipコマンド一発でインストールできます。

pip install https://github.com/stanfordmlgroup/ngboost.git

使い方

分類

from ngboost import NGBClassifier

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from ngboost.distns import k_categorical

X, Y = load_iris(True)
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2)

ngb = NGBClassifier(Dist=k_categorical(3)).fit(X_train, Y_train)
Y_preds = ngb.predict(X_test)
Y_dists = ngb.pred_dist(X_test)

Distの引数にk_categoricalを与える必要があります。
このk_categorialには、分類数と同じ値を与えることが必要です。
Y_distsで各クラスの確率値を獲得できます。

回帰

from ngboost import NGBRegressor

from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

X, Y = load_boston(True)
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2)

ngb = NGBRegressor().fit(X_train, Y_train)
Y_preds = ngb.predict(X_test)
Y_dists = ngb.pred_dist(X_test)

# test Mean Squared Error
test_MSE = mean_squared_error(Y_preds, Y_test)
print('Test MSE', test_MSE)

# test Negative Log Likelihood
test_NLL = -Y_dists.logpdf(Y_test).mean()
print('Test NLL', test_NLL)

こちらもサンプル通りです。
NGBRegressorには特に引数を与えなくとも、実行ができます。
分類と同様、pred_distメソッドにより分布を計算できます。

最後に

NGBoostをとりあえず使ってみました。
ただ、使ったばかりもあり、性能や精度の比較はまだ検討できていないです。
そのため、有用であればコンペでも使いたいと思っています。