[PYTHON] J'ai essayé de faire la reconnaissance de caractères manuscrits de Kana Partie 2/3 Création et apprentissage de données

Aperçu

Dernière fois (1/3): https://qiita.com/tfull_tf/items/6015bee4af7d48176736 Prochaine fois (3/3): https://qiita.com/tfull_tf/items/d9fe3ab6c1e47d1b2e1e

Code: https://github.com/tfull/character_recognition

Lors de la création du système de reconnaissance des kana, nous avons d'abord construit un modèle à l'aide de CNN et confirmé sa précision avec MNIST. Ensuite, préparez les données d'image de Kana, construisez le modèle de la même manière et améliorez le modèle.

Créer des données

Je ne peux penser à aucune donnée d'image pour Kana, donc je vais la créer automatiquement. Pour le moment, il semble que certains ensembles de données soient ouverts au public.

Utilisez ImageMagick pour la génération automatique. Vous pouvez ajouter du texte à l'image avec la commande de conversion. Créez d'abord une image noire, puis écrivez un seul texte blanc dessus.

Méthode de prolifération des données

Afin de préparer plusieurs images pour un personnage, nous avons préparé une méthode pour augmenter les données.

1: utiliser plusieurs polices

Si vous écrivez dans des polices différentes, vous pouvez générer différentes images avec les mêmes caractères en fonction du nombre de types de polices.

Vous pouvez voir la police avec la commande suivante, alors choisissez celle qui semble être utilisable.

convert -list font

Une chose à garder à l'esprit est que tous ne supportent pas le japonais, donc même si vous essayez de sortir kana, rien ne peut être écrit.

Mac OS 10.15, sur lequel je travaillais principalement, n'avait pas une police qui avait l'air bien, j'ai donc généré l'image sur Ubuntu. Les polices suivantes ont été incluses depuis le début, j'ai donc décidé de les utiliser.

font_list = [
    "Noto-Sans-CJK-JP-Thin",
    "Noto-Sans-CJK-JP-Medium",
    "Noto-Serif-CJK-JP"
]

2: changer la taille des caractères

Vous pouvez générer différentes images en écrivant le texte intégral à l'écran ou en écrivant un peu de manière conservatrice. Cette fois, j'ai écrit les lettres en augmentant progressivement la taille d'environ la moitié de la taille à la taille juste en dessous.

3: décalez les lettres

Si vous écrivez de petites lettres, vous pouvez créer des blancs en haut, en bas, à gauche et à droite, de sorte que vous pouvez utiliser la technique de décalage des lettres verticalement et horizontalement. Par exemple, si vous pensez à déplacer le blanc / 2 et le blanc / 3 vers le haut, le bas, la gauche et la droite, vous pouvez générer des images différentes 5 x 5.

4: faire pivoter le personnage

Vous pouvez faire pivoter les caractères avec convert. Vous pouvez augmenter le nombre d'images en le tournant légèrement dans le sens horaire ou antihoraire.

(Inutilisé) Ajouter du flou

Vous pouvez augmenter le nombre d'images en préparant une image floue, mais qu'en est-il d'une image dans laquelle la moitié de l'image est floue? Je ne l'ai pas fait parce que je pensais. Un nombre suffisant d'images peut être sécurisé avec 1 à 4.

(Inutilisé) Ajouter du bruit

En ajoutant du bruit tel que de petits points à l'image, il est possible que l'image non seulement augmente mais devienne également plus résistante au bruit. Je ne l'ai pas fait parce que je ne pouvais pas trouver un moyen facile d'ajouter du bruit, mais cela pourrait être une bonne tâche future.

résultat

Génère une image avec des caractères en combinant 1 à 4 (multiplication). Créé à 256px en hauteur et en largeur, plus de 4000 images ont été obtenues pour chaque personnage. Vous pouvez modifier le nombre de feuilles en jouant avec les différents paramètres utilisés dans la méthode. Il existe 169 types de hiragana (0x3041 ~ 0x3093) et de katakana (0x30A1 ~ 0x30F6), donc la capacité est assez grande.

code


data_directory = "/path/to/data"
image_size = 256

#Créer une image noire
def make_template():
    res = subprocess.call([
        "convert",
        "-size", "{s}x{s}".format(s = image_size),
        "xc:black",
        "{}/tmp.png ".format(data_directory)
    ])

#Créer une image de caractères blancs
def generate(path, font, pointsize, character, rotation, dx, dy):
    res = subprocess.call([
        "convert",
        "-gravity", "Center",
        "-font", font,
        "-pointsize", str(pointsize),
        "-fill", "White",
        "-annotate", format_t(rotation, dx, dy), character,
        "{}/tmp.png ".format(data_directory), path
    ])

#Déplacer la fonction de format
def format_t(rotation, x, y):
    xstr = "+" + str(x) if x >= 0 else str(x)
    ystr = "+" + str(y) if y >= 0 else str(y)
    return "{r}x{r}{x}{y}".format(r = rotation, x = xstr, y = ystr)

Créez une image noire uniquement la première fois et créez une image de caractère blanc tout en modifiant les paramètres de police, taille de point, caractère, rotation, dx, dy en boucle.

Construction de modèles

Maintenant que nous avons l'image, nous allons construire le modèle de la même manière que MNIST, mais cela n'a pas fonctionné depuis le début. La valeur de l'erreur d'entropie croisée est la même pour chaque lot, et lors de l'observation de la valeur dans la couche lors de l'entraînement en tant que débogage, la valeur absolue contient une grande valeur telle que des centaines ou des milliers, et la sortie C'était toujours le même. C'est pourquoi nous avons pu insérer la normalisation par lots pour améliorer considérablement la précision.

import torch.nn as nn

class Model(nn.Module):
    def __init__(self, image_size, output):
        super(Model, self).__init__()
        n = ((image_size - 4) // 2 - 4) // 2

        self.conv1 = nn.Conv2d(1, 4, 5)
        self.relu1 = nn.ReLU()
        self.normal1 = nn.BatchNorm2d(4)
        self.pool1 = nn.MaxPool2d(2, 2)
        self.dropout1 = nn.Dropout2d(0.3)
        self.conv2 = nn.Conv2d(4, 16, 5)
        self.relu2 = nn.ReLU()
        self.normal2 = nn.BatchNorm2d(16)
        self.pool2 = nn.MaxPool2d(2, 2)
        self.dropout2 = nn.Dropout2d(0.3)
        self.flatten = nn.Flatten()
        self.linear1 = nn.Linear(n * n * 16, 1024)
        self.relu3 = nn.ReLU()
        self.normal3 = nn.BatchNorm1d(1024)
        self.dropout3 = nn.Dropout(0.3)
        self.linear2 = nn.Linear(1024, 256)
        self.relu4 = nn.ReLU()
        self.normal4 = nn.BatchNorm1d(256)
        self.dropout4 = nn.Dropout(0.3)
        self.linear3 = nn.Linear(256, output)
        self.softmax = nn.Softmax(dim = 1)

    def forward(self, x):
        x = self.conv1(x)
        x = self.relu1(x)
        x = self.normal1(x)
        x = self.pool1(x)
        x = self.dropout1(x)
        x = self.conv2(x)
        x = self.relu2(x)
        x = self.normal2(x)
        x = self.pool2(x)
        x = self.dropout2(x)
        x = self.flatten(x)
        x = self.linear1(x)
        x = self.relu3(x)
        x = self.normal3(x)
        x = self.dropout3(x)
        x = self.linear2(x)
        x = self.relu4(x)
        x = self.normal4(x)
        x = self.dropout4(x)
        x = self.linear3(x)
        x = self.softmax(x)
        return x

Apprentissage

En gros, nous nous formerons à la même procédure que nous l'avons fait au MNIST. J'ai utilisé Cross Entropy Loss, Adam (taux d'apprentissage = 0,001).

Points à prendre en compte lors de l'apprentissage

Étant donné que l'image a été générée en modifiant les paramètres dans une boucle, évitez-la car les données semblent biaisées si elles sont entraînées dans l'ordre. De plus, comme je veux apprendre chaque personnage de manière égale, j'aimerais les apprendre dans l'ordre.

Si vous vous entraînez en lisant l'image en boucle, vous apprendrez une feuille dans un lot. Cependant, il y a beaucoup de données d'image, donc si vous les lisez toutes en même temps, vous risquez de manquer de mémoire. Pour éviter les deux, j'ai décidé d'utiliser yield pour lire les données par blocs.

# a1,Obtenez la double boucle de a2 par le nombre de morceaux
def double_range(a1, a2, chunk = 100):
    records = []

    for x1 in a1:
        for x2 in a2:
            records.append((x1, x2))
            if len(records) >= chunk:
                yield records
                records = []

    if len(records) > 0:
        yield records

Une fonction qui donne deux tableaux et renvoie les paires obtenues dans une double boucle par le nombre de morceaux. Donnez ceci pour plus.

Pseudo code


for indices in double_range("1~Numéros d'images mélangées jusqu'à N", "Numéro attribué au personnage(0~168)"):
    inputs = []
    for i_character, i_image in indices:
        inputs.append("i_caractère i du deuxième caractère_image Charger la première image")

    model.train(inputs) #Apprentissage

Avec cela, l'utilisation de la mémoire peut être réduite en effectuant une boucle qui lit et entraîne les images pour la taille du lot.

Performance du modèle

4236 [feuilles / caractères] ✕ 169 [caractères] J'ai commencé l'expérience après avoir créé les données d'image. En utilisant 5% du total comme données de test, nous nous sommes entraînés avec 2 époques et avons mesuré le taux de réponse correct des données de test, soit environ 71,4%. Au début, j'ai fait une erreur dans le programme et j'ai choisi 4236 au lieu de 169, mais à ce moment-là, c'était un mystère qu'environ 80% étaient sortis. Je veux améliorer un peu plus les performances, mais il semble que je puisse créer un système de reconnaissance et l'exécuter pour le moment.

Recommended Posts

J'ai essayé de faire la reconnaissance de caractères manuscrits de Kana Partie 2/3 Création et apprentissage de données
J'ai essayé de faire la reconnaissance de caractères manuscrits de Kana Partie 3/3 Coopération avec l'interface graphique en utilisant Tkinter
J'ai essayé de traiter et de transformer l'image et d'élargir les données pour l'apprentissage automatique
J'ai essayé de classer Hanana Oba et Emiri Otani par apprentissage profond (partie 2)
J'ai essayé de créer une API de reconnaissance d'image simple avec Fast API et Tensorflow
J'ai essayé de créer diverses "données factices" avec Python faker
J'ai essayé de créer une interface graphique à trois yeux côte à côte avec Python et Tkinter
J'ai essayé d'implémenter Perceptron Part 1 [Deep Learning from scratch]
J'ai essayé de rendre le deep learning évolutif avec Spark × Keras × Docker
J'ai essayé de faire un processus d'exécution périodique avec Selenium et Python
J'ai essayé d'utiliser PyEZ et JSNAPy. Partie 2: J'ai essayé d'utiliser PyEZ
J'ai créé une API Web
[Deep Learning from scratch] J'ai essayé d'implémenter la couche sigmoïde et la couche Relu
J'ai essayé de classer Oba Hanana et Otani Emiri par apprentissage profond
J'ai essayé la reconnaissance manuscrite des caractères des runes avec scikit-learn
J'ai essayé d'utiliser PyEZ et JSNAPy. Partie 1: Aperçu
[Python] J'ai essayé de résoudre 100 questions passées que les débutants et les intermédiaires devraient résoudre [Partie 5/22]
J'ai implémenté DCGAN et essayé de générer des pommes
J'ai essayé de sauvegarder les données avec discorde
[Python] J'ai essayé de résoudre 100 questions passées que les débutants et les intermédiaires devraient résoudre [Partie 7/22]
J'ai essayé d'obtenir des données CloudWatch avec Python
[Python] J'ai essayé de résoudre 100 questions passées que les débutants et les intermédiaires devraient résoudre [Partie 4/22]
[Python] J'ai essayé de résoudre 100 questions passées que les débutants et les intermédiaires devraient résoudre [Part3 / 22]
[Python] J'ai essayé de résoudre 100 questions passées que les débutants et les intermédiaires devraient résoudre [Partie 1/22]
J'ai essayé de faire de l'IA pour Smash Bra
Introduction à la création d'IA avec Python! Partie 3 J'ai essayé de classer et de prédire les images avec un réseau de neurones convolutifs (CNN)
[Python] J'ai essayé de résoudre 100 questions passées que les débutants et les intermédiaires devraient résoudre [Partie 6/22]
[Introduction au PID] J'ai essayé de contrôler et de jouer ♬
J'ai essayé de faire MAP rapidement une personne suspecte en utilisant les données d'adresse Geolonia
J'ai créé un jeu ○ ✕ avec TensorFlow
J'ai essayé de rendre le deep learning évolutif avec Spark × Keras × Docker 2 Multi-host edition
J'ai essayé de faire une simulation de séparation de source sonore en temps réel avec l'apprentissage automatique Python
J'ai essayé de prédire les courses de chevaux en faisant tout, de la collecte de données à l'apprentissage en profondeur
J'ai essayé de faire un "putain de gros convertisseur de littérature"
J'ai essayé de déplacer l'apprentissage automatique (détection d'objet) avec TouchDesigner
J'ai essayé de lire et d'enregistrer automatiquement avec VOICEROID2 2
J'ai essayé d'ajouter un post-incrément à CPython. Présentation et résumé
J'ai essayé de prédire le match de la J League (analyse des données)
J'ai essayé de lire et d'enregistrer automatiquement avec VOICEROID2
J'ai essayé d'ajouter des appels système et des planificateurs à Linux
J'ai essayé d'effacer la partie négative de Meros
J'ai essayé d'implémenter Grad-CAM avec keras et tensorflow
J'ai essayé de créer une application OCR avec PySimpleGUI
[Deep Learning from scratch] J'ai essayé d'expliquer le décrochage
J'ai essayé d'installer scrapy sur Anaconda et je n'ai pas pu
J'ai essayé de compresser l'image en utilisant l'apprentissage automatique
J'ai essayé le deep learning
J'ai essayé de déboguer.
Introduction à la création d'IA avec Python! Partie 1 J'ai essayé de classer et de prédire le nombre à partir de l'image du numéro manuscrit
J'ai essayé de vérifier la classification yin et yang des membres hololive par apprentissage automatique
J'ai essayé de faire d'Othello AI que j'ai appris 7,2 millions de mains par apprentissage profond avec Chainer
J'ai essayé de prédire et de soumettre les survivants du Titanic avec Kaggle
[Introduction à cx_Oracle] (Partie 6) Mappage des types de données DB et Python
Je veux pouvoir analyser des données avec Python (partie 3)
J'ai essayé de rechercher des vidéos à l'aide de l'API de données Youtube (débutant)
J'ai essayé de combiner Discord Bot et la reconnaissance faciale-pour LT-
Je veux pouvoir analyser des données avec Python (partie 1)
J'ai essayé d'accélérer la création vidéo en traitant en parallèle