[PYTHON] Kumantic Segumantion

"Kumantic Segumantion" pour obtenir des informations sur Kuma à partir de l'image de Kuma. Ceci est une suite de Kumantic Segmentation 2.

Différence par rapport à la dernière fois

La dernière fois, j'ai cherché à "détecter plusieurs </ b> empreintes de pas d'ours" en utilisant "une image de multiples </ b> empreintes de pas d'ours" pour apprendre. , N'a pas fonctionné).

Cette fois, nous visons à "détecter plusieurs </ b> silhouettes d'ours" en utilisant "une image qui montre une seule </ b> silhouette d'ours" pour l'apprentissage.

Générer automatiquement une image d'un ours

Utilisé pour créer des ensembles de données pour la formation et la validation.

import numpy as np
import random
from PIL import Image, ImageDraw, ImageFilter
from itertools import product

def draw_bear(n_bear=1): #Générer de manière aléatoire une image de M. Kuma
    r = g = b = 250
    im = Image.new('RGB', (400, 400), (r, g, b))
    draw = ImageDraw.Draw(im)

    for _ in range(random.randint(-1, 0)):
        r = random.randint(10, 200)
        g = random.randint(10, 200)
        b = random.randint(10, 200)
        x1 = random.randint(0, 400)
        y1 = random.randint(0, 400)
        dx = random.randint(10, 50)
        dy = random.randint(10, 50)
        draw.ellipse((x1, y1, x1+dx, y1+dy), fill=(r, g, b))

    for _ in range(n_bear):
        r = g = b = 1
        center_x = 200
        center_y = 200
        wx = 60
        wy = 50
        dx1 = 90
        dx2 = 20
        dy1 = 90
        dy2 = 20
        dx3 = 15
        dy3 = 100
        dy4 = 60
        shape1 = (center_x - wx, center_y - wy, center_x + wx, center_y + wy)
        shape2 = (center_x - dx1, center_y - dy1, center_x - dx2, center_y - dy2)
        shape3 = (center_x + dx2, center_y - dy1, center_x + dx1, center_y - dy2)
        shape4 = (center_x - dx3, center_y - dy3, center_x + dx3, center_y - dy4)

        zoom = 0.2 + random.random() * 0.4
        center_x = random.randint(-30, 250)
        center_y = random.randint(-30, 250)

        shape1 = modify(shape1, zoom=zoom, center_x=center_x, center_y=center_y)
        shape2= modify(shape2, zoom=zoom, center_x=center_x, center_y=center_y)
        shape3 = modify(shape3, zoom=zoom, center_x=center_x, center_y=center_y)
        shape4 = modify(shape4, zoom=zoom, center_x=center_x, center_y=center_y)

        draw.ellipse(shape1, fill=(r, g, b))
        draw.ellipse(shape2, fill=(r, g, b))
        draw.ellipse(shape3, fill=(r, g, b))
        #draw.ellipse(shape4, fill=(r, g, b))

    return im

def modify(shape, zoom=1, center_x=0, center_y=0):
    x1, y1, x2, y2 = np.array(shape) * zoom
    return (x1 + center_x, y1 + center_y, x2 + center_x, y2 + center_y)

class Noise: #Mettez du bruit sur l'image de l'ours
    def __init__(self, input_image):
        self.input_image = input_image
        self.input_pix = self.input_image.load()
        self.w, self.h = self.input_image.size

    def saltpepper(self, salt=0.05, pepper=0.05):
        output_image = Image.new("RGB", self.input_image.size)
        output_pix = output_image.load()

        for x, y in product(*map(range, (self.w, self.h))):
            r = random.random()
            if r < salt:
                output_pix[x, y] = (255, 255, 255)
            elif r > 1 - pepper:
                output_pix[x, y] = (  0,   0,   0)
            else:
                output_pix[x, y] = self.input_pix[x, y]
        return output_image

##Traitez l'image de Kuma en données d'enseignant pour la segmentation sémantique
def getdata_for_semantic_segmentation(im): 
    x_im = im.filter(ImageFilter.CONTOUR)
    im2 = Noise(input_image=x_im)
    x_im = im2.saltpepper()
    a_im = np.asarray(im)
    y_im = Image.fromarray(np.where(a_im == 1, 255, 0).astype(dtype='uint8'))
    return x_im, y_im

Si vous préparez la fonction ci-dessus, vous pouvez obtenir l'image x_im de M. Kuma et les données de réponse correctes y_im comme suit.

x_im, y_im = getdata_for_semantic_segmentation(draw_bear())

Vérifiez le contenu

x_im

output_4_0.png

y_im

output_5_0.png

Très bien, j'ai confirmé que l'image de M. Kuma avait été générée. C'est un ours, peu importe comment vous le regardez.

Générer 1000 données d'entraînement

Générez 1000 données d'entraînement comme suit.

%%time
X_data = [] #Pour stocker des données d'image
Y_data = [] #Pour stocker des données de réponse correctes
for i in range(1000): #Générer 1000 images
    x_im, y_im = getdata_for_semantic_segmentation(draw_bear())
    X_data.append(x_im) #données d'image
    Y_data.append(y_im) #Corriger les données de réponse
CPU times: user 1min 13s, sys: 865 ms, total: 1min 14s
Wall time: 1min 15s

Juste au cas où, vérifiez uniquement les 8 premières des données générées.

%matplotlib inline
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(10,10))
for i in range(16):
    ax = fig.add_subplot(4, 4, i+1)
    ax.axis('off')
    if i < 8: #Afficher le top 8 des données d'image
        ax.set_title('input_{}'.format(i))
        ax.imshow(X_data[i],cmap=plt.cm.gray, interpolation='none')
    else: #Afficher les 8 meilleures réponses correctes
        ax.set_title('answer_{}'.format(i - 8))
        ax.imshow(Y_data[i - 8],cmap=plt.cm.gray, interpolation='none')
plt.show()

output_7_0.png

Mettez en forme les données résultantes pour PyTorch.

import torch
from torch.utils.data import TensorDataset, DataLoader

#Convertir les données d'image et corriger les données de réponse en ndarray
X_a = np.array([[np.asarray(x).transpose((2, 0, 1))[0]] for x in X_data])
Y_a = np.array([[np.asarray(y).transpose((2, 0, 1))[0]] for y in Y_data])

#Convertir les données d'image ndarray et corriger les données de réponse en tenseur
X_t = torch.tensor(X_a, dtype = torch.float32)               
Y_t = torch.tensor(Y_a, dtype = torch.float32)

#Stocké dans le chargeur de données pour l'apprentissage avec PyTorch
data_set = TensorDataset(X_t, Y_t)
data_loader = DataLoader(data_set, batch_size = 100, shuffle = True)

Réseau d'ours qui reconnaît la silhouette des ours

J'ai utilisé le même réseau que la dernière fois.

from torch import nn, optim
from torch.nn import functional as F
class Kuma(nn.Module):
    def __init__(self):
        super(Kuma, self).__init__()
        #Partie codeur
        self.encode1 = nn.Sequential(
            *[
              nn.Conv2d(
                  in_channels = 1, out_channels = 6, kernel_size = 3, padding = 1),
              nn.BatchNorm2d(6)
              ])
        self.encode2 = nn.Sequential(
            *[
              nn.Conv2d(
                  in_channels = 6, out_channels = 16, kernel_size = 3, padding = 1),
              nn.BatchNorm2d(16)
              ])
        self.encode3 = nn.Sequential(
            *[
              nn.Conv2d(
                  in_channels = 16, out_channels = 32, kernel_size = 3, padding = 1),
              nn.BatchNorm2d(32)
              ])

        #Partie décodeur
        self.decode3 = nn.Sequential(
            *[
              nn.ConvTranspose2d(
                  in_channels = 32, out_channels = 16, kernel_size = 3, padding = 1),
              nn.BatchNorm2d(16)
              ])
        self.decode2 = nn.Sequential(
            *[
              nn.ConvTranspose2d(
                  in_channels = 16, out_channels = 6, kernel_size = 3, padding = 1),
              nn.BatchNorm2d(6)
              ])
        self.decode1 = nn.Sequential(
            *[
              nn.ConvTranspose2d(
                  in_channels = 6, out_channels = 1, kernel_size = 3, padding = 1),
              ])

    def forward(self, x):
        #Partie codeur
        dim_0 = x.size() #Pour restaurer la taille dans la première couche du décodeur
        x = F.relu(self.encode1(x))
        # return_indices =Réglez sur True et max dans le décodeur_Utiliser la position du pool idx
        x, idx_1 = F.max_pool2d(x, kernel_size = 2, stride = 2, return_indices = True)
        dim_1 = x.size() #Pour restaurer la taille dans la deuxième couche du décodeur
        x = F.relu(self.encode2(x))
        # return_indices =Réglez sur True et max dans le décodeur_Utiliser la position du pool idx
        x, idx_2 = F.max_pool2d(x, kernel_size = 2, stride = 2, return_indices = True)            
        dim_2 = x.size()
        x = F.relu(self.encode3(x)) #Pour restaurer la taille dans la troisième couche du décodeur
        # return_indices =Réglez sur True et max dans le décodeur_Utiliser la position du pool idx
        x, idx_3 = F.max_pool2d(x, kernel_size = 2, stride = 2, return_indices = True)

        #Partie décodeur
        x = F.max_unpool2d(x, idx_3, kernel_size = 2, stride = 2, output_size = dim_2)
        x = F.relu(self.decode3(x))
        x = F.max_unpool2d(x, idx_2, kernel_size = 2, stride = 2, output_size = dim_1)           
        x = F.relu(self.decode2(x))                           
        x = F.max_unpool2d(x, idx_1, kernel_size = 2, stride = 2, output_size = dim_0)           
        x = F.relu(self.decode1(x))                           
        x = torch.sigmoid(x)                                     

        return x

Commencer à apprendre

Tout d'abord, apprenez seulement 50 époques.

%%time

kuma = Kuma()
loss_fn = nn.MSELoss()                               
optimizer = optim.Adam(kuma.parameters(), lr = 0.01)

total_loss_history = []                                     
epoch_time = 50
for epoch in range(epoch_time):
    !date
    total_loss = 0.0                          
    kuma.train()
    for i, (XX, yy) in enumerate(data_loader):
        optimizer.zero_grad()       
        y_pred = kuma(XX)
        loss = loss_fn(y_pred, yy)
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
    print("epoch:",epoch, " loss:", total_loss/(i + 1))
    total_loss_history.append(total_loss/(i + 1))

plt.plot(total_loss_history)
plt.ylabel("loss")
plt.xlabel("epoch time")
plt.savefig("total_loss_history")
plt.show()
Tue Feb 25 12:07:52 UTC 2020
epoch: 0  loss: 1202.7168701171875
Tue Feb 25 12:10:12 UTC 2020
epoch: 1  loss: 1200.6845336914062
Tue Feb 25 12:12:28 UTC 2020
...
Tue Feb 25 13:53:40 UTC 2020
epoch: 47  loss: 1199.1316650390625
Tue Feb 25 13:55:54 UTC 2020
epoch: 48  loss: 1199.1294555664062
Tue Feb 25 13:58:08 UTC 2020
epoch: 49  loss: 1199.133544921875

output_10_1.png

CPU times: user 1h 36min 47s, sys: 2min 23s, total: 1h 39min 11s
Wall time: 1h 52min 30s

On dirait qu'il a convergé.

Vérification

Générez 100 nouvelles données qui n'ont pas été utilisées pour la formation et vérifiez.

X_test = [] #Stocker les données d'image pour les tests
Y_test = [] #Stocke les données de réponse correctes pour les tests
Z_test = [] #Stocker les résultats de prédiction pour les tests

for i in range(100): #Générer 100 nouvelles données non utilisées pour la formation
    x_im, y_im = getdata_for_semantic_segmentation(draw_bear())
    X_test.append(x_im)
    Y_test.append(y_im)

#Formater les données d'image de test pour PyTorch
X_test_a = np.array([[np.asarray(x).transpose((2, 0, 1))[0]] for x in X_test])
X_test_t = torch.tensor(X_test_a, dtype = torch.float32)

#Calculer des prédictions à l'aide d'un modèle entraîné
Y_pred = kuma(X_test_t)

#Stocker les valeurs prévues sous forme de ndarray
for pred in Y_pred:
    Z_test.append(pred.detach().numpy())

Je n'illustrerai que les 10 premières données.

#Dessinez des données d'image, corrigez les données de réponse et les valeurs prévues pour les 10 premiers éléments de données
fig = plt.figure(figsize=(12,36))
for i in range(10):
    ax = fig.add_subplot(10, 3, (i * 3)+1)
    ax.axis('off')
    ax.set_title('input_{}'.format(i))
    ax.imshow(X_test[i])
    ax = fig.add_subplot(10, 3, (i * 3)+2)
    ax.axis('off')
    ax.set_title('answer_{}'.format(i))
    ax.imshow(Y_test[i])
    ax = fig.add_subplot(10, 3, (i * 3)+3)
    ax.axis('off')
    ax.set_title('predicted_{}'.format(i))
    yp2 = Y_pred[i].detach().numpy()[0] * 255
    z_im = Image.fromarray(np.array([yp2, yp2, yp2]).transpose((1, 2, 0)).astype(dtype='uint8'))
    ax.imshow(z_im)
plt.show()

output_13_0.png

Il y a beaucoup de bruit, mais cela semble prévisible dans une certaine mesure.

La taille de l'ours

Examinons la relation entre la taille réelle de l'ours et la taille de l'ours détecté (prédit).

A_ans = []
A_pred = []
for yt, zt in zip(Y_test, Z_test):
    #Zone blanche correcte (puisqu'il y a 3 vecteurs, divisez par 3)
    A_ans.append(np.where(np.asarray(yt) > 0.5, 1, 0).sum() / 3) 
    A_pred.append(np.where(np.asarray(zt) > 0.5, 1, 0).sum()) #Zone blanche prédite

plt.figure(figsize=(4, 4))
plt.scatter(A_ans, A_pred, alpha=0.5)
plt.grid()
plt.xlabel('Observed sizes')
plt.ylabel('Predicted sizes')
#plt.xlim([0, 1700])
#plt.ylim([0, 1700])
plt.show()

output_14_0.png

La précision n'est pas bonne, mais il existe une relation linéaire vague.

Enregistrer le modèle entraîné

Vous pouvez enregistrer le modèle entraîné et l'utiliser à nouveau à tout moment comme suit.

torch.save(kuma.state_dict(), "kuma_050_20200225.pytorch")

50 époques supplémentaires

D'une manière ou d'une autre, j'ai essayé d'apprendre 50 époques supplémentaires. Code omis.

output_16_1.png

Je me demande si l'erreur a un peu diminué (mais ça ne change pas beaucoup)

output_18_0.png

output_19_0.png

Il ne semble y avoir aucune amélioration majeure.

torch.save(kuma.state_dict(), "kuma_100_20200225.pytorch")

Pour le moment, j'ai enregistré le modèle entraîné comme ci-dessus.

Détectez plusieurs ours

Il est enfin temps de passer en direct. Jusqu'à présent, nous avons appris des «images contenant un seul ours </ b>» et détecté des ours à partir «d'images contenant un seul ours </ b>». J'avais l'habitude d'apprendre "des images contenant un seul ours </ b>" et des "images contenant plusieurs </ b> ours" Je voudrais détecter M. Kuma.

Chargement du modèle entraîné

Chargez le modèle entraîné que vous avez enregistré précédemment.

kuma = Kuma()
kuma.load_state_dict(torch.load("kuma_100_20200225.pytorch"))
<All keys matched successfully>

Image contenant plusieurs ours

Génère 100 images contenant plusieurs ours. Cela génère également une image décevante qui ne montre pas l'ours.

X_test = [] #Stocker les données d'image pour les tests
Y_test = [] #Stocke les données de réponse correctes pour les tests
Z_test = [] #Stocker les résultats de prédiction pour les tests

for i in range(100): #Générer 100 nouvelles données non utilisées pour la formation
    x_im, y_im = getdata_for_semantic_segmentation(draw_bear(n_bear=random.randint(0, 5)))
    X_test.append(x_im)
    Y_test.append(y_im)

#Formater les données d'image de test pour PyTorch
X_test_a = np.array([[np.asarray(x).transpose((2, 0, 1))[0]] for x in X_test])
X_test_t = torch.tensor(X_test_a, dtype = torch.float32)

Lancer la prévision

Comme il s'agit d'un modèle entraîné, la prédiction est instantanée.

#Calculer des prédictions à l'aide d'un modèle entraîné
Y_pred = kuma(X_test_t)

#Stocker les valeurs prévues sous forme de ndarray
for pred in Y_pred:
    Z_test.append(pred.detach().numpy())

#Dessinez des données d'image, corrigez les données de réponse et les valeurs prévues pour les 10 premiers éléments de données
fig = plt.figure(figsize=(12,36))
for i in range(10):
    ax = fig.add_subplot(10, 3, (i * 3)+1)
    ax.axis('off')
    ax.set_title('input_{}'.format(i))
    ax.imshow(X_test[i])
    ax = fig.add_subplot(10, 3, (i * 3)+2)
    ax.axis('off')
    ax.set_title('answer_{}'.format(i))
    ax.imshow(Y_test[i])
    ax = fig.add_subplot(10, 3, (i * 3)+3)
    ax.axis('off')
    ax.set_title('predicted_{}'.format(i))
    yp2 = Y_pred[i].detach().numpy()[0] * 255
    z_im = Image.fromarray(np.array([yp2, yp2, yp2]).transpose((1, 2, 0)).astype(dtype='uint8'))
    ax.imshow(z_im)
plt.show()

output_10_0.png

C'est assez bruyant, mais j'ai pu faire une prédiction approximative.

Voyons la relation entre la taille réelle de M. Kuma et la taille prévue.

A_ans = []
A_pred = []
for yt, zt in zip(Y_test, Z_test):
    #Zone blanche correcte (puisqu'il y a 3 vecteurs, divisez par 3)
    A_ans.append(np.where(np.asarray(yt) > 0.5, 1, 0).sum() / 3) 
    A_pred.append(np.where(np.asarray(zt) > 0.5, 1, 0).sum()) #Zone blanche prédite

plt.figure(figsize=(4, 4))
plt.scatter(A_ans, A_pred, alpha=0.5)
plt.grid()
plt.xlabel('Observed sizes')
plt.ylabel('Predicted sizes')
#plt.xlim([0, 1700])
#plt.ylim([0, 1700])
plt.show()

output_11_0.png

Il existe une relation linéaire assez claire, donc si vous voulez juste connaître la taille, il semble que vous puissiez corriger cette relation.

Réseau plus profond

J'ai ajouté une autre couche pour créer un réseau plus profond.

import torch
from torch import nn, optim
from torch.nn import functional as F
class Kuma(nn.Module):
    def __init__(self):
        super(Kuma, self).__init__()
        #Partie codeur
        self.encode1 = nn.Sequential(
            *[
              nn.Conv2d(
                  in_channels = 1, out_channels = 6, kernel_size = 3, padding = 1),
              nn.BatchNorm2d(6)
              ])
        self.encode2 = nn.Sequential(
            *[
              nn.Conv2d(
                  in_channels = 6, out_channels = 16, kernel_size = 3, padding = 1),
              nn.BatchNorm2d(16)
              ])
        self.encode3 = nn.Sequential(
            *[
              nn.Conv2d(
                  in_channels = 16, out_channels = 32, kernel_size = 3, padding = 1),
              nn.BatchNorm2d(32)
              ])
        
        self.encode4 = nn.Sequential(
            *[
              nn.Conv2d(
                  in_channels = 32, out_channels = 64, kernel_size = 3, padding = 1),
              nn.BatchNorm2d(64)
              ])

        #Partie décodeur
        self.decode4 = nn.Sequential(
            *[
              nn.ConvTranspose2d(
                  in_channels = 64, out_channels = 32, kernel_size = 3, padding = 1),
              nn.BatchNorm2d(32)
              ])
        self.decode3 = nn.Sequential(
            *[
              nn.ConvTranspose2d(
                  in_channels = 32, out_channels = 16, kernel_size = 3, padding = 1),
              nn.BatchNorm2d(16)
              ])
        self.decode2 = nn.Sequential(
            *[
              nn.ConvTranspose2d(
                  in_channels = 16, out_channels = 6, kernel_size = 3, padding = 1),
              nn.BatchNorm2d(6)
              ])
        self.decode1 = nn.Sequential(
            *[
              nn.ConvTranspose2d(
                  in_channels = 6, out_channels = 1, kernel_size = 3, padding = 1),
              ])

    def forward(self, x):
        #Partie codeur
        dim_0 = x.size()      
        x = F.relu(self.encode1(x))
        x, idx_1 = F.max_pool2d(x, kernel_size = 2, stride = 2, return_indices = True)

        dim_1 = x.size() 
        x = F.relu(self.encode2(x))
        x, idx_2 = F.max_pool2d(x, kernel_size = 2, stride = 2, return_indices = True)

        dim_2 = x.size()
        x = F.relu(self.encode3(x)) 
        x, idx_3 = F.max_pool2d(x, kernel_size = 2, stride = 2, return_indices = True)

        dim_3 = x.size()
        x = F.relu(self.encode4(x)) 
        x, idx_4 = F.max_pool2d(x, kernel_size = 2, stride = 2, return_indices = True)

        #Partie décodeur
        x = F.max_unpool2d(x, idx_4, kernel_size = 2, stride = 2, output_size = dim_3)
        x = F.relu(self.decode4(x))

        x = F.max_unpool2d(x, idx_3, kernel_size = 2, stride = 2, output_size = dim_2)
        x = F.relu(self.decode3(x))

        x = F.max_unpool2d(x, idx_2, kernel_size = 2, stride = 2, output_size = dim_1)           
        x = F.relu(self.decode2(x))

        x = F.max_unpool2d(x, idx_1, kernel_size = 2, stride = 2, output_size = dim_0)           
        x = F.relu(self.decode1(x))

        x = torch.sigmoid(x)                                     

        return x

Le résultat est le suivant. Le code est le même que celui décrit ci-dessus (seul le nom du fichier à enregistrer est différent)

Courbe d'apprentissage

output_6_1.png

Exemple de résultat de prédiction (1 ours)

output_8_0.png

Taille de l'ours

output_9_0.png

Exemple de résultat de prédiction (beaucoup d'ours)

output_13_0.png

Taille de l'ours

output_14_0.png

Ajoutez simplement une couche et c'est assez propre!