はじめに
Google ColaboratoryでTPUを使ってPyTorchのモデルを作成するプログラムです。
また、検証データの推論結果とテストデータの分布から、リーダーボードの予想スコア(Estimated LB)を計算します。
Google Colaboratoryでは8個のTPUを使うことができ、1個のGPUで計算した場合より早くモデルを作成できます。
ただし、TPUはbfloat16で計算しているはずですので、GPUで計算した場合と計算結果が異なる可能性があります(googleが発表する論文には、推論はTPUで計算していても学習はGPUで計算しているものがあります)。
TPUを使いたい場合はTensorflowでモデルを作るというのが常套手段ですが、torch_xlaを使うことでPyTorchのモデルをTPUで実行できます。
torch_xlaのgithubリポジトリ
https://github.com/pytorch/xla
GPUを使ってモデルを作成する方法はこちら
https://prob.space/competitions/ukiyoe-author/discussions/takedarts-Post852f7083860909eb1acb
このモデルのLBスコア: 0.728
事前準備
以下のファイルをgoogle driveにアップロードしておきます。
- 学習用ラベルデータ : ProbSpace/ukiyoe/ukiyoe-train-labels.npz
- 学習用画像データ: ProbSpace/ukiyoe/ukiyoe-train-imgs.npz
セットアップ
torch_xlaとそれに対応するtorchとtorchvisionをインストールしなおします。
これらのライブラリはgoogle colaboratoryにインストールされているtensorflowのバージョンに合わせる必要があるようで、最新版を入れれば動作するというわけではありません。
OpenMPのライブラリも必要になるので、これもインストールします。
学習
モデルはpre-definedのResNet18にしています。
pytorchのモデルをTPUで実行するためには、学習を実行する関数(train_fun)と検証を実行する関数(valid_func)を作成し、モデルを登録したDataParallelオブジェクトにこれらの関数を渡すことで学習/検証が実行されます。
原因は分からないのですが、検証実行時にtorch.no_grad()を使うと計算時間が大幅に増えましたので、検証時も計算グラフを作るプログラムになっています。
また、CPU/TPUのデータ転送のコストも大きいようで、不用意にデータ転送を行うコードを入れるとパフォーマンスが落ちます。
class Classifier(nn.Module):
def __init__(self, model):
super().__init__()
self.model = model
self.criterion = nn.CrossEntropyLoss()
def forward(self, images, labels):
preds = self.model(images)
loss = self.criterion(preds, labels)
accuracy = preds.max(dim=1)[1].eq(labels).float().mean()
return loss, accuracy
def perform(model, loader, optimizer):
loss_total = 0
accuracy_total = 0
count = 0
for images, labels in loader:
loss, accuracy = model(images, labels)
if optimizer is not None:
optimizer.zero_grad()
loss.backward()
xm.optimizer_step(optimizer)
loss_total += float(loss) * len(images)
accuracy_total += float(accuracy) * len(images)
count += len(images)
return np.array(
(loss_total / count, accuracy_total / count), dtype=np.float64)
def train_func(model, loader, device, context):
optimizer = context.getattr_or('optimizer',
lambda: optim.Adam(model.parameters(), lr=0.001))
model.train()
return perform(model, loader, optimizer)
def valid_func(model, loader, device, context):
model.eval()
return perform(model, loader, None)
# モデル作成
model_parallel = dp.DataParallel(Classifier(
models.resnet18(pretrained=False, num_classes=10)))
print(f'tpu devices: {model_parallel.devices}')
# 学習
log = []
for epoch in range(10):
results = model_parallel(train_func, train_loader)
train_loss, train_accuracy = sum(results) / len(results)
results = model_parallel(valid_func, valid_loader)
valid_loss, valid_accuracy = sum(results) / len(results)
print('[{}] train(loss/accuracy)={:.2f}/{:.2f}, valid(loss/accuracy)={:.2f}/{:.2f}'.format(
epoch + 1, train_loss, train_accuracy, valid_loss, valid_accuracy))
log.append((epoch + 1, train_loss, train_accuracy, valid_loss, valid_accuracy))
# モデルを保存
model_copy = copy.deepcopy(model_parallel.models[0].model).to('cpu')
torch.save(model_copy.state_dict(), 'resnet18.pth')
# 結果表示
figure = plt.figure(figsize=(8, 3))
axis = figure.add_subplot(1, 2, 1)
axis.plot([x[0] for x in log], [x[1] for x in log], label='train')
axis.plot([x[0] for x in log], [x[3] for x in log], label='valid')
axis.set_xlabel('epoch')
axis.set_ylabel('loss')
axis.legend()
axis = figure.add_subplot(1, 2, 2)
axis.plot([x[0] for x in log], [x[2] for x in log], label='train')
axis.plot([x[0] for x in log], [x[4] for x in log], label='valid')
axis.set_xlabel('epoch')
axis.set_ylabel('accuracy')
axis.legend()
plt.show()
tpu devices: ['xla:1', 'xla:2', 'xla:3', 'xla:4', 'xla:5', 'xla:6', 'xla:7', 'xla:8']
[1] train(loss/accuracy)=2.37/0.22, valid(loss/accuracy)=6.11/0.05
[2] train(loss/accuracy)=1.49/0.46, valid(loss/accuracy)=5.69/0.11
[3] train(loss/accuracy)=1.25/0.57, valid(loss/accuracy)=1.92/0.39
[4] train(loss/accuracy)=1.10/0.61, valid(loss/accuracy)=1.88/0.43
[5] train(loss/accuracy)=0.99/0.64, valid(loss/accuracy)=1.63/0.45
[6] train(loss/accuracy)=0.91/0.67, valid(loss/accuracy)=1.69/0.53
[7] train(loss/accuracy)=0.83/0.71, valid(loss/accuracy)=1.28/0.54
[8] train(loss/accuracy)=0.79/0.72, valid(loss/accuracy)=1.04/0.65
[9] train(loss/accuracy)=0.75/0.74, valid(loss/accuracy)=1.12/0.62
[10] train(loss/accuracy)=0.68/0.76, valid(loss/accuracy)=0.90/0.71
/usr/local/lib/python3.6/dist-packages/torch_xla_py/__init__.py:2: UserWarning: torch_xla_py has been restructured to torch_xla and it will be removed soon, please call the submodules in torch_xla directly.
warnings.warn('torch_xla_py has been restructured to torch_xla and it will be removed soon, '
リーダーボードのスコアを予測
検証データの推論結果を解析して、このモデルがリーダーボードで獲得するであろうスコアを予測します。
テストデータに含まれる各クラスの割合を、あらかじめ調べておきました(すべて同じクラスにしたsubmissionを提出すると割合が得られます)。
テストデータに含まえるクラスiの割合がpi、クラスiの検証データの再現率がriの場合、リーダーボードの予想スコアを∑piriとしています。
-- predictions (L:Labels, P:Predictions)
|P=0|P=1|P=2|P=3|P=4|P=5|P=6|P=7|P=8|P=9
L=0|120| 4| 0| 0| 0| 4| 1| 0| 0| 0
L=1| 10| 88| 7| 3| 6| 1| 18| 0| 0| 1
L=2| 0| 0| 43| 0| 3| 2| 3| 1| 5| 0
L=3| 0| 8| 1| 18| 4| 0| 3| 5| 0| 0
L=4| 0| 4| 9| 0| 54| 0| 5| 0| 4| 0
L=5| 8| 4| 1| 0| 4| 18| 0| 0| 0| 0
L=6| 0| 0| 9| 0| 1| 0| 73| 0| 1| 0
L=7| 0| 5| 3| 3| 0| 0| 3| 16| 0| 0
L=8| 0| 4| 7| 0| 8| 1| 1| 0| 14| 0
L=9| 0| 2| 0| 0| 0| 0| 1| 0| 0| 10
-- scores
|count|precision|recall|f-score
L=0| 129| 0.8696|0.9302| 0.8989
L=1| 134| 0.7395|0.6567| 0.6957
L=2| 57| 0.5375|0.7544| 0.6277
L=3| 39| 0.7500|0.4615| 0.5714
L=4| 76| 0.6750|0.7105| 0.6923
L=5| 35| 0.6923|0.5143| 0.5902
L=6| 84| 0.6759|0.8690| 0.7604
L=7| 30| 0.7273|0.5333| 0.6154
L=8| 35| 0.5833|0.4000| 0.4746
L=9| 13| 0.9091|0.7692| 0.8333
accuracy = 0.7184
f-score(micro) = 0.7184
f-score(macro) = 0.6760
estimated LB = 0.7079
-- number of predictions
class-0: 76 (actual=72)
class-1: 82 (actual=83)
class-2: 37 (actual=37)
class-3: 22 (actual=27)
class-4: 43 (actual=45)
class-5: 27 (actual=25)
class-6: 60 (actual=46)
class-7: 14 (actual=19)
class-8: 22 (actual=23)
class-9: 14 (actual=20)