種類が豊富な画像モデルライブラリpytorch-image-models(timm)の紹介
皆さんこんにちは
お元気ですか。私は小麦を暫く食べるだけにしたいです。(畑で見たくない‥)
さて、本日は最近勢いのあるモデルのライブラリ(pytorch-image-models)を紹介します。
pytorch-image-modelsとは
通称timmと呼ばれるパッケージです。
PyTorchの画像系のモデルや最適化手法(NAdamなど)が実装されています。
Kagglerもこのパッケージを利用することが増えています。
従来まで利用していたpretrained-models.pytorchは更新が止まっており、最新のモデルに追従できていないところがありました。
このモデルは例えば、EfficientNetやResNeStなどの実装もあります
モデルの検証も豊富でImageNetの様々なパタンで行われているのでこの中で最適なものを選択すると良いでしょう。詳しくはこちらへ。
インストールはpipから可能です。
pip install timm
モデルの利用方法
モデルの利用方法は簡単です。
timmモジュールのcreate_modelを用いることで、指定されたモデルを取得できます。
各モデルには、次のメソッドが実装されています。
forward・・・モデル全体の出力を獲得する
forward_features・・・特徴量出力部分(Pooling前)の出力を獲得する
どのようなモデルが実装されているかは、先述のResultsのページをご確認ください。
import timm import torch m = timm.create_model('efficientnet_b3', pretrained=True) m.eval() matrix = torch.rand(2, 3,224,224) m(matrix).size() # (2, 1000) m.forward_features(matrix).size() # (2, 1536, 7, 7)
最後に
非常にモデルの種類が豊富で使いやすいです。
これを利用すればほんの数行でResNeStなど新しいモデルを試すことができるので、これから使っていこうと思っています。
PyTorch XLAでTPUを操作する
皆さんこんにちは
お元気でしょうか。最近は宅配スーパーによりますます外出しなくなっています。
本日はTPUをPyTorchで使ってみます。
GPUと比較して、TPUは汎用性をなくした代わりによりDeepLearningに必要な演算を高速にできるようにしたものです。
Why TPU?
TPUとは?
TPUはニューラルネットワークの演算専用のアーキテクチャです。
一言で使うモチベーションをお伝えするのであれば「速いから」の一言です
他の用途には利用できない分、大規模な乗算と加算を高速に演算できます。
詳細は次のサイトを見ていただくのが良いと思います。
www.atmarkit.co.jp
cloud.google.com
どの程度高速なのか
Cloud TPU | Google Cloud(次の図は左記から引用)によれば、GPUと比較して27倍の高速化と38%のコストを抑えました。
TPUを有効活用すれば、高速な計算を実現しつつ低コストな計算が可能です。
どうやって使うのか
現在は3つ選択肢があります。
- Google Colaboratory
- Google Cloud Computing
- Kaggle Kernel
Google Cloud Computingは一応使えますが、制限があります。また、Kaggle Kernelも週30hと時間制限があります。
まずは、今回はGoogle Colaboratoryで試すことにします。
※確か性能はKaggle Kernelのほうが良かったはずです。
PyTorch-XLA
Google社の製品であるため、当然ですが、Tensorflowに対応しています。
これをPyTorchで動作させることも可能です。そのライブラリがPyTorch-XLAです。
PyTorch-XLAのXLAはAccelerated Linear Algebraです。
XLAはTensorflowで利用されていますが、PyTorchのInterfaceでCloud TPUを利用するようにしたものです。
インストール
!pip install
実装
以下の実装を利用します。
colab.research.google.com
インストール部
インストールは次の通りです。
この実装を利用すれば、問題ありません。
VERSION = "20200325" #@param ["1.5" , "20200325", "nightly"] !curl https://raw.githubusercontent.com/pytorch/xla/master/contrib/scripts/env-setup.py -o pytorch-xla-env-setup.py !python pytorch-xla-env-setup.py --version $VERSION
結果可視化
# Result Visualization Helper from matplotlib import pyplot as plt M, N = 4, 6 RESULT_IMG_PATH = '/tmp/test_result.jpg' CIFAR10_LABELS = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck'] def plot_results(images, labels, preds): images, labels, preds = images[:M*N], labels[:M*N], preds[:M*N] inv_norm = transforms.Normalize( mean=(-0.4914/0.2023, -0.4822/0.1994, -0.4465/0.2010), std=(1/0.2023, 1/0.1994, 1/0.2010)) num_images = images.shape[0] fig, axes = plt.subplots(M, N, figsize=(16, 9)) fig.suptitle('Correct / Predicted Labels (Red text for incorrect ones)') for i, ax in enumerate(fig.axes): ax.axis('off') if i >= num_images: continue img, label, prediction = images[i], labels[i], preds[i] img = inv_norm(img) img = img.permute(1, 2, 0) # (C, M, N) -> (M, N, C) label, prediction = label.item(), prediction.item() if label == prediction: ax.set_title(u'\u2713', color='blue', fontsize=22) else: ax.set_title( 'X {}/{}'.format(CIFAR10_LABELS[label], CIFAR10_LABELS[prediction]), color='red') ax.imshow(img) plt.savefig(RESULT_IMG_PATH, transparent=True) ||< *** モデル構築部 モデルの構成は標準的なResNet18です。 ここで唯一変わるのは、torch_xlaのモジュールをインポートしていることです。 >|python| import numpy as np import os import time import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import torch_xla import torch_xla.core.xla_model as xm import torch_xla.debug.metrics as met import torch_xla.distributed.parallel_loader as pl import torch_xla.distributed.xla_multiprocessing as xmp import torch_xla.utils.utils as xu import torchvision from torchvision import datasets, transforms class BasicBlock(nn.Module): expansion = 1 def __init__(self, in_planes, planes, stride=1): super(BasicBlock, self).__init__() self.conv1 = nn.Conv2d( in_planes, planes, kernel_size=3, stride=stride, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(planes) self.conv2 = nn.Conv2d( planes, planes, kernel_size=3, stride=1, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(planes) self.shortcut = nn.Sequential() if stride != 1 or in_planes != self.expansion * planes: self.shortcut = nn.Sequential( nn.Conv2d( in_planes, self.expansion * planes, kernel_size=1, stride=stride, bias=False), nn.BatchNorm2d(self.expansion * planes)) def forward(self, x): out = F.relu(self.bn1(self.conv1(x))) out = self.bn2(self.conv2(out)) out += self.shortcut(x) out = F.relu(out) return out class ResNet(nn.Module): def __init__(self, block, num_blocks, num_classes=10): super(ResNet, self).__init__() self.in_planes = 64 self.conv1 = nn.Conv2d( 3, 64, kernel_size=3, stride=1, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(64) self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1) self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2) self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2) self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2) self.linear = nn.Linear(512 * block.expansion, num_classes) def _make_layer(self, block, planes, num_blocks, stride): strides = [stride] + [1] * (num_blocks - 1) layers = [] for stride in strides: layers.append(block(self.in_planes, planes, stride)) self.in_planes = planes * block.expansion return nn.Sequential(*layers) def forward(self, x): out = F.relu(self.bn1(self.conv1(x))) out = self.layer1(out) out = self.layer2(out) out = self.layer3(out) out = self.layer4(out) out = F.avg_pool2d(out, 4) out = torch.flatten(out, 1) out = self.linear(out) return F.log_softmax(out, dim=1) def ResNet18(): return ResNet(BasicBlock, [2, 2, 2, 2])
学習
さて、TPUを使う上で大きく変わってくるのはここからです。
次の通りです。
def train_resnet18(): torch.manual_seed(1) # Get and shard dataset into dataloaders norm = transforms.Normalize( mean=(0.4914, 0.4822, 0.4465), std=(0.2023, 0.1994, 0.2010)) transform_train = transforms.Compose([ transforms.RandomCrop(32, padding=4), transforms.RandomHorizontalFlip(), transforms.ToTensor(), norm, ]) transform_test = transforms.Compose([ transforms.ToTensor(), norm, ]) train_dataset = datasets.CIFAR10( root=os.path.join(FLAGS['data_dir'], str(xm.get_ordinal())), train=True, download=True, transform=transform_train) test_dataset = datasets.CIFAR10( root=os.path.join(FLAGS['data_dir'], str(xm.get_ordinal())), train=False, download=True, transform=transform_test) train_sampler = torch.utils.data.distributed.DistributedSampler( train_dataset, num_replicas=xm.xrt_world_size(), rank=xm.get_ordinal(), shuffle=True) train_loader = torch.utils.data.DataLoader( train_dataset, batch_size=FLAGS['batch_size'], sampler=train_sampler, num_workers=FLAGS['num_workers'], drop_last=True) test_loader = torch.utils.data.DataLoader( test_dataset, batch_size=FLAGS['batch_size'], shuffle=False, num_workers=FLAGS['num_workers'], drop_last=True) # Scale learning rate to num cores learning_rate = FLAGS['learning_rate'] * xm.xrt_world_size() # (2) Tensor Coreのコア数 # Get loss function, optimizer, and model device = xm.xla_device() # (3) 実行デバイスの取得 model = ResNet18().to(device) optimizer = optim.SGD(model.parameters(), lr=learning_rate, momentum=FLAGS['momentum'], weight_decay=5e-4) loss_fn = nn.NLLLoss() def train_loop_fn(loader): tracker = xm.RateTracker() model.train() for x, (data, target) in enumerate(loader): optimizer.zero_grad() output = model(data) loss = loss_fn(output, target) loss.backward() xm.optimizer_step(optimizer) tracker.add(FLAGS['batch_size']) if x % FLAGS['log_steps'] == 0: print('[xla:{}]({}) Loss={:.5f} Rate={:.2f} GlobalRate={:.2f} Time={}'.format( xm.get_ordinal(), x, loss.item(), tracker.rate(), tracker.global_rate(), time.asctime()), flush=True) # get_ordinalは動作しているプロセスの番号 def test_loop_fn(loader): total_samples = 0 correct = 0 model.eval() data, pred, target = None, None, None for data, target in loader: output = model(data) pred = output.max(1, keepdim=True)[1] correct += pred.eq(target.view_as(pred)).sum().item() total_samples += data.size()[0] accuracy = 100.0 * correct / total_samples print('[xla:{}] Accuracy={:.2f}%'.format( xm.get_ordinal(), accuracy), flush=True) return accuracy, data, pred, target # Train and eval loops accuracy = 0.0 data, pred, target = None, None, None for epoch in range(1, FLAGS['num_epochs'] + 1): para_loader = pl.ParallelLoader(train_loader, [device]) train_loop_fn(para_loader.per_device_loader(device)) xm.master_print("Finished training epoch {}".format(epoch)) para_loader = pl.ParallelLoader(test_loader, [device]) accuracy, data, pred, target = test_loop_fn(para_loader.per_device_loader(device)) if FLAGS['metrics_debug']: xm.master_print(met.metrics_report(), flush=True) return accuracy, data, pred, target # Start training processes def _mp_fn(rank, flags): global FLAGS FLAGS = flags torch.set_default_tensor_type('torch.FloatTensor') accuracy, data, pred, target = train_resnet18() if rank == 0: # Retrieve tensors that are on TPU core 0 and plot. plot_results(data.cpu(), pred.cpu(), target.cpu()) xmp.spawn(_mp_fn, args=(FLAGS,), nprocs=FLAGS['num_cores'], start_method='fork') # (1) 実行用のプロセス起動します。
#### (1) xmp.spawnによるTPU上での実行
xmp.spawnはXLAのデバイス(TPU)で実行するためのプロセスを起動します。
#### (2) Tensor Coreのコア数
xm.xrt_world_size()はコア数(並列数)を取得できます。
この部分の実装はバッチのサイズに応じて最適化手法(SGD)の学習率を調整しています。
#### (3) 実行デバイスの取得
xm.xla_device()の利用により、デバイス情報を取得でき、TPU/CPUのどちらかが返ってきます。
#### (4) master_printを使った出力
xlaのデバイス上ではmaster_printを利用して出力しています。
masterのデバイスで出力するAPIとなります。(この手の内容ちゃんとした説明が見つからない‥)
### 利用後の感想
肌感として非常にGPUよりも高速ではあります。
概ね実装はほぼ同じで、少しの差分を修正すれば動作します。
ただ、時々メモリエラーを起こすなど不安定になることがあります。
この原因の特定が難しく今の所プロファイラが見当たらないため、まだまだ挙動が読めておらず、エラーの原因がわからないことも多くあります。
最後に
TPUを有効活用できれば、生産性が上がるのかなと思っています。
ただ、まだまだ私自身、使いこなすにはほど遠いと思っているところです。
少しずつ使いこなしていき、生産性を上げられるようにしたいと思っています。
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社によるものです。
Chainerの頃にはあったTrainerに似ている構成(厳密にはクラス構成が異なる)や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の実装など、よりシンプルにかけそうだといった印象で実利用時(これから)に期待を持っています。
研究開発がメインな身としてはより開発が進むと嬉しいと感じています。
NGBoostを使ってみた
皆さんこんにちは。
お元気ですか。自粛ムードはまだまだ続きそうですね。
本日は少し前の発表された勾配ブースティングの手法NGBoostを紹介しようと思います。
NGBoostについて
NGBoostとは?
NGBoostは予測の不確実性をも推定する勾配ブースティングのアルゴリズムです。
従来までは、例えば温度を推定する場合、30度しか出ませんでした。
しかし、このNGBoostでは、どの程度30度らしいかも推定できます。
Kaggleよりも業務でフォールバックの機能として使えるので、便利で使い方の幅も感じるところです。
インストール
他のライブラリ同様、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をとりあえず使ってみました。
ただ、使ったばかりもあり、性能や精度の比較はまだ検討できていないです。
そのため、有用であればコンペでも使いたいと思っています。