Ravi de vous rencontrer, je suis consultant chez Hitachi, Ltd. Lumada Data Science Lab.. À Hitachi, il y a une activité appelée «activité en petits groupes» dans laquelle les bénévoles se réunissent pour travailler sur leur thème préféré. Cette fois, l'équipe de data scientists dont je fais partie a fait une activité intitulée ** "Faisons une application de reconnaissance de sashimi!" **. Grâce à cet effort, nous présenterons l'apprentissage et la sensibilisation, des exemples de code de modèles réels, etc.
La bonne réponse est ...
Pour être honnête, je ne connais pas la différence entre Kanpachi et Buri ... Dans un tel cas, "l'application de reconnaissance de sashimi" est utile!
Mon travail principal est l'analyse des données, mais si j'acquiers les compétences nécessaires pour construire un système, je peux l'utiliser dans le développement de proto et demander à des clients de l'essayer, et je pourrai expérimenter et partager les résultats. Par conséquent, nous avons commencé dans le but de créer des opportunités de travailler de zéro à la construction du système et de partager le savoir-faire entre les membres.
Les rôles ont été répartis de cette manière. J'étais principalement en charge du développement de modèles.
Nous avons procédé dans l'ordre de détermination des thèmes → collecte de données → construction du modèle → développement d'application.
Au lieu de vérifier la génialité de l'IA, nous avons cherché des services et des systèmes que les clients pourraient réellement utiliser. Comme chaque membre apportait ses idées et discutait des hypothèses et des besoins, «l'application de reconnaissance de sashimi» a finalement été sélectionnée.
Il est difficile de distinguer les types de sashimi. En particulier, les étrangers qui visitent le Japon mangent souvent sans savoir de quel type de poisson il s'agit. Si ces étrangers en visite au Japon peuvent en apprendre davantage sur les différences dans le sashimi, approfondir leur compréhension de la culture japonaise et la promouvoir dans le monde entier, je pense que nous pouvons contribuer à l'industrie de la restauration et du tourisme.
** Enquête sur les besoins **
La demande entrante est attendue aux Jeux Olympiques de TOKYO.
Selon une enquête, ce que les étrangers visitant le Japon attendent le plus du Japon, c'est la «nourriture japonaise». «D'un autre côté, les restaurants qui proposent de la« cuisine japonaise »estiment qu'ils n'ont pas été en mesure d'expliquer pleinement l'attrait de la culture japonaise et des ingrédients qui les sous-tendent.
En coopération avec des restaurants tels que Kaitenzushi, nous fournirons une application capable de répondre correctement au type de sashimi ou de rivaliser avec l'IA. Si le taux de réponse correcte du client est supérieur au résultat du jugement AI, des points pouvant être utilisés en magasin seront attribués. Cela mènera à la compréhension et à la consommation de la culture japonaise tout en faisant profiter les clients.
J'ai collecté des images sur Internet. C'est dur ...
J'ai acheté la vraie chose dans un supermarché et j'ai pris une image. Nous avons tourné des vidéos en changeant la direction de prise de vue, comment faire briller la lumière, la couleur de la plaque, etc., et avons conçu un moyen de collecter de nombreuses images sans frais. Cependant, vous pouvez manger trop de sashimi et vous casser l'estomac ...
Au final, j'ai rassemblé plus de 40 000 photos de sashimis, même si j'ai eu du mal.
Nous avons construit une IA qui continue d'apprendre en faisant tourner le cycle de collecte de données → prétraitement → division des données → construction de modèles → (ré) apprentissage.
Le modèle (nommé SushiNet) crée un réseau résiduel personnalisé à 24 niveaux (ResNet) par scratch. En (ré) apprentissage, les ensembles de données collectées sont déséquilibrés, nous essayons donc de nous concentrer sur les classes sous-estimées en définissant des poids pour chaque classe.
Ici, nous allons présenter un exemple d'exécution de remplissage de données en (ré) apprentissage.
rows=cols=3
datagen = ImageDataGenerator(rotation_range=90)
show_aug_images(x)
De cette manière, l'image est tournée de manière aléatoire.
Un exemple de version complète du modèle est donné à la fin de cet article. Si vous êtes intéressé, essayez-le ︕
Affiche la correspondance entre chaque composant du système et le référentiel.
Lorsque vous ouvrez l'URL de l'application de reconnaissance de sashimi (= atterrissage) sur le navigateur de votre smartphone, l'application frontale (= HTML + JavaScript + CSS) sera téléchargée et l'application démarrera sur le navigateur.
Lorsque vous démarrez l'application de reconnaissance de sashimi, photographiez le sashimi avec l'appareil photo et le téléchargez, l'application frontale contrôle l'appareil photo du terminal via le navigateur, acquiert l'image et l'envoie au serveur.
Le serveur reçoit l'image, l'application back-end reconnaît et évalue le sashimi, et le résultat du jugement est envoyé à l'application frontale via le navigateur. Le résultat du jugement reçu par l'application frontale est affiché sur le navigateur du smartphone.
J'ai utilisé l'API HTML5 getUserMedia () pour accéder à l'appareil photo à partir du navigateur de mon smartphone. Cette API a le statut de «recommandation candidate» et n'est pas encore une spécification officielle, donc la spécification est sujette à changement. En fait, j'étais un peu impatient quand il est devenu clair pendant le développement qu'il devenait impossible d'utiliser la communication de schéma HTTP en raison d'une sécurité renforcée et qu'il était nécessaire d'utiliser des HTTP.
C'est un point très important lors de l'utilisation de Tensorflow à partir d'une application Web. Le graphe de calcul créé par Tensorflow dans le thread A qui traite une requête HTTP A interfère avec le graphe de calcul créé dans le thread B qui traite une autre requête HTTP B. Pour éviter cela, le graphe de calcul doit être singleton.
Avant de saisir l'image prise par l'appareil photo dans le modèle, un traitement d'image doit être effectué. Il est nécessaire de concevoir soigneusement comment partager cela entre l'application frontale (= JavaScript sur le navigateur) et l'application principale (= Python). Si cette conception est lâche, des bogues se produiront fréquemment après le test d'intégration et des retouches se produiront. En fait, en raison d'une erreur de conception dans le processus de compression / décompression, le côté JavaScript envoie au format RGBA, tandis que le côté Python attend le format RVB, donc une erreur s'est produite.
Cliquez ici pour l'application terminée.
À quel point l'IA a-t-elle été proche des humains?⁉
Types de sashimi | Taux de réponse humaine correcte | Taux de réponse correcte de l'IA | Gagner ou perdre |
---|---|---|---|
Thon | 95% | 75% | Victoire humaine |
Hamachi | 44% | 78% | L'IA gagne |
Sanma | 50% | 100% | L'IA gagne |
grande sériole | 40% | 40% | N'êtes-vous pas bon non plus? |
Je pense que les résultats étaient assez bons. (Bien que le taux de réponse correcte pour les humains ne soit pas très élevé en premier lieu ...)
Cependant, il existe toujours un écart important entre le produit réel et les données collectées, et il y a place à amélioration. Il existe peu de caractéristiques faciles à comprendre telles que l'apparence des animaux, et le fait que l'apparence change en fonction de la fraîcheur est également un facteur qui rend difficile à juger. Le futur problème est de savoir comment nettoyer les données obtenues sur Internet.
――L'objectif consistant à couvrir l'ensemble du développement du système a été presque atteint. ――Il a conduit à un partage mutuel des connaissances et à une amélioration entre les membres --J'ai également appris la partie développement d'applications (coopération cloud, etc.) ―― La collecte de données est de toute façon difficile ... Je comprends un peu les difficultés des clients et des SE
En fait, il peut être le plus important de connaître les difficultés du dernier client et SE. J'espère que cette application pourra être mise en pratique d'ici les Jeux olympiques de l'année prochaine.
from tensorflow.keras.layers import Dropout, BatchNormalization, Flatten, Activation, Input, Dense,Add,Reshape
from tensorflow.keras.layers import ZeroPadding2D,Conv2D,ELU,MaxPooling2D,AveragePooling2D,GlobalAveragePooling2D
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras import Model
from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, Callback, ReduceLROnPlateau, LearningRateScheduler,CSVLogger
from tensorflow.keras.losses import binary_crossentropy, categorical_crossentropy, mean_squared_error
from tensorflow.keras.optimizers import Adam, RMSprop, SGD
from tensorflow.keras.utils import Sequence, to_categorical
from tensorflow.keras import losses, models, optimizers
from tensorflow.keras import backend as K
import tensorflow as tf
import matplotlib.pyplot as plt
from PIL import Image
import numpy as np
import warnings
import os
from datetime import datetime
warnings.simplefilter('ignore')
warnings.filterwarnings('ignore')
%matplotlib inline
#Charge de données
img_path="xxx.jpg "
im = Image.open(img_path)
im = im.resize((224,224),Image.BILINEAR)
x = np.expand_dims(im,axis=0)
#Fonction de visualisation de remplissage de données
def show_aug_images(x):
g = datagen.flow(x,
batch_size=1)
plt.figure(figsize=(8,8))
for i in range(rows*cols):
aug_img=g.next()
plt.subplot(rows,cols,i+1)
plt.axis('off')
plt.imshow(aug_img[0].astype('uint8'))
rows=cols=3
datagen = ImageDataGenerator(rotation_range=90)
show_aug_images(x)
rows=cols=3
datagen = ImageDataGenerator(brightness_range=[0.1,3])
show_aug_images(x)
rows=cols=3
datagen = ImageDataGenerator(width_shift_range=0.4,fill_mode='reflect')
show_aug_images(x)
rows=cols=3
datagen = ImageDataGenerator(zoom_range=0.4)
show_aug_images(x)
En utilisant la structure résiduelle, nous avons construit un modèle ResNet à 24 couches qui utilise ELU (Exponential Linear Unit) en empilant plusieurs bolocks résiduels.
def cbe_block(X,F,kernel_size,strides,padding):
X = Conv2D(filters=F,kernel_size=kernel_size,strides=strides,padding=padding)(X)
X = BatchNormalization()(X)
X = ELU()(X)
return X
def cb_block(X,F,kernel_size,strides,padding):
X = Conv2D(filters=F,kernel_size=kernel_size,strides=strides,padding=padding)(X)
X = BatchNormalization()(X)
return X
def residual_id(X, f, filters):
F1, F2, F3 = filters
X_s = X
X = cbe_block(X=X,F=F1,kernel_size=(1,1),strides=(1,1),padding='valid')
X = cbe_block(X=X,F=F2,kernel_size=(f,f),strides=(1,1),padding='same')
X = cb_block(X=X,F=F3,kernel_size=(1,1),strides=(1,1),padding='valid')
X = Add()([X, X_s])
X = ELU()(X)
return X
def residual_conv(X, f, filters, s=2):
F1, F2, F3 = filters
X_s = X
X = cbe_block(X=X,F=F1,kernel_size=(1,1),strides=(s,s),padding='valid')
X = cbe_block(X=X,F=F2,kernel_size=(f,f),strides=(1,1),padding='same')
X = cb_block(X=X,F=F3,kernel_size=(1,1),strides=(1,1),padding='valid')
X_s = Conv2D(filters=F3, kernel_size=(1,1), strides=(s,s), padding='valid')(X_s)
X_s = BatchNormalization()(X_s)
X = Add()([X, X_s])
X = ELU()(X)
return X
def SushiNet(input_shape = (224, 224, 3), classes = 10):
X_input = Input(input_shape)
X = ZeroPadding2D((3, 3))(X_input)
X = Conv2D(64, (7, 7), strides = (2, 2))(X)
X = BatchNormalization()(X)
X = ELU()(X)
X = MaxPooling2D((3, 3), strides=(2, 2))(X)
X = residual_conv(X, f = 5, filters = [64, 64, 256],s = 1)
for i in range(2):
X = residual_id(X, 3, [64, 64, 256])
X = Dropout(0.3)(X)
X = residual_conv(X, f = 5, filters= [128, 128, 512], s = 2)
for i in range(3):
X = residual_id(X, 3, [128, 128, 512])
X = Dropout(0.3)(X)
X = GlobalAveragePooling2D()(X)
X = Dropout(0.3)(X)
X = Dense(64)(X)
X = Dense(classes, activation='softmax')(X)
model = Model(inputs = X_input, outputs = X)
model.compile(optimizer='adam',
loss='categorical_crossentropy',
metrics=['accuracy'])
return model
model = SushiNet(input_shape = (224, 224, 3), classes = 10)
** Astuces: ** class_weight: Lorsque vous entraînez une classe déséquilibrée, vous pouvez mettre à l'échelle la fonction de perte en préréglant le poids pour chaque classe.
datagen = image.ImageDataGenerator(
width_shift_range = 0.1,
height_shift_range=0.1,
rotation_range=10,
channel_shift_range=150,
zoom_range=0.5,
horizontal_flip=True,
vertical_flip=True,
fill_mode='reflect',
brightness_range=[0.5,3.5])
datagen.fit(X_train)
model.fit_generator(datagen.flow(X_train, y_train,
batch_size=batch_size),
epochs=epochs,
validation_data=(X_valid, y_valid),
steps_per_epoch = X_train.shape[0]/batch_size,
shuffle = True,
class_weight = class_weight)