MENU

PyTorchに入門してみる part 5(最終回) MNISTを単純なLSTMで解く

はじめに

PyTorch入門シリーズ5回目になります。これで最終回にしようと思います。

今まで4回は公開されているモデルを使用させて頂きました。

今回は自分でスクリプトを書きます。

題材は入門にうってつけの「MNIST」です。

テーマは『できるだけ簡潔に』としました。


LSTMを使ってMNISTにチャレンジします。画像分類にLSTM?と疑問に思う方もいるかもしれません。
詳細はこちらを参照して下さい。
MNISTを単純なLSTMで解く(MXNet) - パソコン関連もろもろ

Pythonスクリプト

できる限り簡潔に書いたつもりです。ただPyTorchでこのようなことをするのは初めてなので改善点があるかもしれません。

import torch
import torch.nn as nn
from torchvision import datasets, transforms
from torch.utils.data import DataLoader

device = 'cuda' if torch.cuda.is_available() else 'cpu' 

#データの取得
transform = transforms.Compose([transforms.ToTensor()])
trainset = datasets.MNIST(root='.', download = True, transform = transform, train = True)
testset = datasets.MNIST(root='.', download = True, transform = transform, train = False)

trainloader = DataLoader(trainset, batch_size = 100, shuffle = True)
testloader = DataLoader(testset, batch_size = 100, shuffle = False)

#モデルの定義
class MyModel(nn.Module):
    def __init__(self, input_size = 28, hidden_layer = 128):
        super().__init__()
        self.lstm = nn.LSTM(input_size, hidden_layer, batch_first = True)
        self.flat = nn.Flatten()
        self.linear = nn.Linear(input_size * hidden_layer, 10)
        torch.nn.init.xavier_uniform_(self.linear.weight)
        self.linear.bias.data.fill_(0.01)
        self.softmax = nn.Softmax(dim =1)
        
    def forward(self, x):
        x = self.lstm(x)
        x = self.flat(x[0])
        x = self.linear(x)
        x = self.softmax(x)
        return x

model = MyModel().to(device)

loss_func = nn.CrossEntropyLoss()
opt = torch.optim.Adam(model.parameters())

#評価のための関数定義
def evaluate_accuracy(data, net):
    total_count = 0
    correct_count = 0
    net.eval()
    for batch_data in data:
        x = torch.squeeze(batch_data[0], dim = 1).to(device)
        y = batch_data[1]
        out = net(x)
        out = out.argmax(1).to('cpu')
        total_count += y.shape[0]
        correct_count += (out == y).sum().item()
    acc = correct_count / total_count
    return acc

#学習ループ
for epoch in range(10):
    model.train()
    for batch_data in trainloader:
        opt.zero_grad()
        x = torch.squeeze(batch_data[0], dim = 1).to(device)
        y = batch_data[1].to(device)

        output = model(x)

        loss = loss_func(output, y)
        loss.backward()
        opt.step()
    
    test_acc = evaluate_accuracy(testloader, model)
    print('%d epoch test_acc = %f' %(epoch + 1, test_acc))

設定したハイパーパラメーターは以下の4つだけです。

LSTMのlayer数:128
全結合層のバイアス初期化:0.01
batchサイズ:100
epoch数:10

結果

1 epoch test_acc = 0.940400
2 epoch test_acc = 0.970900
3 epoch test_acc = 0.975200
4 epoch test_acc = 0.979000
5 epoch test_acc = 0.984600
6 epoch test_acc = 0.986100
7 epoch test_acc = 0.986200
8 epoch test_acc = 0.983900
9 epoch test_acc = 0.985100
10 epoch test_acc = 0.988800

それなりの結果が得られたので書いたスクリプトに大きな間違いはなさそうです。

参考にさせて頂いたサイト

tzmi.hatenablog.com

さいごに

間違いや改善点があればコメント頂けましたら幸いです。

PyTorch入門シリーズ(前回までの記事)

touch-sp.hatenablog.com
touch-sp.hatenablog.com
touch-sp.hatenablog.com
touch-sp.hatenablog.com

PyTorchに入門してみる part 4 Object Trackingの結果を使ってYOLOv5の転移学習を行う

はじめに

前回Object Trackingの結果をVOCフォーマットで出力しました。

しかしPyTorchでの転移学習を調べているとYOLOv5が簡単そうでした。


そのためObject Trackingの出力がYOLOv5用になるようにスクリプトを書き換えました。


その後YOLO5の転移学習を行いました。

結果

f:id:touch-sp:20211019195544p:plain:w400
以前MXNetでやったことをPyTorchに置き換えただけです。

PC環境

Windows 11 
Core i7-7700K + GTX 1080

Python 3.8.10

Python環境構築

新しいPython仮想環境を作り以下をインストールしました。

pip install torch==1.9.1+cu102 torchvision==0.10.1+cu102 torchaudio===0.9.1 -f https://download.pytorch.org/whl/torch_stable.html
pip install -r https://raw.githubusercontent.com/ultralytics/yolov5/master/requirements.txt
pip install yacs

Object Tracking(学習データの作成)

pysotのGitHubをcloneして以下のPythonスクリプトを実行します。
この部分の詳細は前回の記事を参照して下さい。

import os

import cv2
import torch

from pysot.core.config import cfg
from pysot.models.model_builder import ModelBuilder
from pysot.tracker.tracker_builder import build_tracker

from torchvision.datasets.utils import download_url

#=========================================================
video_list = ['target.mp4', 'green.mp4']

url_1 = 'https://github.com/dai-ichiro/robo-one/raw/main/video_1.mp4'
url_2 = 'https://github.com/dai-ichiro/robo-one/raw/main/video_2.mp4'

download_url(url_1, root = '.', filename = video_list[0])
download_url(url_2, root = '.', filename = video_list[1])

target_name = [x.split('.')[0] for x in video_list]

out_path = 'train_data'

config = 'experiments/siamrpn_r50_l234_dwxcorr/config.yaml'
snapshot = 'experiments/siamrpn_r50_l234_dwxcorr/model.pth'
#=========================================================

train_images_dir = os.path.join(out_path, 'images', 'train')
train_labels_dir = os.path.join(out_path, 'labels', 'train')

os.makedirs(train_images_dir)
os.makedirs(train_labels_dir)

init_rect_list = []

for video in video_list:
    cap = cv2.VideoCapture(video)
    ret, img = cap.read()
    cap.release()

    source_window = "draw_rectangle"
    cv2.namedWindow(source_window)
    rect = cv2.selectROI(source_window, img, False, False)

    init_rect_list.append(rect)
    cv2.destroyAllWindows()

# モデルを取得する
cfg.merge_from_file(config)
cfg.CUDA = torch.cuda.is_available() and cfg.CUDA
device = torch.device('cuda' if cfg.CUDA else 'cpu')
model = ModelBuilder()
model.load_state_dict(torch.load(snapshot,
    map_location=lambda storage, loc: storage.cpu()))
model.eval().to(device)
tracker = build_tracker(model)

for i, video in enumerate(video_list):
    # 映像ファイルを読み込む
    video_frames = []
    cap = cv2.VideoCapture(video)
    w = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
    h = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
    while(True):
        ret, img = cap.read()
        if not ret:
            break
        video_frames.append(img)
    cap.release()

    #トラッキングを実行
    jpeg_filenames_list = []

    for ind, frame in enumerate(video_frames):
        if ind == 0:
            tracker.init(frame, init_rect_list[i])
            bbox = init_rect_list[i]
        else:
            outputs = tracker.track(frame)
            bbox = outputs['bbox']
            
        filename = '%d%06d'%((i+1),ind)

        #画像の保存
        jpeg_filename = filename + '.jpg'
        cv2.imwrite(os.path.join(train_images_dir, jpeg_filename), frame)

        #ラベルテキストの保存
        txt_filename= filename + '.txt'
        with open(os.path.join(train_labels_dir, txt_filename), 'w') as f:
            center_x = (bbox[0] + bbox[2] / 2) / w
            center_y = (bbox[1] + bbox[3] / 2) / h
            width = bbox[2] / w
            height = bbox[3] / h
            f.write('%d %f %f %f %f'%(i, center_x, center_y, width, height))

with open('train.yaml', 'w', encoding='cp932') as f:
    f.write('path: %s'%out_path)
    f.write('\n')
    f.write('train: images/train')
    f.write('\n')
    f.write('val: images/train')
    f.write('\n')
    f.write('nc: %d'%len(video_list))
    f.write('\n')
    f.write('names: ')
    f.write('[')
    output_target_name = ['\'' + x + '\'' for x in target_name]
    f.write(', '.join(output_target_name))
    f.write(']')

このスクリプトを実行すると「train_data」フォルダと「train.yaml」が作成されます。

YAMLファイル

上記スクリプトで作成されたYAMLファイルの中身はこのようになっています。

path: train_data
train: images/train
val: images/train
nc: 2
names: ['target', 'green']

転移学習

まずはYOLOv5のGitHubをcloneする必要があります。(GitHubからZIPでダウンロードしても可)
train.pyと同じフォルダに先ほど作成した「train_data」フォルダと「train.yaml」を移動させて下さい。
そして以下のようにtrain.pyを実行すれば学習が始まります。
batchとepochsの数字は適当に変更して下さい。

python train.py --batch 16 --epochs 10 --data train.yaml --weights yolov5s.pt

学習が終わると「runs/train/exp/weights」フォルダに「best.pt」と「last.pt」が保存されます。

結果を確認する方法

ここからはGitHubのcloneは必要ありません。保存された「best.pt」をどこに移動しても構いません。
ただし、モデルを読み込むときにpathを設定して下さい。

静止画で結果確認

import torch
from torchvision.datasets.utils import download_url

url = 'https://github.com/dai-ichiro/robo-one/raw/main/test.jpg'
fname = url.split('/')[-1]
download_url(url, root = '.', filename = fname)

model = torch.hub.load('ultralytics/yolov5', 'custom', path = 'best.pt')
results = model([fname])
results.show()

冒頭で紹介した図になります。

Webカメラで結果確認

import torch
import cv2
from matplotlib import pyplot as plt

device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu') 

model = torch.hub.load('ultralytics/yolov5', 'custom', path = 'best.pt')
model.to(device)
class_num = model.yaml['nc']

cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)

colors = dict()

while True:
    ret, frame = cap.read()

    frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    results = model(frame_rgb)

    pandasDF = results.pandas().xyxy[0]

    for row in pandasDF.itertuples():
        
        if row.confidence < 0.8: break

        score = '{:.3f}'.format(row.confidence)
        xmin = int(row.xmin)
        ymin = int(row.ymin)
        xmax = int(row.xmax)
        ymax = int(row.ymax)
        class_id = row._6 #(class)
        class_name = row.name
        
        if class_id not in colors:
            colors[class_id] = plt.get_cmap('hsv')(class_id / class_num)

        bcolor = [x * 255 for x in colors[class_id]]
        cv2.rectangle(frame, (xmin, ymin), (xmax, ymax), bcolor, 3)

        y = ymin - 15 if ymin - 15 > 15 else ymin + 15
        cv2.putText(frame, '{:s} {:s}'.format(class_name, score),
                        (xmin, y), cv2.FONT_HERSHEY_SIMPLEX, 1.0,
                        bcolor, 3, lineType=cv2.LINE_AA)

    cv2.imshow('demo', frame)

    if cv2.waitKey(1) & 0xFF == 27:
        break

cap.release()
cv2.destroyAllWindows()

参考にさせて頂いたサイト

さいごに

間違いや改善点があればコメント頂けましたら幸いです。

PyTorchに入門してみる part3 Object Trackingを行ってみる

はじめに

以前MXNetとGluonCVを使ってやったことをPyTorchでやることによってPyTorchの勉強を進めます。

今回はObject Trackingです。

これによって物体検出モデルの学習データが効率よく作成できることを以前証明しました。



Object Trackingはこちらを使わせて頂きました。
github.com
開発がすでに終了したのか最近更新されていません。しかし、Windowsと現時点で最新のPyTorchの環境で問題なく動作しました。

環境

GPUあり

Windows 11 
Core i7-7700K + GTX 1080

Python 3.8.10

新しいPython仮想環境を作り以下をインストールしました。

pip install torch==1.9.1+cu102 torchvision==0.10.1+cu102 torchaudio===0.9.1 -f https://download.pytorch.org/whl/torch_stable.html
pip install opencv-python
pip install yacs

Pythonの環境構築はこれだけで終了です。このようになりました。

numpy==1.21.2
opencv-python==4.5.3.56
Pillow==8.4.0
PyYAML==6.0
torch==1.9.1+cu102
torchaudio==0.9.1
torchvision==0.10.1+cu102
typing-extensions==3.10.0.2
yacs==0.1.8

GPUなし

Windows 10
Core i7-1165G7

Python 3.8.10

新しいPython仮想環境を作り以下をインストールしました。

pip install torch torchvision torchaudio
pip install opencv-python
pip install yacs

Pythonの環境構築はこれだけで終了です。このようになりました。

numpy==1.21.2
opencv-python==4.5.3.56
Pillow==8.4.0
PyYAML==6.0
torch==1.9.1
torchaudio==0.9.1
torchvision==0.10.1
typing-extensions==3.10.0.2
yacs==0.1.8

ソースコードのダウンロード

gitが使えるならgit clone、使えないならGitHubページからZIP形式でダウンロードして解凍すれば良いです。
f:id:touch-sp:20211018153311p:plain:w500

学習済みモデルのダウンロード

Model Zooが用意されておりそちらからダウンロード可能です。
github.com
今回は「siamrpn_r50_l234_dwxcorr」というモデルをダウンロードしました。


「model.pth」という名前のファイルがダウンロードされます。以下のフォルダに入れておきます。
pysot >> experiments >> siamrpn_r50_l234_dwxcorr >> model.pth

Pythonスクリプト

Tracking結果がVOCフォーマットで出力されるように以下のPythonスクリプトを新たに書きました。

実行する時のカレントディレクトリは一つ目のpysotにしておく必要があります。

推奨されているインストール手順を省略しているので、そうしないと以下のimportに失敗します。

from pysot.core.config import cfg
from pysot.models.model_builder import ModelBuilder
from pysot.tracker.tracker_builder import build_tracker

最終的なスクリプト

実行すると画面が表示されるのでTrackingしたい物体をマウスで囲ってください。
スペースキーを押すとそれ以降のスクリプトが実行されます。

import os

import cv2
import torch
import xml.etree.ElementTree as ET

from pysot.core.config import cfg
from pysot.models.model_builder import ModelBuilder
from pysot.tracker.tracker_builder import build_tracker

#=========================================================
video_name = 'demo/bag.avi'

target_name = 'target'

out_path = 'train_data'

config = 'experiments/siamrpn_r50_l234_dwxcorr/config.yaml'
snapshot = 'experiments/siamrpn_r50_l234_dwxcorr/model.pth'
#=========================================================

annotation_dir = os.path.join(out_path, 'Annotations')
main_dir =  os.path.join(out_path, 'ImageSets/Main')
jpegimages_dir = os.path.join(out_path, 'JPEGImages')

os.makedirs(annotation_dir)
os.makedirs(main_dir)
os.makedirs(jpegimages_dir)

# 映像ファイルを読み込む
video_frames = []
cap = cv2.VideoCapture('demo/bag.avi')
while(True):
    ret, img = cap.read()
    if not ret:
        break
    video_frames.append(img)
cap.release()

# モデルを取得する
cfg.merge_from_file(config)
cfg.CUDA = torch.cuda.is_available() and cfg.CUDA
device = torch.device('cuda' if cfg.CUDA else 'cpu')
model = ModelBuilder()
model.load_state_dict(torch.load(snapshot,
    map_location=lambda storage, loc: storage.cpu()))
model.eval().to(device)
tracker = build_tracker(model)

#最初の位置を取得する
init_rect = cv2.selectROI(video_name, video_frames[0], False, False)

#トラッキングを実行
jpeg_filenames_list = []

for ind, frame in enumerate(video_frames):
    if ind == 0:
        tracker.init(frame, init_rect)
        bbox = init_rect
    else:
        outputs = tracker.track(frame)
        bbox = list(map(int, outputs['bbox']))
        '''
        cv2.rectangle(frame, (bbox[0], bbox[1]),
                        (bbox[0]+bbox[2], bbox[1]+bbox[3]),
                        (0, 255, 0), 3)
        cv2.imshow(video_name, frame)
        cv2.waitKey(1)
        '''
    filename = '%06d'%(ind)

    #画像の保存
    jpeg_filename = filename + '.jpg'
    cv2.imwrite(os.path.join(jpegimages_dir, jpeg_filename), frame)

    #テキストファイルの作成
    jpeg_filenames_list.append(filename)

    #XMLファイルの保存
    xml_filename = filename + '.xml'
    
    new_root = ET.Element('annotation')
    
    new_filename = ET.SubElement(new_root, 'filename')
    new_filename.text = jpeg_filename

    Size = ET.SubElement(new_root, 'size')
    Width = ET.SubElement(Size, 'width')
    Height = ET.SubElement(Size, 'height')
    Depth = ET.SubElement(Size, 'depth')

    Width.text = str(frame.shape[1])
    Height.text = str(frame.shape[0])
    Depth.text = str(frame.shape[2])

    Object = ET.SubElement(new_root, 'object')
    
    Name = ET.SubElement(Object, 'name')
    Name.text = target_name

    Difficult = ET.SubElement(Object, 'difficult')
    Difficult.text = '0'

    Bndbox = ET.SubElement(Object, 'bndbox')
    Xmin = ET.SubElement(Bndbox, 'xmin')
    Ymin = ET.SubElement(Bndbox, 'ymin')
    Xmax = ET.SubElement(Bndbox, 'xmax')
    Ymax = ET.SubElement(Bndbox, 'ymax')

    Xmin.text = str(bbox[0])
    Ymin.text = str(bbox[1])
    Xmax.text = str(bbox[0]+bbox[2])
    Ymax.text = str(bbox[1]+bbox[3])

    new_tree = ET.ElementTree(new_root) 

    new_tree.write(os.path.join(annotation_dir, xml_filename))

#テキストファイルの保存
text = "\n".join(jpeg_filenames_list)
with open(os.path.join(main_dir, 'train.txt'), "w") as f:
    f.write(text)

cv2.destroyAllWindows()

結果

実行がうまくいくと「train_data」というフォルダが出力されます。

VOCフォーマットになっているのでこのまま物体検出モデルの転移学習に使えるはずです。

PyTorchでの転移学習はこれから学習します。

さいごに

間違いや改善点があればコメント頂けましたら幸いです。

2021年10月19日追記

つづきを書きました。
touch-sp.hatenablog.com

PyTorchに入門してみる part2 GluonCVでは使えなかった物体検出モデル DETR を使ってみる

はじめに

前回に引き続き学習済みモデルを使いながらPyTorchに慣れていきます。


せっかくなのでGluonCVでは使えなかった比較的新しいモデルを使ってみようと思います。
github.com

環境

GPUがある環境とない環境のふたつで動作確認しました。

GPUあり

Windows 11 
Core i7-7700K + GTX 1080

Python 3.8.10

Pythonの新しい仮想環境を作り以下のように必要なものをインストールしました。引っかかるところは幸いありませんでした。

pip install torch==1.9.1+cu102 torchvision==0.10.1+cu102 torchaudio===0.9.1 -f https://download.pytorch.org/whl/torch_stable.html
pip install matplotlib
pip install scipy
pip install packaging

このようになりました。

cycler==0.10.0
kiwisolver==1.3.2
matplotlib==3.4.3
numpy==1.21.2
packaging==21.0
Pillow==8.4.0
pyparsing==2.4.7
python-dateutil==2.8.2
scipy==1.7.1
six==1.16.0
torch==1.9.1+cu102
torchaudio==0.9.1
torchvision==0.10.1+cu102
typing-extensions==3.10.0.2

GPUなし

Windows 10
Core i7-1165G7

Python 3.8.10

Pythonの新しい仮想環境を作り以下のように必要なものをインストールしました。引っかかるところは幸いありませんでした。

pip install torch torchvision torchaudio
pip install matplotlib
pip install scipy
pip install packaging

このようになりました。

cycler==0.10.0
kiwisolver==1.3.2
matplotlib==3.4.3
numpy==1.21.2
packaging==21.0
Pillow==8.4.0
pyparsing==2.4.7
python-dateutil==2.8.2
scipy==1.7.1
six==1.16.0
torch==1.9.1
torchaudio==0.9.1
torchvision==0.10.1
typing-extensions==3.10.0.2

Pythonスクリプト

import torch
from torchvision import transforms
from PIL import Image
import urllib
from matplotlib import pyplot as plt
import numpy as np

device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu') 

CLASSES = [
    'N/A', 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus',
    'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 'N/A',
    'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse',
    'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'N/A', 'backpack',
    'umbrella', 'N/A', 'N/A', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis',
    'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove',
    'skateboard', 'surfboard', 'tennis racket', 'bottle', 'N/A', 'wine glass',
    'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich',
    'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake',
    'chair', 'couch', 'potted plant', 'bed', 'N/A', 'dining table', 'N/A',
    'N/A', 'toilet', 'N/A', 'tv', 'laptop', 'mouse', 'remote', 'keyboard',
    'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'N/A',
    'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier',
    'toothbrush'
]

model = torch.hub.load('facebookresearch/detr:main', 'detr_resnet50', pretrained=True)
model.to(device)
model.eval()

url = 'https://raw.githubusercontent.com/zhreshold/mxnet-ssd/master/data/demo/person.jpg'
img_file = 'person.jpg'
try: urllib.URLopener().retrieve(url, img_file)
except: urllib.request.urlretrieve(url, img_file)

transform = transforms.Compose([
    transforms.Resize(800),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])

img = Image.open(img_file)

input_tensor = transform(img)               # <class 'torch.Tensor'>, torch.Size([3, 800, 1207])
input_batch = input_tensor.unsqueeze(0)     # <class 'torch.Tensor'>, torch.Size([1, 3, 800, 1207])

with torch.no_grad():
    results = model(input_batch.to(device))    # <class 'dict'>

prob = results['pred_logits'].softmax(-1)[0, :, :-1]    # torch.Size([100, 91])
prob = prob.max(-1)                                     # <class 'torch.return_types.max'>

scores = prob.values                        # torch.Size([100])
class_IDs = prob.indices                    # torch.Size([100])
bboxes = results['pred_boxes'].squeeze()    # torch.Size([100, 4])

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.imshow(np.array(img))

colors = dict()
threshold = 0.8
for i, each_score in enumerate(scores):
    if each_score < threshold: continue
    score = '{:.3f}'.format(float(each_score))
    class_id = int(class_IDs[i])
    class_name = CLASSES[class_id]
    if class_id not in colors:
        colors[class_id] = plt.get_cmap('hsv')(class_id / len(CLASSES))
    centerX, centerY, w, h = [float(x) for x in bboxes[i]]
    xmin = (centerX - 0.5 * w) * img.size[0]
    ymin = (centerY - 0.5 * h) * img.size[1]
    w = w * img.size[0]
    h = h * img.size[1]
    rect = plt.Rectangle((xmin, ymin), w, h,
                             fill=False,
                             edgecolor=colors[class_id],
                             linewidth=3.5)
    ax.add_patch(rect)
    ax.text(xmin, ymin - 2,
                '{:s} {:s}'.format(class_name, score),
                bbox=dict(facecolor=colors[class_id], alpha=0.5),
                fontsize=12, color='white')

plt.axis('off')        
plt.show()

結果

f:id:touch-sp:20211017012044p:plain:w400
以下のような警告が出ましたが問題なく動作しています。警告を消す方法は今のところわかりません。

UserWarning: floor_divide is deprecated, and will be removed in a future version of pytorch. It currently rounds toward 0 (like the 'trunc' function NOT 'floor'). This results in incorrect rounding for negative values.
To keep the current behavior, use torch.div(a, b, rounding_mode='trunc'), or for actual floor division, use torch.div(a, b, rounding_mode='floor'). (Triggered internally at  ..\aten\src\ATen\native\BinaryOps.cpp:467.)

参考にさせていただいたサイト

さいごに

間違いや改善点があればコメント頂けましたら幸いです。

PyTorchに入門してみる part1 Segmentationモデルで人物の切り抜き

はじめに

前回の記事は読んで頂けましたでしょうか?

前回、PyTorchを勉強しようと決心しました。


とりあえず学習済みモデルで何かをしてみようと思い人物の切り抜きをやってみました。


「MXNet」の「GluonCV」に相当するのが「PyTorch Hub」なのでしょうか?


ほとんどチュートリアル通りにスクリプトを書いてみました。

Pythonスクリプト

驚くことにほとんど同じスクリプトになりました。どちらが先かはわかりませんが片方がもう一方の良い部分を取り入れた結果ではないでしょうか。

MXNetのスクリプト

こちらは以前に書いたスクリプトです。

import numpy as np
from PIL import Image
import mxnet as mx
from mxnet.gluon.data.vision import transforms
import gluoncv

filename = gluoncv.utils.download('https://raw.githubusercontent.com/dmlc/web-data/master/gluoncv/segmentation/voc_examples/1.jpg')

#画像をPILで読み込む
img = Image.open(filename)

#データの正規化
transform_fn = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize([.485, .456, .406], [.229, .224, .225])
])
input_tensor = transform_fn(mx.nd.array(img))
input_batch = input_tensor.expand_dims(0)

#モデルを読み込む
model = gluoncv.model_zoo.get_model('fcn_resnet101_voc', pretrained=True)

#モデルの適応
#人だけを抽出する(class:15)
output = model.predict(input_batch)
predict = mx.nd.squeeze(mx.nd.argmax(output, 1)).asnumpy()
a = np.where(predict ==15, 255, 0)
b = Image.fromarray(a).convert('L')

#元画像と結果を重ね合わせる
img.putalpha(b)
img.show()

PyTorchのスクリプト

import numpy as np
from PIL import Image
import urllib
import torch
from torchvision import transforms

url, filename = ("https://raw.githubusercontent.com/dmlc/web-data/master/gluoncv/segmentation/voc_examples/1.jpg", "1.jpg")
try: urllib.URLopener().retrieve(url, filename)
except: urllib.request.urlretrieve(url, filename)

#画像をPILで読み込む
img = Image.open(filename)

#データの正規化
transform_fn = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize([.485, .456, .406], [.229, .224, .225]),
])
input_tensor = transform_fn(img)
input_batch = input_tensor.unsqueeze(0)

#モデルを読み込む
model = torch.hub.load('pytorch/vision:v0.10.0', 'fcn_resnet101', pretrained=True)
model.eval()

#モデルの適応
#人だけを抽出する(class:15)
with torch.no_grad():
    output = model(input_batch)['out'][0]
predict = output.argmax(0).numpy()
a = np.where(predict ==15, 255, 0)
b = Image.fromarray(a).convert('L')

#元画像と結果を重ね合わせる
img.putalpha(b)
img.show()

結果

f:id:touch-sp:20211016184836j:plain:w200
元画像
f:id:touch-sp:20211016185634p:plain:w200
MXNetの結果
f:id:touch-sp:20211016185724p:plain:w200
PyTorchの結果

感想

ここまで似ているなら学習コストは低く済みそうです。

【悲報】MXNetとAutoGluonの決別

AutoGluonのGitHubをなにげなくのぞいたらこのように書かれていました。


f:id:touch-sp:20211016155640p:plain
https://github.com/awslabs/autogluon/issues/1343



「PyTorch」と「TensorFlow」の二大巨頭の後ろでひっそりと存在していた「MXNet」というDeep Learningフレームワークを皆さんはご存じでしょうか?


もともと「MXNet」から派生したはずの「AutoGluon」がこれからは「PyTorch」とともに歩むと書かれています。


人気の出ない「MXNet」にとっての唯一といっていい希望の光が「AutoGluon」だったはずです。


「AutoGluon」が世に出た時にはすこし話題に上り、これから「MXNet」の人気が上がっていくと期待したのですがそれははかない夢でした。


ということで今更ながら「PyTorch」を勉強しようと決心しました。

【物体検出】MXNet 2.0(ベータ)でGluonCVの学習済みモデルを使用する

はじめに

前回MXNet 2.0(ベータ)でGluonCVの学習済み画像分類モデルを使用する方法を書きました。
touch-sp.hatenablog.com
今回はMXNet 2.0(ベータ)でGluonCVの学習済み物体検出モデルを使用する方法を書きます。


手順は前回と同様です。

手順

MXNet 1.xでモデルをdownloadしてexport

ここではGluonCVが必要です。

from gluoncv import model_zoo
from gluoncv.utils import export_block

net = model_zoo.get_model('yolo3_darknet53_voc', pretrained=True, root='models')
net.hybridize()

export_block('yolo3', net, preprocess=None, layout='CHW')

with open('detection_class_names.txt', 'w') as f:
    f.writelines('\n'.join(net.classes))

MXNet 2.0(ベータ)でモデルを読み込んで使用する

ここではGluonCVは必要ありません。
GluonCVの「utils.viz.plot_bbox」が使えないため結果表示のスクリプトが長くなってしまいました。
MXNet以外にmatplotlibのインストールが必要です。

from mxnet import np, npx, gluon, image
from matplotlib import pyplot as plt

ctx = npx.gpu() if npx.num_gpus() > 0 else npx.cpu()

with open('detection_class_names.txt', 'r') as f:
    classes = [x.strip() for x in f.readlines()]

url = 'https://raw.githubusercontent.com/zhreshold/mxnet-ssd/master/data/demo/person.jpg'
img = gluon.utils.download(url)
x = image.imread(img)
x = image.resize_short(x, 512)

def transform(data):
    rgb_mean = np.array([0.485, 0.456, 0.406])
    rgb_std = np.array([0.229, 0.224, 0.225])
    data = (data.astype('float32') / 255 - rgb_mean) / rgb_std
    data = np.expand_dims(np.transpose(data, (2,0,1)), axis=0)
    return data

net = gluon.SymbolBlock.imports("yolo3-symbol.json",['data'], "yolo3-0000.params")
net.reset_ctx(ctx)

class_IDs, scores, bounding_boxs = net(transform(x).as_in_ctx(ctx))

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.imshow(x.asnumpy())

colors = dict()
threshold = 0.8
for i, each_score in enumerate(scores[0]):
    if each_score < threshold: break
    score = '{:.3f}'.format(each_score.item())
    class_id = int(class_IDs[0][i].item())
    class_name = classes[class_id]
    if class_id not in colors:
        colors[class_id] = plt.get_cmap('hsv')(class_id / len(classes))
    xmin, ymin, xmax, ymax = [int(x) for x in bounding_boxs[0][i]]
    rect = plt.Rectangle((xmin, ymin), xmax - xmin, ymax - ymin,
                             fill=False,
                             edgecolor=colors[class_id],
                             linewidth=3.5)
    ax.add_patch(rect)
    ax.text(xmin, ymin - 2,
                '{:s} {:s}'.format(class_name, score),
                bbox=dict(facecolor=colors[class_id], alpha=0.5),
                fontsize=12, color='white')

plt.axis('off')        
plt.show()

結果

f:id:touch-sp:20211016003553p:plain:w400

環境

GPUなし

Ubuntu 20.04LTS on WSL2
Python 3.8.10
certifi==2021.10.8
charset-normalizer==2.0.7
cycler==0.10.0
graphviz==0.8.4
idna==3.3
kiwisolver==1.3.2
matplotlib==3.4.3
mxnet==2.0.0b20211015
numpy==1.21.2
Pillow==8.4.0
pkg_resources==0.0.0
pyparsing==2.4.7
python-dateutil==2.8.2
requests==2.26.0
six==1.16.0
urllib3==1.26.7

GPUあり

Ubuntu 20.04LTS on WSL2
Python 3.8.10
certifi==2021.10.8
charset-normalizer==2.0.7
cycler==0.10.0
graphviz==0.8.4
idna==3.3
kiwisolver==1.3.2
matplotlib==3.4.3
mxnet-cu112==2.0.0b20211015
numpy==1.21.2
Pillow==8.4.0
pkg_resources==0.0.0
pyparsing==2.4.7
python-dateutil==2.8.2
requests==2.26.0
six==1.16.0
urllib3==1.26.7

補足

MXNet 1.xとGluonCVを使う場合は非常に簡単です。こちらを参照して下さい。
touch-sp.hatenablog.com