[PYTHON] Jouez pour prédire la valeur de la course et le type à partir du nom de Pokemon dans TensorFlow

L'autre jour, je suis allé au Groupe d'étude TensorFlow en tant que présentateur. Les gens autour de moi m'ont demandé si je n'écrirais rien sur le contenu de la session d'étude, mais j'avais peur du nombre de personnes annonce que personne ne serait heureux ), Donc à la place, je vais consommer le matériel mort que j'avais initialement prévu d'utiliser.

Cible

Ce que vous voulez faire est très simple comme le titre l'indique. Si vous entrez le nom du Pokémon, je veux que vous voyiez la valeur de la race et le type de chose. C'est comme essayer quelque chose qui est souvent le cas avec les fabricants de diagnostics Twitter, un peu plus sérieusement.

Conception de modèle

Détails d'entrée

Nous avons décomposé le nom du Pokémon caractère par caractère et utilisé le nombre d'occurrences de chaque caractère et 2 grammes comme quantité de caractéristiques. Par exemple, dans le cas de Dedenne:

{
De: 2,Vers le bas: 1,Ne: 1,
Dédé: 1,Tanière: 1,Nne: 1
}

Créer vous-même des fonctionnalités n-gram est un problème, mais utiliser le [Vectorizer] de scikit-learn (http://scikit-learn.org/stable/modules/feature_extraction.html) 2, 3 Vous pouvez créer des fonctions n-gram tout en définissant des paramètres détaillés dans les lignes. Vous pouvez facilement enregistrer un vectoriseur qui stocke toutes les informations nécessaires, c'est pourquoi Ultra Super Miracle est recommandé. À moins que vous n'ayez à accumuler de la vertu en préparation du monde suivant ou qu'il y ait des circonstances spéciales, vous devez absolument l'utiliser.

Détails de sortie

La sortie est un peu encombrante et vous devez la diviser grossièrement en trois sorties.

Vous pouvez faire un modèle pour chacun et faire une prédiction, mais la flexibilité de tout regrouper est la force du réseau neuronal (je pense personnellement que c'est le cas), alors j'ai essayé de tout mettre dans un seul réseau. T. En d'autres termes, c'est ce que c'est.

graph.png

Le nombre d'unités dans la dernière couche est de 6 + 18 + 19. La partie correspondant à la valeur de race était sortie telle quelle et la perte était définie par l'erreur quadratique. La sortie correspond à 18 ou 19 types, dont chacun est poussé dans softmax, et la perte est définie par chaque entropie croisée. La fonction objectif finale est l'addition pondérée de ces pertes.

Collecte de données

~~ Ce ne sont pas des données confidentielles, donc si vous faites de votre mieux, vous vous réunirez. Faites de votre mieux. ~~ Seules les données utilisées ont été [téléchargées sur GitHub] avec le code (https://github.com/sfujiwara/pn2bs).

Le code fini

# -*- coding: utf-8 -*-

import numpy as np
import pandas as pd
import tensorflow as tf
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.externals import joblib
from sklearn.feature_extraction import DictVectorizer


def inference(x_placeholder, n_in, n_hidden1, n_hidden2):
    """
    Description
    -----------
    Forward step which build graph.

    Parameters
    ----------
    x_placeholder: Placeholder for feature vectors
    n_in: Number of units in input layer which is dimension of feature
    n_hidden1: Number of units in hidden layer 1
    n_hidden2: Number of units in hidden layer 2

    Returns
    -------
    y_bs: Output tensor of predicted values for base stats
    y_type1: Output tensor of predicted values for type 1
    y_type2: Output tensor of predicted values for type 2
    """
    # Hidden1
    with tf.name_scope('hidden1') as scope:
        weights = tf.Variable(
            tf.truncated_normal([n_in, n_hidden1]),
            name='weights'
        )
        biases = tf.Variable(tf.zeros([n_hidden1]))
        hidden1 = tf.nn.sigmoid(tf.matmul(x_placeholder, weights) + biases)

    # Hidden2
    with tf.name_scope('hidden2') as scope:
        weights = tf.Variable(
            tf.truncated_normal([n_hidden1, n_hidden2]),
            name='weights'
        )
        biases = tf.Variable(tf.zeros([n_hidden2]))
        hidden2 = tf.nn.sigmoid(tf.matmul(hidden1, weights) + biases)

    # Output layer for base stats
    with tf.name_scope('output_base_stats') as scope:
        weights = tf.Variable(
            tf.truncated_normal([n_hidden2, 6]),
            name='weights'
        )
        biases = tf.Variable(tf.zeros([6]))
        y_bs = tf.matmul(hidden2, weights) + biases

    # Output layer for type1
    with tf.name_scope('output_type1') as scope:
        weights = tf.Variable(
            tf.truncated_normal([n_hidden2, 18]),
            name='weights'
        )
        biases = tf.Variable(tf.zeros([18]))
        # y_type1 = tf.nn.softmax(tf.matmul(hidden2, weights) + biases)
        y_type1 = tf.matmul(hidden2, weights) + biases

    # Output layer for type2
    with tf.name_scope('output_type2') as scope:
        weights = tf.Variable(
            tf.truncated_normal([n_hidden2, 19]),
            name='weights'
        )
        biases = tf.Variable(tf.zeros([19]))
        y_type2 = tf.matmul(hidden2, weights) + biases
        # y_type2 = tf.nn.softmax(tf.matmul(hidden2, weights) + biases)

    return [y_bs, y_type1, y_type2]


def build_loss_bs(y_bs, t_ph_bs):
    """
    Parameters
    ----------
    y_bs: Output tensor of predicted values for base stats
    t_ph_bs: Placeholder for base stats

    Returns
    -------
    Loss tensor which includes placeholder of features and labels
    """
    loss_bs = tf.reduce_mean(tf.nn.l2_loss(t_ph_bs - y_bs), name='LossBaseStats')
    return loss_bs


def build_loss_type1(y_type1, t_ph_type1):
    """
    Parameters
    ----------
    y_type1: Output tensor of predicted values for base stats
    t_ph_type1: Placeholder for base stats

    Returns
    -------
    Loss tensor which includes placeholder of features and labels
    """
    loss_type1 = tf.reduce_mean(
        tf.nn.softmax_cross_entropy_with_logits(y_type1, t_ph_type1),
        name='LossType1'
    )
    return loss_type1


def build_loss_type2(y_type2, t_ph_type2):
    """
    Parameters
    ----------
    y_type2: Output tensor of predicted values for base stats
    t_ph_type2: Placeholder for base stats

    Returns
    -------
    Loss tensor which includes placeholder of features and labels
    """
    loss_type2 = tf.reduce_mean(
        tf.nn.softmax_cross_entropy_with_logits(y_type2, t_ph_type2),
        name='LossType2'
    )
    return loss_type2


def build_optimizer(loss, step_size):
    """
    Parameters
    ----------
    loss: Tensor of objective value to be minimized
    step_size: Step size for gradient descent

    Returns
    -------
    Operation of optimization
    """
    optimizer = tf.train.GradientDescentOptimizer(step_size)
    global_step = tf.Variable(0, name='global_step', trainable=False)
    train_op = optimizer.minimize(loss, global_step=global_step)
    return train_op


if __name__ == '__main__':
    # Set seed
    tf.set_random_seed(0)

    # Load data set and extract features
    df = pd.read_csv('data/poke_selected.csv')

    # Fill nulls in type2
    df.loc[df.type2.isnull(), 'type2'] = 'Rien'

    # Vectorize pokemon name
    pokename_vectorizer = CountVectorizer(analyzer='char', min_df=1, ngram_range=(1, 2))
    x = pokename_vectorizer.fit_transform(list(df['name_jp'])).toarray()
    t_bs = np.array(df[['hp', 'attack', 'block', 'contact', 'defense', 'speed']])

    # Vectorize pokemon type1
    poketype1_vectorizer = DictVectorizer(sparse=False)
    d = df[['type1']].to_dict('record')
    t_type1 = poketype1_vectorizer.fit_transform(d)

    # Vectorize pokemon type2
    poketype2_vectorizer = DictVectorizer(sparse=False)
    d = df[['type2']].to_dict('record')
    t_type2 = poketype2_vectorizer.fit_transform(d)

    # Placeholders
    x_ph = tf.placeholder(dtype=tf.float32)
    t_ph_bs = tf.placeholder(dtype=tf.float32)
    t_ph_type1 = tf.placeholder(dtype=tf.float32)
    t_ph_type2 = tf.placeholder(dtype=tf.float32)

    # build graph, loss, and optimizer
    y_bs, y_type1, y_type2 = inference(x_ph, n_in=1403, n_hidden1=512, n_hidden2=256)
    loss_bs = build_loss_bs(y_bs, t_ph_bs)
    loss_type1 = build_loss_type1(y_type1, t_ph_type1)
    loss_type2 = build_loss_type2(y_type2, t_ph_type2)
    loss = tf.add_n([1e-4 * loss_bs, loss_type1, loss_type2], name='ObjectiveFunction')
    optim = build_optimizer(loss, 1e-1)

    # Create session
    sess = tf.Session()

    # Initialize variables
    init = tf.initialize_all_variables()
    sess.run(init)

    # Create summary writer and saver
    summary_writer = tf.train.SummaryWriter('log', graph_def=sess.graph_def)
    tf.scalar_summary(loss.op.name, loss)
    tf.scalar_summary(loss_bs.op.name, loss_bs)
    tf.scalar_summary(loss_type1.op.name, loss_type1)
    tf.scalar_summary(loss_type2.op.name, loss_type2)
    summary_op = tf.merge_all_summaries()
    saver = tf.train.Saver()

    # Run optimization
    for i in range(1500):
        # Choose indices for mini batch update
        ind = np.random.choice(802, 802)
        batch_xs = x[ind]
        batch_ts_bs = t_bs[ind]
        batch_ts_type1 = t_type1[ind]
        batch_ts_type2 = t_type2[ind]
        # Create feed dict
        fd = {
            x_ph: batch_xs,
            t_ph_bs: batch_ts_bs,
            t_ph_type1: batch_ts_type1,
            t_ph_type2: batch_ts_type2
        }
        # Run optimizer and update variables
        sess.run(optim, feed_dict=fd)
        # Show information and write summary in every n steps
        if i % 100 == 99:
            # Show num of epoch
            print 'Epoch:', i + 1, 'Mini-Batch Loss:', sess.run(loss, feed_dict=fd)
            # Write summary and save checkpoint
            summary_str = sess.run(summary_op, feed_dict=fd)
            summary_writer.add_summary(summary_str, i)
            name_model_file = 'model_lmd1e-4_epoch_' + str(i+1) + '.ckpt'
            save_path = saver.save(sess, 'model/tensorflow/'+name_model_file)
    else:
        name_model_file = 'model_lmd1e-4_epoch_' + str(i+1) + '.ckpt'
        save_path = saver.save(sess, 'model/tensorflow/'+name_model_file)

    # Show example
    poke_name = 'Tonnerre'
    v = pokename_vectorizer.transform([poke_name]).toarray()
    pred_bs = sess.run(y_bs, feed_dict={x_ph: v})
    pred_type1 = np.argmax(sess.run(y_type1, feed_dict={x_ph: v}))
    pred_type2 = np.argmax(sess.run(y_type2, feed_dict={x_ph: v}))
    print poke_name
    print pred_bs
    print pred_type1, pred_type2
    print poketype1_vectorizer.get_feature_names()[pred_type1]
    print poketype2_vectorizer.get_feature_names()[pred_type2]

    # Save vectorizer of scikit-learn
    joblib.dump(pokename_vectorizer, 'model/sklearn/pokemon-name-vectorizer')
    joblib.dump(poketype1_vectorizer, 'model/sklearn/pokemon-type1-vectorizer')
    joblib.dump(poketype2_vectorizer, 'model/sklearn/pokemon-type2-vectorizer')

Laisse moi apprendre

Cela ne signifie pas que le matériau est sérieusement réglé parce que c'est un matériau, alors j'ai juste regardé TensorBoard pour voir si la perte sur le type minimum et la perte sur la valeur de course ont été réduites de manière équilibrée.

Essayez de jouer

Chargeons le modèle et jouons avec comme ça.

# -*- coding: utf-8 -*-

import numpy as np
import pandas as pd
import tensorflow as tf
from sklearn.externals import joblib
import pn2bs


# Placeholder
x_ph = tf.placeholder(dtype=tf.float32)
t_ph = tf.placeholder(dtype=tf.float32)

y_bs, y_type1, y_type2 = pn2bs.inference(x_ph, n_in=1403, n_hidden1=512, n_hidden2=256)

# Create session
sess = tf.Session()

# Load TensorFlow model
saver = tf.train.Saver()
saver.restore(sess, "model/tensorflow/model_lmd1e-4_epoch_1500.ckpt")

# Load vectorizer of scikit-learn
pokename_vectorizer = joblib.load("model/sklearn/pokemon-name-vectorizer")
poketype1_vectorizer = joblib.load("model/sklearn/pokemon-type1-vectorizer")
poketype2_vectorizer = joblib.load("model/sklearn/pokemon-type2-vectorizer")

poke_name = 'Gonzales'
v = pokename_vectorizer.transform([poke_name]).toarray()
pred_bs = sess.run(y_bs, feed_dict={x_ph: v})
pred_type1 = np.argmax(sess.run(y_type1, feed_dict={x_ph: v}))
pred_type2 = np.argmax(sess.run(y_type2, feed_dict={x_ph: v}))
result = {
    'name'   : poke_name,
    'hp'     : pred_bs[0][0],
    'attack' : pred_bs[0][1],
    'block'  : pred_bs[0][2],
    'contact': pred_bs[0][3],
    'defense': pred_bs[0][4],
    'speed'  : pred_bs[0][5],
    'type1'  : poketype1_vectorizer.get_feature_names()[pred_type1],
    'type2'  : poketype2_vectorizer.get_feature_names()[pred_type2],
}
print result['name']
print result['hp']
print result['attack']
print result['block']
print result['contact']
print result['defense']
print result['speed']
print result['type1']
print result['type2']

Un exemple du résultat. Il n'y a rien de spécial à dire, mais devinez quelque chose sur chacun d'eux.

Name H A B C D S Type 1 Type 2
Flux Tensol 92 102 84 85 65 73 Fée mal
Mega Pikachu 74 80 50 97 85 80 Électricité Rien
Gonzales 81 103 107 86 103 65 Dragon Électricité
Mega Gonzales 100 137 131 118 117 103 Dragon mal

Problèmes futurs (pour ne pas dire que nous allons y travailler)

Résumé

J'ai pensé que la flexibilité de la modélisation est excellente parce que le réseau neuronal jette les bonnes propriétés de convexité et fonctionne à la colombe de toutes ses forces. Par exemple, si vous essayez de faire la même chose avec SVM, vous cesserez d'abord de vous demander quoi faire lorsque l'on vous demandera une sortie multidimensionnelle telle que les valeurs de race, et vous résoudrez même le problème de classification lié aux types ensemble. Le jour où on m'a dit, j'ai l'impression que je devrais dormir et dire dormir. Cependant, quand il s'agit de laisser les 2 outs en bas de la 9e manche au réseau neuronal, cela ressemble à hmm. quelque chose comme ca.

Postscript

2016-01-26

Je suis un peu désolé de ne pas l'avoir publié dans un état où je peux l'essayer correctement même s'il y a des gens qui le stockent encore de temps en temps, j'ai donc publié quelque chose qui fonctionne [sur GitHub](https: / /github.com/sfujiwara/pn2bs). Je pensais que ce serait cool de publier un graphique radar avec Google Charts ou quelque chose selon le nom entré, mais je voulais laisser le bot le frapper et jouer avec, alors j'en ai fait une API Web.

Je pense qu'il sortira probablement lorsque je l'utilisera, donc si je l'avoue en premier, lorsque je mets un Pokémon existant, il se fanera si le statut est complètement différent de celui actuel, alors j'ai intentionnellement adopté un modèle qui semble surappris.

Recommended Posts

Jouez pour prédire la valeur de la course et le type à partir du nom de Pokemon dans TensorFlow
Prédire le sexe à partir du nom à l'aide de l'API Gender et de Pykakasi en Python
[Comment!] Apprenez et jouez à Super Mario avec Tensorflow !!
Les étrangers parlent: Comment nommer les classes et les méthodes en anglais
Lire json avec C # et convertir en type dictionnaire (forcé)
Clipping et normalisation dans TensorFlow
[Python] Hit Keras depuis TensorFlow et TensorFlow depuis c ++ pour accélérer l'exécution.
Que faire lorsque le type de valeur est ambigu en Python?