[PYTHON] Kumantisches Segumantion

"Kumantic Segumantion", um Informationen über Kuma aus dem Bild von Kuma zu erhalten. Dies ist eine Fortsetzung von Kumantic Segmentation 2.

Unterschied zum letzten Mal

Letztes Mal wollte ich " mehrere </ b> Fußabdrücke von Bären erkennen", indem ich "ein Bild von mehreren </ b> Fußabdrücken von Bären" zum Lernen verwendete. , Funktioniert nicht).

Dieses Mal möchten wir "mehrere </ b> Bärensilhouetten erkennen", indem wir "ein Bild verwenden, das nur eine </ b> Bärensilhouette zeigt", um zu lernen.

Erzeugt automatisch ein Bild eines Bären

Wird zum Erstellen von Datensätzen für Training und Validierung verwendet.

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

def draw_bear(n_bear=1): #Generieren Sie zufällig ein Bild von Herrn 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: #Setzen Sie Rauschen auf das Bild des Bären
    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

##Verarbeiten Sie Kumas Bild in Lehrerdaten für die semantische Segmentierung
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

Wenn Sie die obige Funktion vorbereiten, können Sie das Bild "x_im" von Herrn Kuma und die richtigen Antwortdaten "y_im" wie folgt erhalten.

x_im, y_im = getdata_for_semantic_segmentation(draw_bear())

Überprüfen Sie den Inhalt

x_im

output_4_0.png

y_im

output_5_0.png

Okay, ich habe bestätigt, dass das Bild von Herrn Kuma erzeugt wurde. Dies ist ein Bär, egal wie du es betrachtest.

Generieren Sie 1000 Trainingsdaten

Generieren Sie 1000 Trainingsdaten wie folgt.

%%time
X_data = [] #Zum Speichern von Bilddaten
Y_data = [] #Zum Speichern korrekter Antwortdaten
for i in range(1000): #Generieren Sie 1000 Bilder
    x_im, y_im = getdata_for_semantic_segmentation(draw_bear())
    X_data.append(x_im) #Bilddaten
    Y_data.append(y_im) #Richtige Antwortdaten
CPU times: user 1min 13s, sys: 865 ms, total: 1min 14s
Wall time: 1min 15s

Überprüfen Sie für alle Fälle nur die ersten 8 der generierten Daten.

%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: #Zeigen Sie die Top 8 der Bilddaten an
        ax.set_title('input_{}'.format(i))
        ax.imshow(X_data[i],cmap=plt.cm.gray, interpolation='none')
    else: #Zeigen Sie die Top 8 der richtigen Antwortdaten an
        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

Formatieren Sie die resultierenden Daten für PyTorch.

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

#Konvertieren Sie Bilddaten und korrigieren Sie die Antwortdaten in 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])

#Konvertieren Sie ndarray-Bilddaten und korrigieren Sie die Antwortdaten in Tensor
X_t = torch.tensor(X_a, dtype = torch.float32)               
Y_t = torch.tensor(Y_a, dtype = torch.float32)

#Im Data Loader zum Lernen mit PyTorch gespeichert
data_set = TensorDataset(X_t, Y_t)
data_loader = DataLoader(data_set, batch_size = 100, shuffle = True)

Bärennetzwerk, das die Silhouette von Bären erkennt

Ich habe das gleiche Netzwerk wie beim letzten Mal verwendet.

from torch import nn, optim
from torch.nn import functional as F
class Kuma(nn.Module):
    def __init__(self):
        super(Kuma, self).__init__()
        #Encoderteil
        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)
              ])

        #Decoderteil
        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):
        #Encoderteil
        dim_0 = x.size() #Zum Wiederherstellen der Größe in der ersten Schicht des Decoders
        x = F.relu(self.encode1(x))
        # return_indices =Stellen Sie im Decoder True und Max ein_Verwenden Sie die Poolposition idx
        x, idx_1 = F.max_pool2d(x, kernel_size = 2, stride = 2, return_indices = True)
        dim_1 = x.size() #Zum Wiederherstellen der Größe in der zweiten Schicht des Decoders
        x = F.relu(self.encode2(x))
        # return_indices =Stellen Sie im Decoder True und Max ein_Verwenden Sie die Poolposition 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)) #Zum Wiederherstellen der Größe in der dritten Schicht des Decoders
        # return_indices =Stellen Sie im Decoder True und Max ein_Verwenden Sie die Poolposition idx
        x, idx_3 = F.max_pool2d(x, kernel_size = 2, stride = 2, return_indices = True)

        #Decoderteil
        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

Fang an zu lernen

Lerne zuerst nur 50 Epochen.

%%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

Es sieht so aus, als wäre es konvergiert.

Überprüfung

Generieren Sie 100 neue Daten, die nicht für das Training verwendet wurden, und überprüfen Sie sie.

X_test = [] #Speichern Sie Bilddaten zum Testen
Y_test = [] #Speichert korrekte Antwortdaten zum Testen
Z_test = [] #Speichern Sie Vorhersageergebnisse zum Testen

for i in range(100): #Generieren Sie 100 neue Daten, die nicht für das Training verwendet werden
    x_im, y_im = getdata_for_semantic_segmentation(draw_bear())
    X_test.append(x_im)
    Y_test.append(y_im)

#Formatieren Sie die Testbilddaten für 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)

#Berechnen Sie Vorhersagen mit einem trainierten Modell
Y_pred = kuma(X_test_t)

#Speichern Sie die vorhergesagten Werte als ndarray
for pred in Y_pred:
    Z_test.append(pred.detach().numpy())

Ich werde nur die ersten 10 Daten veranschaulichen.

#Zeichnen Sie Bilddaten, korrekte Antwortdaten und vorhergesagte Werte für die ersten 10 Daten
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

Es gibt viel Lärm, aber es scheint bis zu einem gewissen Grad vorhersehbar zu sein.

Die Größe des Bären

Schauen wir uns die Beziehung zwischen der tatsächlichen Größe des Bären und der Größe des erkannten (vorhergesagten) Bären an.

A_ans = []
A_pred = []
for yt, zt in zip(Y_test, Z_test):
    #Korrekter weißer Bereich (Da 3 Vektoren vorhanden sind, durch 3 teilen)
    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()) #Voraussichtlicher weißer Bereich

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

Die Genauigkeit ist nicht gut, aber es gibt eine vage lineare Beziehung.

Speichern Sie das trainierte Modell

Sie können das trainierte Modell wie folgt speichern und jederzeit wieder verwenden.

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

Weitere 50 Epochen

Irgendwie habe ich versucht, weitere 50 Epochen zu lernen. Code weggelassen.

output_16_1.png

Ich frage mich, ob der Fehler etwas abgenommen hat (aber er ändert sich nicht viel).

output_18_0.png

output_19_0.png

Es scheint keine wesentliche Verbesserung zu geben.

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

Vorerst habe ich das trainierte Modell wie oben gespeichert.

Erkennen Sie mehrere Bären

Jetzt ist es endlich Zeit, live zu gehen. Bisher haben wir "Bilder mit nur einem Bären </ b>" gelernt und Bären aus "Bildern mit nur einem Bären </ b>" erkannt. Ich habe es getan, aber von nun an werde ich "Bilder mit nur einem Bären </ b>" und aus "Bildern mit mehreren </ b> Bären" lernen. Ich möchte Herrn Kuma entdecken.

Geladenes trainiertes Modell laden

Laden Sie das trainierte Modell, das Sie zuvor gespeichert haben.

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

Bild mit mehreren Bären

Erzeugt 100 Bilder mit mehreren Bären. Es erzeugt auch ein enttäuschendes Bild, das den Bären nicht zeigt.

X_test = [] #Speichern Sie Bilddaten zum Testen
Y_test = [] #Speichert korrekte Antwortdaten zum Testen
Z_test = [] #Speichern Sie Vorhersageergebnisse zum Testen

for i in range(100): #Generieren Sie 100 neue Daten, die nicht für das Training verwendet werden
    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)

#Formatieren Sie die Testbilddaten für 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)

Prognose starten

Da es sich um ein trainiertes Modell handelt, erfolgt die Vorhersage sofort.

#Berechnen Sie Vorhersagen mit einem trainierten Modell
Y_pred = kuma(X_test_t)

#Speichern Sie die vorhergesagten Werte als ndarray
for pred in Y_pred:
    Z_test.append(pred.detach().numpy())

#Zeichnen Sie Bilddaten, korrekte Antwortdaten und vorhergesagte Werte für die ersten 10 Daten
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

Es ist ziemlich laut, aber ich konnte eine grobe Vorhersage machen.

Lassen Sie uns die Beziehung zwischen der tatsächlichen Größe von Herrn Kuma und der vorhergesagten Größe sehen.

A_ans = []
A_pred = []
for yt, zt in zip(Y_test, Z_test):
    #Korrekter weißer Bereich (Da 3 Vektoren vorhanden sind, durch 3 teilen)
    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()) #Voraussichtlicher weißer Bereich

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

Es gibt eine ziemlich saubere lineare Beziehung. Wenn Sie also nur die Größe wissen möchten, können Sie diese Beziehung anscheinend korrigieren.

Tieferes Netzwerk

Ich habe eine weitere Ebene hinzugefügt, um ein tieferes Netzwerk zu erstellen.

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__()
        #Encoderteil
        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)
              ])

        #Decoderteil
        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):
        #Encoderteil
        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)

        #Decoderteil
        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

Das Ergebnis ist wie folgt. Der Code ist der gleiche wie der oben beschriebene (nur der zu speichernde Dateiname ist unterschiedlich)

Lernkurve

output_6_1.png

Beispiel für ein Vorhersageergebnis (1 Bär)

output_8_0.png

Bärengröße

output_9_0.png

Beispiel für Vorhersageergebnisse (viele Bären)

output_13_0.png

Bärengröße

output_14_0.png

Fügen Sie einfach eine Schicht hinzu und es ist ziemlich sauber!