[PYTHON] Classification multi-étiquette d'images multi-classes avec pytorch

introduction

«J'ai essayé de faire ce que dit le titre. J'ai tout fait, donc comme mémo. Il y a une raison pour laquelle vous ne pouvez pas toucher profondément le contenu, et certaines parties sont un peu désordonnées. ――Par exemple, où avez-vous déclaré cette variable? Il peut y avoir une cible

Ce que j'ai fait

--Classification d'images multi-classes et multi-étiquettes. -Une image comme "Cette image est de classe A. Cette image correspond à A et B."

Structure des dossiers

J'ai fait ce qui suit. Mais ce n'est peut-être pas le meilleur pour être honnête. Cela s'est produit parce que l'enquête a commencé à fonctionner modérément.

Structure des dossiers


.
├── data
│   ├── labels        //Stockage Json de combinaison d'images et d'étiquettes
│   │     ├── A.json
│   │     ├── B.json
│ │ └── et bien d'autres jsons
│   └── images        //jpg Stockage d'images. Mixte pour l'apprentissage et la vérification
│         ├── A.jpg
│         ├── B.jpg
│ └── et bien d'autres jpgs
├── model             //Destination d'enregistrement du modèle
└── predict           //Installé en tant que zone de stockage d'images que vous souhaitez prédire

À propos, le contenu de json sous les étiquettes est le suivant. La clé est le nom de l'image et la valeur est les informations de classe (1 ou 0).

échantillon


# A.json
{
    "A": {
        "Étiquette A": 1,
        "Étiquette B": 1,
        "Étiquette C": 0
    }
}

# B.json
{
    "B": {
        "Étiquette A": 0,
        "Étiquette B": 0,
        "Étiquette C": 1
    }
}

code

Divers préparations

# ref: https://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html#sphx-glr-beginner-blitz-cifar10-tutorial-py

from PIL import Image
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
import numpy as np
import pathlib
import random


#À utiliser si vous avez un GPU
def check_cuda():
    return 'cuda:0' if torch.cuda.is_available() else 'cpu'
device = torch.device(check_cuda())

#Données de formation, division des données de test
image_set = {pathlib.Path(i).stem for i in pathlib.Path('data/images').glob('*.jpg')}
n_data = len(image_set)
traindata_rate = 0.7
train_idx = random.sample(range(n_data), int(n_data*traindata_rate))

_trainset = {}
_testset = {}
for i, _tuple in enumerate(image_set.items()):
    k, v = _tuple    
    if i in train_idx:
        _trainset[k] = v
    else :
        _testset[k] = v

Transform

# ref: https://qiita.com/takurooo/items/e4c91c5d78059f92e76d
trfm = transforms.Compose([
    transforms.Resize((100, 100)),    # image size --> (100, 100)
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
])

Dataset

class MultiLabelDataSet(torch.utils.data.Dataset):
    def __init__(self, labels, image_dir='./data/images', ext='.jpg', transform=None):
        self.labels = labels
        self.image_dir = image_dir
        self.ext = ext
        self.transform = transform

        self.keys = list(labels.keys())
        self.vals = list(labels.values())

    def __len__(self):
        return len(self.labels)

    def __getitem__(self, idx):
        image_path = f'{self.image_dir}/{self.keys[idx]}{self.ext}'
        image_array = Image.open(image_path)
        if self.transform:
            image = self.transform(image_array)
        else:
            image = torch.Tensor(np.transpose(image_array, (2, 0, 1)))/255  # for 0~1 scaling
            
        label = torch.Tensor(list(self.vals[idx].values()))

        return {'image': image, 'label': label}

DataLoader

batch_size = 8

trainset = MultiLabelDataSet(_trainset, transform=trfm)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size,
                                          shuffle=False, num_workers=2)

testset = MultiLabelDataSet(_testset, transform=trfm)
testloader = torch.utils.data.DataLoader(testset, batch_size=batch_size,
                                         shuffle=False, num_workers=2)

classes = ['A', 'B', 'C'...]Comme

vérification des données

import matplotlib.pyplot as plt
%matplotlib inline

# functions to show an image
def imshow(img):
    plt.imshow(np.transpose(img, (1, 2, 0)))
    plt.show()

# sample data
dataiter = iter(trainloader)
tmp = dataiter.next()
images = tmp['image']
labels = tmp['label']

# print images
imshow(torchvision.utils.make_grid(images))

modèle

Le nombre de couches et de canaux est approprié ... J'utilise BCEWithLogitsLoss pour ne pas mordre les sigmoids (je l'ai dit quand j'ai googlé)

class MultiClassifier(nn.Module):
    def __init__(self):
        super(MultiClassifier, self).__init__()

        self.ConvLayer1 = nn.Sequential(
            # ref(H_out & W_out): https://pytorch.org/docs/stable/nn.html#conv2d
            nn.Conv2d(3, 32, 3),
            nn.MaxPool2d(2),
            nn.ReLU(),
            )

        self.ConvLayer2 = nn.Sequential(
            nn.Conv2d(32, 64, 3),
            nn.MaxPool2d(2),
            nn.ReLU(),
            )

        self.ConvLayer3 = nn.Sequential(
            nn.Conv2d(64, 128, 3),
            nn.MaxPool2d(2),
            nn.ReLU(),
            )    

        self.ConvLayer4 = nn.Sequential(
            nn.Conv2d(128, 256, 3),
            nn.MaxPool2d(2),
            nn.ReLU(),
            nn.Dropout(0.2, inplace=True),
            )    

        self.Linear1 = nn.Linear(256 * 4 * 4, 2048)
        self.Linear2 = nn.Linear(2048, 1024)
        self.Linear3 = nn.Linear(1024, 512)
        self.Linear4 = nn.Linear(512, len(classes))


    def forward(self, x):
        x = self.ConvLayer1(x)
        x = self.ConvLayer2(x)
        x = self.ConvLayer3(x)
        x = self.ConvLayer4(x)
#         print(x.size())
        x = x.view(-1, 256 * 4 * 4)
        x = self.Linear1(x)
        x = self.Linear2(x)
        x = self.Linear3(x)
        x = self.Linear4(x)
        return x

def try_gpu(target):
    if check_cuda():
        device = torch.device(check_cuda())
        target.to(device)

model = MultiClassifier()
try_gpu(model)

entraînement

Une variable appelée «pos_weight» apparaît soudainement dans le critère, mais c'est à cause de la pondération lorsque la classe positive est correcte. https://pytorch.org/docs/stable/nn.html#torch.nn.BCEWithLogitsLoss

Si vous n'avez pas besoin d'une telle opération, vous n'avez pas besoin de la spécifier. Je l'ai précisé car je voulais augmenter le poids au moment de la bonne réponse. Les détails sont liés comme réf, donc là ~~ j'échapperai à l'explication ~~

# ref: https://medium.com/@thevatsalsaglani/training-and-deploying-a-multi-label-image-classifier-using-pytorch-flask-reactjs-and-firebase-c39c96f9c427
import numpy as np
from pprint import pprint
from torch.autograd import Variable
import torch.optim as optim

# ref: https://discuss.pytorch.org/t/bceloss-vs-bcewithlogitsloss/33586
# ref: https://discuss.pytorch.org/t/weights-in-bcewithlogitsloss/27452
criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)
try_gpu(criterion)

optimizer = optim.SGD(model.parameters(), lr = 0.005, momentum = 0.9)

def pred_acc(original, predicted):
    # ref: https://pytorch.org/docs/stable/torch.html#module-torch
    return torch.round(predicted).eq(original).sum().numpy()/len(original)


def fit_model(epochs, model, dataloader, phase='training', volatile = False):
    if phase == 'training':
        model.train()
        
    if phase == 'validataion':
        model.eval()
        volatile = True
        
    running_loss = []
    running_acc = []
    for i, data in enumerate(dataloader):
        inputs, target = Variable(data['image']), Variable(data['label'])
        
        # for GPU
        if device != 'cpu':
            inputs, target = inputs.to(device), target.to(device)

        if phase == 'training':
            optimizer.zero_grad()  #Initialisation du gradient

        ops = model(inputs)
         acc_ = []
         for j, d in enumerate(ops):
             acc = pred_acc(torch.Tensor.cpu(target[j]), torch.Tensor.cpu(d))
             acc_.append(acc)

        loss = criterion(ops, target)
        running_loss.append(loss.item())
        running_acc.append(np.asarray(acc_).mean())
        
        if phase == 'training':
            loss.backward()  #Erreur de propagation de retour
            optimizer.step() #Mise à jour des paramètres

    total_batch_loss = np.asarray(running_loss).mean()
    total_batch_acc = np.asarray(running_acc).mean()

    if epochs % 10 == 0:
        pprint(f"[{phase}] Epoch: {epochs}, loss: {total_batch_loss}.")
        pprint(f"[{phase}] Epoch: {epochs}, accuracy: {total_batch_acc}.")
    
    return total_batch_loss, total_batch_acc


from tqdm import tqdm

num = 50
best_val = 99
trn_losses = []; trn_acc = []
val_losses = []; val_acc = []
for idx in tqdm(range(1, num+1)):
    trn_l, trn_a = fit_model(idx, model, trainloader)
    val_l, val_a = fit_model(idx, model, testloader, phase='validation')
    trn_losses.append(trn_l); trn_acc.append(trn_a)
    val_losses.append(val_l); val_acc.append(val_a)

    if best_val > val_l:
        torch.save(model.state_dict(), f'model/best_model.pth')
        best_val = val_l
        best_idx = idx

Prévoir

def get_tensor(img):
    tfms = transforms.Compose([
        transforms.Resize((100, 100)),
        transforms.ToTensor(),
        transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
        ])
    return tfms(Image.open(img)).unsqueeze(0)

def predict(img, label_lst, model):
    tnsr = get_tensor(img)
    op = model(tnsr)  # Predict result(float)
    op_b = torch.round(op) # Rounding result(0 or 1)
    op_b_np = torch.Tensor.cpu(op_b).detach().numpy()
    preds = np.where(op_b_np == 1)[1]  # result == 1
    
    sigs_op = torch.Tensor.cpu(torch.round((op)*100)).detach().numpy()[0]
    o_p = np.argsort(torch.Tensor.cpu(op).detach().numpy())[0][::-1]  # label index order by score desc
    
    # anser label
    label = [label_lst[i] for i in preds]
    
    # all result
    arg_s = {label_lst[int(j)] : sigs_op[int(j)] for j in o_p}

    return label, dict(arg_s.items())


model = MultiClassifier()
model.load_state_dict(torch.load(f'model/best_model.pth', map_location=torch.device('cpu')))
model = model.eval()    #Passer en mode inférence

target = 'XXXXXX'
img = Image.open(f'predict/{target}.jpg').resize((100, 100))
plt.imshow(img)

_, all_result = predict(f'predict/{target}.jpg', classes, model)
print('predict top5: ', *sorted(all_result.items(), key=lambda x: x[1], reverse=True)[:5])

finalement

C'est tout pour la mise en œuvre.

Augmentation des données (Cela semble facile à mettre en œuvre), polissage de modèles, Je pense qu'il est encore possible d'améliorer la précision si des paramètres de poids appropriés sont effectués pendant l'évaluation.

J'étais satisfait de ce que je voulais faire pour le moment.









… Pour votre information. J'ai fait quelque chose à prévoir comme ça.

sample.png

Recommended Posts

Classification multi-étiquette d'images multi-classes avec pytorch
[PyTorch] Classification des images du CIFAR-10
Prédiction de la moyenne Nikkei avec Pytorch 2
Prédiction de la moyenne Nikkei avec Pytorch
Prédiction de la moyenne Nikkei avec Pytorch ~ Makuma ~
Classification multi-étiquettes par forêt aléatoire avec scikit-learn
Algorithme d'apprentissage automatique (implémentation de la classification multi-classes)
Transcription d'images avec l'API Vision de GCP
Classification des documents avec texte toch de PyTorch
Mélangez des centaines de milliers d'images uniformément avec tensorflow.
Classification des images de guitare par apprentissage automatique Partie 1
Jouez avec PyTorch
Catégoriser les images de visage de personnages d'anime avec Chainer
SVM (classification multi-classes)
Histoire d'essayer d'utiliser Tensorboard avec Pytorch
Validation croisée avec PyTorch
À partir de PyTorch
Classification des images de guitare par apprentissage automatique, partie 2
Conversion en ondelettes d'images avec PyWavelets et OpenCV
Comment utiliser xgboost: classification multi-classes avec des données d'iris
Afficher des images intégrées de mp3 et flac avec mutagène
Essayez de projeter la conversion d'image en utilisant OpenCV avec Python
[PyTorch] Un peu de compréhension de CrossEntropyLoss avec des formules mathématiques
Créez un lot d'images et gonflez avec ImageDataGenerator
Résumé des problèmes lors de la segmentation sémantique avec Pytorch
Préparation de l'environnement d'exécution de PyTorch avec Docker Novembre 2019
J'ai essayé la méthode la plus simple de classification de documents multi-étiquettes
J'ai essayé la "conversion de morphologie" de l'image avec Python + OpenCV
SVM multi-classes avec scikit-learn
Indice de classification typique
Naive Bays (classification multi-classes)
Iris de classification multiclasse Keras
Installer la diffusion de la torche avec PyTorch 1.7
Centrer l'image avec python-pptx
Sauvegardez la sortie de GAN une par une ~ Avec l'implémentation de GAN par PyTorch ~
Classification d'images avec un réseau de neurones auto-fabriqué par Keras et PyTorch
L'histoire de l'affichage d'images avec OpenCV ou PIL (uniquement)
Classification en temps réel de plusieurs objets dans les images de la caméra avec apprentissage en profondeur de Raspberry Pi 3 B + et PyTorch