msdd’s diary

deep leraning勉強中。アニメ画像とか生成したい。

PyTorchでの学習の進み具合をプログレスバーで表示してみる

はじめに

前回、tqdmを使って、プログレスバーを表示してみた。

msdd.hatenablog.com

今回は、実際にpytorchを使って、ニューラルネットワークを学習する時の学習の進行度を プログレスバーで表示してみる。

学習時にバーを表示させる

pytorchを使って、ニューラルネットワークを学習させる時に、 プログレスバーを表示させて、学習状況を見やすくしてみる。

バージョン

import tqdm
import torch
import torchvision

print(tqdm.__version__)
print(torch.__version__)
print(torchvision.__version__)

出力

4.43.0
1.4.0
0.5.0

tqdmのバージョンを4.38.0?では、 学習の途中で無限ループのような感じになった。 なので、tqdmのバージョンを上げて、4.43.0で実行すると、 きちんと動くようになった。

import文とパラメータ

import文

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms

from tqdm import tqdm

パラメータの変数を宣言する。gpuありで学習した。 クラス数は、今回用いたデータセットCIFAR10の10クラス。epoch数は、 学習状況を少しみたいだけなので、少なくした。 batch sizeとlrは適当にした。

device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
batch_size=128
num_classes=10
num_epochs=20
lr=0.1

データセットの用意

データセットの用意をする。 今回は、画像識別のデータセットCIFAR10を用いた。 データセットを読み込み時に変更するtransformは、画像をtensor形式にするToTensor()のみにした。 CIFAR10()でデータをダウンロードし、使えるようにする。 その後、データをまとめて処理できるようにbatch_size分を読み出すdataloaderを作り、データの準備は終わり。

transform={
    "train":transforms.Compose([
        transforms.ToTensor(),
    ]),
    "test":transforms.Compose([
        transforms.ToTensor(),
    ])
}

dataset={
    "train":torchvision.datasets.CIFAR10(root="CIFAR10",train=True,transform=transform["train"],download=True),
    "test":torchvision.datasets.CIFAR10(root="CIFAR10",train=False,transform=transform["test"],download=True)
}

dataloader={
    "train":torch.utils.data.DataLoader(dataset["train"],batch_size=batch_size,shuffle=True,num_workers=2),
    "test":torch.utils.data.DataLoader(dataset["test"],batch_size=batch_size,shuffle=False,num_workers=2)
}

モデル、optimizer、ロス関数を用意

ネットワークはresnet18を使用した。最後の出力の数を、データセットのクラス数の10クラスへと 変えて、前に宣言したdeviceへ送る。optimizerはadamでロス関数はcross entropy lossを使用した。

model=torchvision.models.resnet18(pretrained=False)
model.fc=nn.Linear(model.fc.in_features,num_classes)
model=model.to(device)

optimizer=optim.Adam(model.parameters(),lr=lr)
criterion=nn.CrossEntropyLoss()

学習でプログレスバー表示

学習部分のコード。 1epochごとに、学習とテストを行う。 データローダーでバッチごとに処理をしていくたびにプログレスバーを更新していくように している。

tqdm()でバーを表示できるようにする。今回は手動でバーを更新する方法を使った。 引数のtotalに繰り返しの合計数などを入れ、繰り返し時にupdate(n)でバーをnだけ進めて行く。 今回はdataloaderのサイズ、バッチごとに処理する回数を入れた。 引数のunitは入れた文字で処理速度の単位を変えることができる。今回はバッチごとの処理をしているので、batchを入れた。\ set_description(f"Epoch[{epoch}/{num_epochs}]({phase})")で、 バーの前側の部分に現在のepoch数と最終のepoch数、 trainかtestかを表示するようにしている。文字列の前にfを付けることで、{}で囲んだ中に変数を入れることで、 変数の値を表示できて便利である。\ set_postfix({"loss":running_loss.item(),"accuracy":accuracy })でバッチごとに学習またはテストした途中の段階でのlossと精度accuracyを更新している。\ 最後に、バッチ1つ分の処理が終了したので、update(1)でバーを1進め、totalになるまで繰り返す。

def train(model,dataloader,otpimizer,criterion,num_epochs,device):

    for epoch in range(1,num_epochs+1):

        for phase in ["train","test"]:

            if phase=="train":
                model.train()
            elif phase=="test":
                model.eval()

            with torch.set_grad_enabled(phase=="train"):
                loss_sum=0
                corrects=0
                total=0

                with tqdm(total=len(dataloader[phase]),unit="batch") as pbar:
                    pbar.set_description(f"Epoch[{epoch}/{num_epochs}]({phase})")
                    for imgs,labels in dataloader[phase]:   
                        imgs,labels=imgs.to(device),labels.to(device)
                        output=model(imgs)
                        loss=criterion(output,labels)

                        if phase=="train":
                            optimizer.zero_grad()
                            loss.backward()
                            optimizer.step()

                        predicted=torch.argmax(output,dim=1) ## dimあってる?
                        corrects+=(predicted==labels).sum()
                        total+=imgs.size(0)
                            
                        #loss関数で通してでてきたlossはCrossEntropyLossのreduction="mean"なので平均
                        #batch sizeをかけることで、batch全体での合計を今までのloss_sumに足し合わせる
                        loss_sum+=loss*imgs.size(0) 

                        accuracy=corrects.item()/total
                        running_loss=loss_sum/total
                        pbar.set_postfix({"loss":running_loss.item(),"accuracy":accuracy })
                        pbar.update(1)

学習を行うと、バーが表示され、lossとaccuracyが更新されているのがわかる。 進むにつれてlossが下がっていっており、 学習が進んでいるとわかる。予測時間も表示されるので、どれくらいで終わるかも予測しやすい。

train(model,dataloader,optimizer,criterion,num_epochs,device)

f:id:msdd:20200326180450g:plain

まとめ

実際の学習コードでプログレスバーを表示して、学習の進み具合を見れるようにしてみた。 実際にlossが下がっているかを確認でき学習の状況を把握しやすくなる。

コード置き場

training_tqdm_pytorch.ipynb

ニューラルネットワーク学習時に進み具合を表示する簡単な方法

はじめに

ニューラルネットワークの学習で、進行度合を文字ではなく、 プログレスバーのような目で見える形にするとわかりやすい。 tqdmというライブラリを使い、学習経過をプログレスバーで簡単に表示できるので試してみる。

tqdmとは

簡単に、コマンドライン上にプログレスバーを表示でき、 経過時間と予測時間も表示してくれる便利なpythonライブラリ。

f:id:msdd:20200324215316g:plain

インストール方法

pip install tqdm

プログレスバーの表示

使用ライブラリとバージョン

プログレスバー表示

  • tqdm : 4.38.0

試してみる

ニューラルネットワークの学習の時のことを考えて、試してみる。

datasetで1epoch分、つまりdatasetの数(下の例ではデータ6個分を学習するのを1epoch)だけ学習するとする。 下のコードのようにdatasetがあり、データを読み込み、 time.sleep()部分が学習部分とする。 このままだと、何も表示されず学習の結果がわかりにくい。 そこで、プログレスバーを表示して、学習状況を見やすくしてみる。

import time

dataset=["a","b","c","d","e","f"]

for data in dataset:

    time.sleep(0.5)

出力




上のコードに少し足すだけで、プログレスバーを表示できる。for文でのデータセットの部分をtqdmでおおう。 tqdmの引数は、iterable(反復可能)なもので、 range()などを引数として入れられる。 これだけで、簡単にバーを表示できる。

表示は、進行度の%、処理した数と処理すべき合計数、経過時間、残り時間の予測、処理スピードなどの情報が表示される。

import time
from tqdm import tqdm

dataset=["a","b","c","d","e","f"]

for data in tqdm(dataset):

    time.sleep(0.5)

出力

f:id:msdd:20200324215316g:plain

手動でプログレスバーを操作する方法もある。 tqdmの引数のtotalに合計値を入れ、tqdmのインスタンスpbarとする。 手動でupdate(n)を使い、nだけ増やしていく。

dataset=["a","b","c","d","e","f"]

with tqdm(total=len(dataset)) as pbar:
    for data in dataset:
        time.sleep(0.5)

        pbar.update(1)

出力

f:id:msdd:20200324215403g:plain

学習の時、現在の精度やlossなどの値も確認したいので、表示してみる。 情報を表示するには、set_postfix(dict)を用いることで表示できる。 引数には、キーが表示したい名前、辞書の値が表示したい値の辞書をいれる。

dataset=["a","b","c","d","e","f"]

accuracy=0
with tqdm(total=len(dataset)) as pbar:
    for data in dataset:
        time.sleep(0.5)
        accuracy=accuracy+10
        pbar.set_postfix({"accuracy":accuracy})
        pbar.update(1)

出力

f:id:msdd:20200324215424g:plain

また、学習には1epoch分だけでなく、何epochも行う。 これをバーで表示すると、何個もバーが連なり、 現在が何epoch目かがわからないようになる。

dataset=["a","b","c","d","e","f"]

num_epochs=10
for epoch in range(1,num_epochs+1):
    #1epoch分の学習
    with tqdm(total=len(dataset)) as pbar:
        for data in dataset:
            time.sleep(0.5)

            pbar.update(1)

出力

f:id:msdd:20200324215443g:plain

そこで、前側にepoch数を書いておくことで、現在の進行度がわかるようになる。 バーの前側に文字を表示するのは、set_description()

dataset=["a","b","c","d","e","f"]

num_epochs=10
for epoch in range(1,num_epochs+1):
    #1epoch分の学習
    with tqdm(total=len(dataset)) as pbar:
        for data in dataset:
            time.sleep(0.5)

            pbar.set_description(f"Epoch[{epoch}/{num_epochs}]")
            pbar.update(1)

出力

f:id:msdd:20200324215520g:plain

まとめ

学習の過程を見てわかりやすいように、プログレスバーで表示してみた。 データセットなどのiterableな部分をtqdmで包むだけで、 簡単に表示でき使いやすい。 ニューラルネットワークの学習にも使っていきたい。

Google Colab Proを使いたかった(使えなかった)

はじめに

Google ColabでGPU付でニューラルネットワークの学習を行っていたら、 約6時間ぐらいの所でプログラムが止まってしまった。 その後、再実行使用とすると下のようなGPUバックエンドに接続できませんとの表示が出た。

f:id:msdd:20200323164811p:plain

学習には時間がかかるので、6時間で止まってしまうとつらい。もう少し長く使えないかと探していると Google Colab Proがあることを知った。Google Colabは無料で使えるが、Pro版は、1カ月9.99ドル払うことで、より速いGPUに優先的にアクセス、より長時間、より多くのメモリを使用可能となるとある。

Google Colab Pro使いたかった

Google Colab Proの特徴として下の3つがある。

  • GPU
    • Colab ProはT4やP100など高性能なGPUを利用しやすい(使用上限は有り)。使用量の上限もProの方が多い。
  • 接続時間最大
    • 無料版: 12時間
    • Pro : 24時間
  • メモリ
    • Pro: ハイメモリVMを使用可能(無料版の2倍のメモリとCPU)

月9.99ドルの低価格なのでリソース保障はなし。

これはすごく便利そうと思い、早速申し込もうとするが、 現在はColab Proは米国のみで利用可能であり、日本では使えないことがわかる。 書いてあるだけで、実際は使えるのではと思い、 試しに購入できないかと購入画面へと行くと、 どうやら米国の住所がいるようである。

f:id:msdd:20200323164824p:plain

残念。。。 米国の住所なんてないのであきらめた。 よさそうなプランなので使いたかった。

おわりに

stack overflow での回答で、他のGoogleのサービス同様に、最初に米国でローンチした後、他国への展開するかもしれないとある。 それを期待して、日本でもサービス開始するのを待ちたい。

PyTorchでOne Hotに変換する

PyTorchでOne Hotに変換する方法を試してみる。

使う関数

torch.nn.functional.one_hot(tensor, num_classes=-1)

ドキュメント

https://pytorch.org/docs/stable/nn.functional.html#torch.nn.functional.one_hot

Pytorchのバージョンは1.4.0を使用して、試してみた。

import torch
import torch.nn.functional as F

print(torch.__version__)
1.4.0

もとの入力を用意し、one_hot関数に通すことでone hotに変換したものを得られる。

label=torch.tensor(3)
print("label:",label)

one_hot=F.one_hot(label)
print("one hot:",one_hot)

出力は下のようになった。ラベルの3からone hotへと変換できている。 注意すべき所で、one_hot関数の引数のnum_classesに何も指定しないと入力の中で一番大きい値からクラス数が決定される。今回は、最大が3なのでクラス数は4となり、one hotでは4つの値となっている。

label: tensor(3)
one hot: tensor([0, 0, 0, 1])

num_classesにクラス数を指定すれば、きちんとクラス数分のものが得られる。

label=torch.tensor(3)
print("label:",label)

one_hot=F.one_hot(label,num_classes=10)
print("one hot:",one_hot)
label: tensor(3)
one hot: tensor([0, 0, 0, 1, 0, 0, 0, 0, 0, 0])

入力のラベルが複数あるような場合でも同じく使える。それぞれの値に対してone hotを得られる。

label=torch.tensor([0,1,2,0,4])
print("label:",label)

one_hot=F.one_hot(label,num_classes=5)
print("one hot:",one_hot)
label: tensor([0, 1, 2, 0, 4])
one hot: tensor([[1, 0, 0, 0, 0],
        [0, 1, 0, 0, 0],
        [0, 0, 1, 0, 0],
        [1, 0, 0, 0, 0],
        [0, 0, 0, 0, 1]])

imgaugでbounding boxを表示

はじめに

物体検出では、物体の位置を表示する方法として、検出した物体を矩形(bounding box、バウンディングボックス)で囲む方法がある。 そのbounding boxを表示する方法として、imgaugというライブラリを使って表示することができるので試してみた。

imgaugとは

画像拡張用のpythonライブラリ

画像にノイズを加えたり、クロップしたりなどできる。 その中にbounding box表示用の関数もある。

リンク

bounding boxの表示

使用ライブラリのバージョン

  • torchvision: 0.5.0
  • imgaug: 0.4.0

データの用意

画像とbounding boxの位置の情報を用意する。今回は、torchvisionのクラスを使い、Pascal VOCのデータセットを使用した。 Pascal VOCでは、bounding boxは左上の頂点と右下の頂点の座標で表されている。

画像(image)とアノテーション情報(annotation)を取得した。 アノテーション情報の中に、複数の物体の情報がはいっており、物体の名前とbounding boxの位置が含まれている。

import torchvision

voc_dataset=torchvision.datasets.VOCDetection(root="VOCDetection/2012",year="2012",image_set="train",download=True)

image,target=voc_dataset[0]
annotation=target["annotation"]

画像を表示してみる。

from IPython.display import display

display(image)

f:id:msdd:20200317085206p:plain

物体の情報を表示してみる。

print(annotation["object"])
[{'name': 'horse', 'pose': 'Left', 'truncated': '0', 'occluded': '1', 'bndbox': {'xmin': '53', 'ymin': '87', 'xmax': '471', 'ymax': '420'}, 'difficult': '0'}, {'name': 'person', 'pose': 'Unspecified', 'truncated': '1', 'occluded': '0', 'bndbox': {'xmin': '158', 'ymin': '44', 'xmax': '289', 'ymax': '167'}, 'difficult': '0'}]

2つの物体(horseとperson)があり、それぞれbounding boxの情報(bndbox)をもっている。bounding boxの情報は左上の頂点の座標のxminymin、右下の頂点の座標のxmaxymaxで表されている。

このbounding boxを画像上に表示してみる。

bounding boxの描画

アノテーション情報から物体の名前(obj["name"])、bounding boxの位置情報(xminyminxmaxymax)を取り出す。 bb=BoundingBox(x1=xmin,y1=ymin,x2=xmax,y2=ymax,label=obj["name"])で描画用のbounding boxを作成する。 引数はx1,y1,x2,y2で左上の座標(x1,y1)と右下の座標(x2,y2)を指定する。 引数のlabelに表示したい名前を入れることもできる。 それをlist形式で、bb_listに保存する。 BoundingBoxesOnImage()で複数のbounding boxをまとめたものを作り、draw_on_image()で画像に対してbounding boxを付け加えた画像を生成する。 BoundingBoxesOnImage()の引数のshapeでは、(height,width,channel)の順の画像のshapeを入力する。 draw_on_image()での返り値は、ndarrayの画像なので表示するためにPILの画像に変換して表示した。

from imgaug.augmentables.bbs import BoundingBox,BoundingBoxesOnImage
from PIL import Image

bb_list=[]
for obj in annotation["object"]:
    box=obj["bndbox"]
    xmin,ymin=float(box["xmin"]),float(box["ymin"])
    xmax,ymax=float(box["xmax"]),float(box["ymax"])

    #bounding boxを作成
    bb=BoundingBox(x1=xmin,y1=ymin,x2=xmax,y2=ymax,label=obj["name"])
    bb_list.append(bb)

image_shape=(image.height,image.width,3)

#imageにbounding boxを描画
bbs=BoundingBoxesOnImage(bb_list,shape=image_shape)
bbs_image=bbs.draw_on_image(image,color=(255,255,255))

# 表示するためにpil形式に変換して、表示する
pil_bbs_image=Image.fromarray(bbs_image)
display(pil_bbs_image)

得られた画像はきちんと、物体の周囲にbounding boxが表示され、その上側に設定した名前が表示されている。

f:id:msdd:20200317085105p:plain

おわりに

imgaugを使ってbounding boxを表示してみた。 ラベル付きでbounding boxを表示できる関数が手軽に使えて便利。物体ごとに色を変えたいので、 できる方法がないか調べていきたい。

参考

TorchvisionでIoUの計算

はじめに

物体検出の評価指標で使われたりしているIoUを求めてみた。

IoUとは

Intersection over Unionの略である。Jaccard indexとも言われている。

物体検出の評価指標などで使われている。 物体検出では、物体を矩形で囲むことで、物体の位置を予測する。 正解もまた、矩形で囲まれたものが与えられる。この予測した矩形と正解の矩形を 評価する手法の1つとして、IoUが使われる。

2つの矩形があり、重なった部分の面積を面積の和で割ったものがIoUである。

f:id:msdd:20200310171456p:plain

2つの矩形が、まったく重ならなかったらIoU=0となり、完全に重なるとIoU=1となる。

f:id:msdd:20200310151355p:plainf:id:msdd:20200310151319p:plain

IoUを求めてみる

IoUを求めてみる。

バージョン

  • PyTorch : 1.4.0
  • Torchvision : 0.5.0

使った関数

torchvision.ops.boxes.box_iou(boxes1,boxes2)

torchvisionにはtorchvision.ops.boxesbox_iou(boxes1, boxes2)という関数がある。ドキュメントには載っていないが使えるので使う。

引数が矩形のboxes1とboxes2でそれぞれサイズがTensor[N, 4]、Tensor[M, 4]である。 この4の所は矩形の座標で(x1,y1,x2,y2)である。 返り値はiouでサイズがTensor[N, M]のテンソルである。

Google Colabで試した

矩形を2つ用意する。 最も左上の座標を(0,0)として右方向、下方向が+となる方向。 それぞれ左上の座標と左下の座標で矩形を表している。

一方の矩形にもう一方の矩形が含まれている例

box1は(0,0),(3,3)の矩形(黄色)、box2は(0,0),(5,5)の矩形(青)。

f:id:msdd:20200310163853p:plain:w400

import torchvision.ops.boxes as bops

box1=torch.tensor([[0,0,3,3]],dtype=torch.float)
box2=torch.tensor([[0,0,5,5]],dtype=torch.float)
print("box1:",box1)
print("box2:",box2)

area1,area2=bops.box_area(box1),bops.box_area(box2)
print("area1:",area1)
print("area2:",area2)

iou=bops.box_iou(box1,box2)
print(iou)

実行してみる。

box1: tensor([[0., 0., 3., 3.]])
box2: tensor([[0., 0., 5., 5.]])
area1: tensor([9.])
area2: tensor([25.])
tensor([[0.3600]])

重なり部分の面積=(3-0)(3-0)=9、和の部分の面積=(5-0)(5-0)=25となる。

f:id:msdd:20200310164145p:plain:w250
重なり部分
f:id:msdd:20200310164140p:plain:w250
和の部分

その結果、  IoU=\frac{9}{25}=0.36 となる

2つの矩形が重ならない例

box1:(5,0),(8,3)で、box:(0,0),(5,5)の場合

f:id:msdd:20200310165051p:plain:w400

box1=torch.tensor([[5,0,8,3]],dtype=torch.float)
box2=torch.tensor([[0,0,5,5]],dtype=torch.float)
print("box1:",box1)
print("box2:",box2)

iou=bops.box_iou(box1,box2)
print(iou)

実行してみる。

box1: tensor([[5., 0., 8., 3.]])
box2: tensor([[0., 0., 5., 5.]])
tensor([[0.]])

重なっている部分がないので、IoU=0となる。

一部が重なる例

box1:(3,1),(6,4)で、box:(0,0),(5,5)の場合

f:id:msdd:20200310165157p:plain:w400

box1=torch.tensor([[3,1,6,4]],dtype=torch.float)
box2=torch.tensor([[0,0,5,5]],dtype=torch.float)
print("box1:",box1)
print("box2:",box2)

iou=bops.box_iou(box1,box2)
print(iou[0][0].item())
print(iou)

実行してみる。

box1: tensor([[3., 1., 6., 4.]])
box2: tensor([[0., 0., 5., 5.]])
0.2142857164144516
tensor([[0.2143]])

重なり部分の面積は6、和の部分の面積は28なので、 IoU=\frac{6}{28}=0.214285...=0.2143 となる。

f:id:msdd:20200310165216p:plain:w250
重なり部分
f:id:msdd:20200310165213p:plain:w250
和の部分

入力のboxesが複数ある例

boxes1:(3,1),(6,4)の黄色の矩形と(1,3),(4,5)の紫の矩形 で、boxes2:(0,3),(2,6)の赤の矩形と(0,0),(5,5)の青の矩形の場合

f:id:msdd:20200310165540p:plain:w400

boxes1=torch.tensor([[3,1,6,4],[1,3,4,5]],dtype=torch.float) #黄, 紫
boxes2=torch.tensor([[0,3,2,6],[0,0,5,5]],dtype=torch.float) #赤, 青

print("boxes1:",boxes1)
print("boxes2:",boxes2)

iou=bops.box_iou(boxes1,boxes2)
print(iou)

出力は、

boxes1: tensor([[3., 1., 6., 4.], [1., 3., 4., 5.]])
boxes2: tensor([[0., 3., 2., 6.], [0., 0., 5., 5.]])
tensor([[0.0000, 0.2143],[0.2000, 0.2400]])

返り値のIoUは入力サイズのboxes1が2、boxes2が2より、2x2のtensorが返ってくる。

得られたIoUのtensorは下の表のようになる。縦がboxes1で横がboxes2の並びとなる。

(0,3),(2,6) (0,0),(5,5)
(3,1),(6,4) 黄色 0.0 0.2143
(1,3),(4,5)  0.2000 0.2400

f:id:msdd:20200310165557p:plain

つまり、iou[index1][index2]でboxes1[index1]とboxes2[index2]の2つの矩形のIoUを得られる。

つまった点

box_iouの入力のbox1,box2をdtype=torch.intのtensorで計算すると、結果が0になってしまう。 そのため、dtypeはfloatにする必要がある。

まとめ

IoUについて調べ、torchvisionのbox_iou関数を使ってIoUを求めてみた。 複数の矩形のIoUを一括で求められて便利。 今後は、物体検出の評価指標は他にも色々あるので調べてみたい。

参考サイト

PyTorchでPascal VOCデータセット を使ってみた

はじめに

Object DetectionのデータセットPascal VOCデータセットをPyTorchで使ってみた。

Pascal VOCとは

Pattern Analysis, Statistical Modelling and Computational Learning(PASCAL) Visual Object Classes Challenge(VOC)。 2005年~2012年に開催されていた物体のクラス認識などのコンペティション。 物体検出やセグメンテーションなどのタスクがある。

f:id:msdd:20200307184142j:plain
物体検出サンプル(http://host.robots.ox.ac.uk/pascal/VOC/voc2012/index.html より)

f:id:msdd:20200307184315j:plainf:id:msdd:20200307184358p:plain
セグメンテーションのサンプル(http://host.robots.ox.ac.uk/pascal/VOC/voc2012/index.html より)

クラス数は2007年からは20クラス。

  • Person: person
  • Animal: bird, cat, cow, dog, horse, sheep
  • Vehicle: aeroplane, bicycle, boat, bus, car, motorbike, train
  • Indoor: bottle, chair, dining table, potted plant, sofa, tv/monitor

その中で今回は、物体検出のデータセットを使ってみた。

VOCDetectionクラス

PyTorchでPascal VOCのデータセットを使うのに、 Torchvisionの VOCDetection というクラスがある。 Pascal VOCのデータを簡単に読み込めるようなクラスが用意されている。 これを使ってみた。

クラスは下のような引数をとる。

torchvision.datasets.VOCDetection(root, year='2012', image_set='train', download=False, transform=None, target_transform=None, transforms=None)

torch.utils.data.Datasetのサブクラスで、 データを得る__getitem__と、 データセットの大きさを得る __len__を持っている。

引数は下のようなもの。

引数名 説明
root データセットのルートフォルダー (e.g.) ./VOCDetection
year どの年のデータセット 2007, 2008, 2009, 2010, 2011, 2012
image_set データセットの区分 train, trainval, val
download データセットをダウンロードするかどうか True, False
transform 画像に対して変形を行う際のtransform関数 None(何もしない), torchvision.transforms.ToTensor()など
target_transform targetに対して変形を行う際のtransform関数 None(何もしない) など
transforms transformとtarget_transformを合わせたようなやつ。?なんで必要なのこれ? None(何もしない)など

year=2007の時のみ、image_set="test"も使える。

indexでアクセスできる。 返り値は、(image,target)で、imageはPILの画像、targetはxml treeの辞書となっている。 targetには画像サイズやbounding boxの位置などの情報が入っている。

試してみた

バージョン

PyTorch: 1.4.0
Torchvision : 0.5.0

Google Colabで実行

まずは、データセットクラスを宣言。2012年のデータセットのtrainを使う。 download=Trueなので、実行するとデータがrootで指定した./VOCDetection/2012にダウンロードされる。

import torchvision

voc_dataset=torchvision.datasets.VOCDetection(root="./VOCDetection/2012",year="2012",image_set="train",download=True)

データセットから一番最初のデータを取得する。 返り値は、画像とアノテーション情報である。

image,target=voc_dataset[0]

データセットの画像を表示してみる。

from IPython.display import display

display(image)

f:id:msdd:20200307184649p:plain

次に、アノテーション情報targetについて見てみる。 targetを表示してみると、下のような辞書の形でアノテーション情報が入っている。

{'annotation': {'folder': 'VOC2012', 'filename': '2008_000008.jpg', 'source': {'database': 'The VOC2008 Database', 'annotation': 'PASCAL VOC2008', 'image': 'flickr'}, 'size': {'width': '500', 'height': '442', 'depth': '3'}, 'segmented': '0', 'object': [{'name': 'horse', 'pose': 'Left', 'truncated': '0', 'occluded': '1', 'bndbox': {'xmin': '53', 'ymin': '87', 'xmax': '471', 'ymax': '420'}, 'difficult': '0'}, {'name': 'person', 'pose': 'Unspecified', 'truncated': '1', 'occluded': '0', 'bndbox': {'xmin': '158', 'ymin': '44', 'xmax': '289', 'ymax': '167'}, 'difficult': '0'}]}}

画像のサイズ、物体の名前と位置などを使うので取り出してみる。 bounding boxの情報は、画像の一番左上のピクセルを(1,1)とした座標であり、左上の頂点と右下の頂点の情報で保持されている。

annotation=target["annotation"]

#画像サイズ
width, height, depth = annotation["size"].values()
#物体の情報
objects=annotation["object"]
for obj in objects:
    name=obj["name"]
    bndbox=obj["bndbox"]
    #bounding boxの左上と右下の座標
    x_min,y_min,x_max,y_max=[int(val) for val in bndbox.values()]

画像とbounding boxが得られたので、画像上にbounding boxを表示してみる。 bounding boxの適用した画像を作る関数をつくる。もしかしたら、すでに便利な関数があるかもしれないが、 少し調べた範囲ではわからなかった。

from PIL import Image,ImageDraw

def make_bbox_img(pil_image,x0y0,x1y1,text,width=1,color="red"):
    bbox_img=pil_image.copy()
    d=ImageDraw.Draw(bbox_img)
    d.rectangle((x0y0,x1y1),outline=color,width=width)
    #テキストのライン
    d.text((x0y0[0]+1,x0y0[1]+1),text,fill="white")
    d.text((x0y0[0]-1,x0y0[1]-1),text,fill="white")
    #テキスト
    d.text(x0y0,text,fill="black")

    return bbox_img


name_to_color={
    "person":"orange",
    "bird":"darkcyan",
    "cat":"lightyellow",
    "cow":"coral",
    "dog":"dimgray",
    "horse":"darkgreen",
    "sheep":"wheat",
    "aeroplane":"red",
    "bicycle":"royalblue",
    "boat":"blue",
    "bus":"yellow",
    "car":"hotpink",
    "motorbike":"lawngreen",
    "train":"cyan",
    "bottle":"olive",
    "chair":"indigo",
    "diningtable":"maroon",
    "pottedplant":"mediumslateblue",
    "sofa":"purple",
    "tvmonitor":"lime",
}


def make_voc_bbox_img(pil_image,objects,width=1):
    bbox_img=pil_image.copy()
    d=ImageDraw.Draw(bbox_img)

    #list形式でないものがあるのでlist形式へ直す
    if not isinstance(objects,list):
        objects=[objects]

    for obj in objects:
        name=obj["name"]
        bndbox=obj["bndbox"]
        x_min,y_min,x_max,y_max=[int(val) for val in bndbox.values()]

        color=name_to_color[name]
        #x:[1,image_width],y:[1,image_height]
        bbox_img=make_bbox_img(bbox_img,(x_min-1,y_min-1),(x_max-1,y_max-1),text=name,color=color,width=width)

    return bbox_img

先ほどの画像に適用してみる。

bbox_img=make_voc_bbox_img(image,objects)
display(bbox_img)

f:id:msdd:20200307184744p:plain

うまくbounding boxを表示出来ている。 色が見にくいなどはあるが、表示できるようになったので、まあよしとする。 bounding box表示方法は、簡単な方法がないか探していきたい。

数枚の画像で試してみた。 f:id:msdd:20200307184810p:plain

f:id:msdd:20200307184827p:plain

f:id:msdd:20200307184833p:plain

つまづいた点

つまづいた点としては下のものがある。
- annotationのbounding boxの座標の値がstr型 - annotationのobjectの値が、全てlist形式と思っていたが、 objectが複数あるものはlist形式で、単一のものはlist形式でない点。

まとめ

PyTorchで物体検出を試してみたくて、Pascal VOCデータセットについて調べて動かしてみた。 データ形式などがわかった。 すでにクラスが用意されているので、自分でデータセットの読み込みなどを書く必要がなくて便利。 bounding box表示の関数がないか今後調べていきたい。


つくりながら学ぶ! PyTorchによる発展ディープラーニング

つくりながら学ぶ! PyTorchによる発展ディープラーニング

  • 作者:小川雄太郎
  • 発売日: 2019/07/29
  • メディア: 単行本(ソフトカバー)