«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
--Classification d'images multi-classes et multi-étiquettes. -Une image comme "Cette image est de classe A. Cette image correspond à A et B."
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
}
}
# 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
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))
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)
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
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])
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.
Recommended Posts