[PYTHON] Créez une application qui reconnaît les images en écrivant des chiffres à l'écran sur Android (PyTorch Mobile) [création de réseau CNN]

Application à créer cette fois

Créez une application de reconnaissance d'image qui reconnaît les nombres écrits sur l'écran avec Pytorch Mobile et kotlin. ** Créez toutes les fonctions du modèle et d'Android pour la reconnaissance d'image à partir de zéro. ** ** Il sera divisé en deux parties, ** CNN Network Creation (Python) ** et ** Android Implementation (kotlin) **.

Si vous êtes un ingénieur Android qui ne possède pas d'environnement Python, ou si vous rencontrez des difficultés pour créer un modèle, [Créez une application de reconnaissance d'image qui discrimine les nombres écrits à l'écran avec android (PyTorch Mobile) [implémentation Android]](https: // qiita. Veuillez aller à com / YS-BETA / items / 15a4a2c64360f91f8b3a) et télécharger le modèle formé dans la section de mise en œuvre pour continuer.

J'ai répertorié ce code python sur Github Github: https://github.com/SY-BETA/CNN_PyTorch

Ce ↓

Flux de création

  1. Téléchargez MNIST (* Il est nécessaire de changer le nombre de canaux à 3 canaux)
  2. Créez un modèle CNN simple avec python (PyTorch)
  3. Former le modèle
  4. Enregistrez le modèle
  5. Implémentation de la possibilité de dessiner des images sur Android
  6. Implémentez le modèle sur Android pour la propagation vers l'avant

Que faire à ce moment

Faites 1 ~ 4. Enregistrez même le modèle en utilisant python. La bibliothèque utilisée cette fois est PyTorch L'environnement d'exécution est jupyter notebook Téléchargez l'ensemble de données MNIST pour créer et entraîner un modèle CNN simple.

Télécharger MNIST

Téléchargez l'ensemble de données numériques manuscrites MNIST que tout le monde aime utiliser torchvision

import torch
import torchvision
import torchvision.transforms as transforms

transform = transforms.Compose([
        transforms.ToTensor()])
train = torchvision.datasets.MNIST(
    root="data/train", train=True, transform=transform, target_transform=None, download=True)
test = torchvision.datasets.MNIST(
    root="data/test", train=False, transform=transform, target_transform=None, download=True)

Jetez un œil à MNIST

Voyons quel type d'ensemble de données

from matplotlib import pyplot as plt
import numpy as np

print(train.data.size())
print(test.data.size())
img = train.data[0].numpy()
plt.imshow(img, cmap='gray')
print('Label:', train.targets[0])

** Résultat d'exécution ** キャプチaaaaャ.PNG

Passer de l'échelle de gris à RVB

Changez le nombre de canaux de couleur de MNIST de 1 à 3.

** Pourquoi vous souciez-vous de gaspiller une telle augmentation du montant du calcul? ** -> Lorsque vous traitez avec des images sur Android, manipulez-les au format bitmap, lors de la conversion en tenseur avec pytorch mobile ** Ne peut être converti qu'en tenseur avec 3 canaux **. (La conversion de l'échelle de gris est-elle ajoutée à l'avenir ou est-ce une telle spécification ...) Entraînons donc le modèle en le convertissant en RVB.

** Non limité à ce moment, le modèle utilisé dans PyTorch Mobile doit être un modèle avec 3 canaux de couleur. ** **

train_data_resized = train.data.numpy()  #du tenseur de la torche au numpy
test_data_resized = test.data.numpy()

train_data_resized = torch.FloatTensor(np.stack((train_data_resized,)*3, axis=1))  #Convertir en RVB
test_data_resized =  torch.FloatTensor(np.stack((test_data_resized,)*3, axis=1))
print(train_data_resized.size())

La taille du jeu de données est maintenant passée de torch.Size ([60000, 28, 28]) à torch.Size ([60000, 3, 28, 28]).

Créez votre propre jeu de données

Créer une classe d'ensemble de données personnalisée

Cette fois, le jeu de données MNIST ne peut pas être utilisé car cela est dû au nombre de canaux, donc créez un jeu de données personnalisé en héritant du Dataset de pytorch. De plus, une classe de normalisation, qui est un prétraitement d'image, est également créée ici.

import torch.utils.data as data

mean = (0.485, 0.456, 0.406)
std = (0.229, 0.224, 0.225)

#Prétraitement d'image
class ImgTransform():
    def __init__(self):
        self.transform = transforms.Compose([
            transforms.ToTensor(),  #Conversion de Tensol
            transforms.Normalize(mean, std)  #Standardisation
        ])

    def __call__(self, img):
        return self.transform(img)

#Hériter de la classe Dataset
class _3ChannelMnistDataset(data.Dataset):
    def __init__(self, img_data, target, transform):
        #[Le nombre de données,la taille,côté,Nombre de canaux]À
        self.data = img_data.numpy().transpose((0, 2, 3, 1)) /255
        self.target = target
        self.img_transform = transform #Instance de la classe de prétraitement d'image

    def __len__(self):
        #Renvoie le nombre d'images
        return len(self.data)

    def __getitem__(self, index):
        #Prétraitement d'image(Standardisation)Renvoie les données
        img_transformed = self.img_transform(self.data[index])
        return img_transformed, self.target[index]

Notez que «mean» et «std» sont les valeurs habituelles qui sont souvent utilisées pour la normalisation, comme VGG16. C'est la valeur à ce moment-là qui est toujours normalisée lors de la conversion en tenseur sur Android. Si vous ne connaissez pas la valeur, vous pouvez vérifier ʻImageUtils` de pytroch mobile dans Android Studio. aaaaキャプチャ.PNG

Créer un ensemble de données à l'aide de la classe créée ci-dessus

train_dataset = _3ChannelMnistDataset(train_data_resized, train.targets, transform=ImgTransform())
test_dataset = _3ChannelMnistDataset(test_data_resized, test.targets, transform=ImgTransform())

#Essayez de tester l'ensemble de données
index = 0
print(train_dataset.__getitem__(index)[0].size())
print(train_dataset.__getitem__(index)[1])
print(train_dataset.__getitem__(index)[0][1]) #Vous pouvez voir qu'il est correctement standardisé

Création du chargeur de données

Créez un chargeur de données personnalisé avec l'ensemble de données créé. La taille du lot est de 100

train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=100, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=100, shuffle=False)

Créer un réseau CNN

Créez un réseau simple avec 1 couche de convolution et 3 couches entièrement connectées. (Je déteste prendre le temps d'apprendre)

from torch import nn
class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.relu = nn.ReLU()
        self.pool = nn.MaxPool2d(3)
        self.conv = nn.Conv2d(3, 10, kernel_size=4)
        self.fc1 = nn.Linear(640, 300)
        self.fc2 = nn.Linear(300, 100)
        self.fc3 = nn.Linear(100, 10)

    def forward(self, x):
        x = self.conv(x)
        x = self.relu(x)
        x = self.pool(x)
        x = x.view(x.size()[0], -1) #Vectorisé pour le traitement linéaire de la matrice(view(Hauteur largeur))
        x = self.fc1(x)
        x = self.relu(x)
        x = self.fc2(x)
        x = self.relu(x)
        x = self.fc3(x)
        return x

model = Model()
print(model)

Un tel réseau キャプfadsfcvaチャ.PNG

Former le réseau

Créer des fonctions de mode d'entraînement et de mode d'inférence

import tqdm
from torch import optim

#Mode d'inférence
def eval_net(net, data_loader, device="cpu"): #Si vous avez un GPU, allez sur gpu
    #En mode inférence
    net.eval()
    ypreds = [] #Variable de stockage d'étiquette prédite
    for x, y in (data_loader):
        #Transférer vers l'appareil avec la méthode
        x = x.to(device)
        y = [y.to(device)]
        #Prédire la classe avec la probabilité la plus élevée
        #propagation vers l'avant
        with torch.no_grad():
            _, y_pred = net(x).max(1)
            ypreds.append(y_pred)
            #Prédiction pour chaque mini-lot en un tenseur
            y = torch.cat(y)
            ypreds = torch.cat(ypreds)
            #Calculer la valeur prévue(Bonne réponse = somme des éléments prédictifs)
            acc = (y == ypreds).float().sum()/len(y)
            return acc.item()


#Mode entraînement
def train_net(net, train_loader, test_loader,optimizer_cls=optim.Adam, 
              loss_fn=nn.CrossEntropyLoss(),n_iter=3, device="cpu"):
    train_losses = []
    train_acc = []
    eval_acc = []
    optimizer = optimizer_cls(net.parameters())
    for epoch in range(n_iter):  #Tourner 4 fois
        runnig_loss = 0.0
        #En mode entraînement
        net.train()
        n = 0
        n_acc = 0
    
        for i, (xx, yy) in tqdm.tqdm(enumerate(train_loader),
                                     total=len(train_loader)):
            xx = xx.to(device)
            yy = yy.to(device)
            output = net(xx)
            
            loss = loss_fn(output, yy)
            optimizer.zero_grad()   #Initialiser l'optimiseur
            loss.backward()   #Fonction de perte(Erreur d'entropie croisée)De la propagation arrière
            optimizer.step()
            
            runnig_loss += loss.item()
            n += len(xx)
            _, y_pred = output.max(1)
            n_acc += (yy == y_pred).float().sum().item()
            
        train_losses.append(runnig_loss/i)
        #Précision des prévisions des données d'entraînement
        train_acc.append(n_acc / n)
        #Précision des prévisions des données de vérification
        eval_acc.append(eval_net(net, test_loader, device))

        #Voir les résultats avec cette époque
        print("epoch:",epoch, "train_loss:",train_losses[-1], "train_acc:",train_acc[-1],
              "eval_acc:",eval_acc[-1], flush=True)

Essayez d'abord de déduire sans apprendre

eval_net(model, test_loader)

Étant donné que la valeur de départ du paramètre aléatoire du réseau n'est pas fixe, elle n'est pas reproductible et change de manière aléatoire, mais dans mon environnement, le score avant l'apprentissage était de «0,0799999982».

Apprendre

Apprentissage à l'aide de la fonction créée précédemment

train_net(model, train_loader, test_loader)

Enfin, la précision de la prédiction est devenue d'environ "0,98000001907". Eh bien, la précision est trop élevée. Je m'inquiète si c'est trop précis ...

Je vais en inférer un

Mettez une donnée dans le modèle entraîné et essayez de prédire l'étiquette.

data = train_dataset.__getitem__(0)[0].reshape(1, 3, 28, 28) #Redimensionner (notez la taille du chargeur de données)
print("étiquette",train_dataset.__getitem__(0)[1].data)
model.eval()
output = model(data)
print(output.size())
output

** Résultat d'exécution ** キafdfafdaャプチャ.PNG Vous pouvez voir que le score avec un indice de 5 est le plus élevé et peut être prédit.

Enfin, la création et la formation du modèle sont terminées! !!

Enregistrer le modèle

Enregistrer le modèle pour une utilisation sur Android

#Enregistrer le modèle
model.eval()
#Taille d'entrée d'échantillon
example = torch.rand(1, 3, 28, 28)
traced_script_module = torch.jit.trace(model, example)
traced_script_module.save("./CNNModel.pt")
print(model)

fin

Pour l'instant, c'est la fin de [Création de réseau] !! Ensuite, nous allons implémenter le modèle créé sur Android. Lorsque je l'ai converti en tenseur avec PyTorch Mobile, il est devenu un tenseur RVB, et je ne pouvais pas le faire en niveaux de gris, j'ai donc dû me donner la peine de convertir MNIST en RVB, ce qui était beaucoup de traitement gênant. En conséquence, je ne pouvais pas utiliser le jeu de données MNIST tel quel, et je devais utiliser mon propre jeu de données et mon chargeur de données. Eh bien, je pense qu'il peut difficilement être utilisé à l'échelle de gris ou au niveau commercial. De plus, bien qu'il s'agisse d'un réseau CNN correctement conçu, j'ai été surpris que la précision soit étonnamment élevée, comme prévu CNN Je vais vous donner Github pour le moment.

Ce code Github: https://github.com/SY-BETA/CNN_PyTorch

Modèle formé créé cette fois (.py): https://github.com/SY-BETA/CNN_PyTorch/blob/master/CNNModel.pt

Passons à l'implémentation Android Créez une application de reconnaissance d'image qui discrimine les nombres écrits à l'écran avec Android (PyTorch Mobile) [Implémentation Android]

Recommended Posts

Créez une application qui reconnaît les images en écrivant des chiffres à l'écran sur Android (PyTorch Mobile) [création de réseau CNN]
Créez une application de reconnaissance d'image qui discrimine les nombres écrits à l'écran avec Android (PyTorch Mobile) [implémentation Android]
Créez une application Web qui reconnaît les nombres avec un réseau neuronal
[kotlin] Créez une application qui reconnaît les photos prises avec un appareil photo sur Android
À propos de l'itinéraire le plus court pour créer un modèle de reconnaissance d'image par apprentissage automatique et mettre en œuvre une application Android