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.
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.
Afin de préparer plusieurs images pour un personnage, nous avons préparé une méthode pour augmenter les données.
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"
]
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.
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.
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.
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.
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.
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.
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.
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
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).
É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.
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