[PYTHON] J'ai créé une IA qui prédit des anecdotes et m'a fait déduire mes anecdotes. Hee-AI

Hee-AI

J'ai créé Hee-AI.

スクリーンショット 2020-06-26 14.29.55.png

Ce Google Colab est publié non modifiable. Veuillez le copier sur votre disque et jouer avec! (Environnement PC recommandé)

Lien Google Colab

Le code source est également disponible, veuillez donc vous y référer pour référence.

Histoire de production

Tout cela se passe le 26 juin 2020.

Motivation

Mon hobby explore Wikipedia, mais un jour [Trivia Fountain](https://ja.wikipedia.org/wiki/%E3%83%88%E3%83%AA%E3%83%93% E3% 82% A2% E3% 81% AE% E6% B3% 89_% E3% 80% 9C% E7% B4% A0% E6% 99% B4% E3% 82% 89% E3% 81% 97% E3% 81% 8D% E3% 83% A0% E3% 83% 80% E7% 9F% A5% E8% AD% 98% E3% 80% 9C) Tous les anecdotes passées et leurs goûts présentés dans le programme J'ai découvert que c'est répertorié.

Sans hésitation, j'ai pensé: "Ce sera un problème de retour ... !!!". スクリーンショット 2020-06-26 14.55.12.png

J'ai immédiatement lancé le bureau.

plan

En fin de compte, je voulais que beaucoup de gens jouent, alors j'ai d'abord pensé à en faire un service Web, mais je ne voulais pas arrêter mes recherches pendant plus d'un jour, alors j'ai abandonné. Exécutez-le sur Google Colab En rendant cela possible, je pourrais facilement y jouer si je l'avais copié sur chaque lecteur, alors j'ai fait un plan sur cet itinéraire.

Google Colab Google Colab (Google Colaboratory) est un service fourni par Google qui vous permet d'exécuter Jupyter Notebook sur Google Drive sur la machine de Google. Vous pouvez également utiliser le GPU. C'est très utile car vous n'avez pas à calculer localement. Cependant, comme il s'agit d'un Jupyter Notebook, il ne convient pas aux personnes comme moi qui aiment les structures hiérarchiques.

Mais cette fois, je ne peux pas dire cela. De plus, si possible, vous devez exécuter une cellule pour tout compléter et réduire au maximum la charge de l'utilisateur. Cependant, faites en sorte que le processus d'apprentissage et le processus d'inférence soient la même cellule. Dans ce cas, l'apprentissage se déroule à chaque fois que vous inférez. Vous ne devez donc séparer que cette partie. Par conséquent, en tant que formulaire rempli, nous avons prévu que Google Colab ne se compose que de deux cellules, comme indiqué ci-dessous.

Et je voulais le publier avec le traitement qui puisse être omis autant que possible.Par conséquent, j'ai décidé de ne pas cloner le référentiel Github qui a une structure de répertoire sur Google Golab, mais de le rendre complet sauf pour le système d'installation. Par conséquent, les données Wikipedia, etc. sont également stockées dans le type de dictionnaire python en premier, et un package python appelé stickytape est utilisé pour créer un fichier python, et diverses idées ont été conçues.

Festival d'été de Qiita 2020

Donc, il est devenu un jeu de contrainte forte de créer une application d'IA uniquement avec python sur Colab, donc au Qiita Summer Festival 2020 en cours J'ai également décidé de postuler.

Dans ce projet, nous recherchons des articles basés sur les trois thèmes suivants.

  1. Si vous souhaitez créer △△ (application) en utilisant uniquement 〇〇 (langue)
  2. Partagez les échecs passés dans le développement du système et comment les surmonter!
  3. [Apprentissage automatique] "Ne faites pas" Partageons des anti-modèles!

J'ai commencé à écrire en pensant que j'avais 1., mais pendant que je le faisais, j'ai pensé que je pourrais peut-être toucher l'anti-modèle d'apprentissage automatique de 3. Donc je pense que je vais également mettre un sujet là-dessus.

C'était l'histoire.

Revenons à l'histoire de la production.

Grattage

Au début, j'ai copié du HTML et je me suis battu pendant environ 15 minutes pour voir s'il pouvait être automatiquement formaté avec des macros vim, mais il y avait des valeurs aberrantes, et je me suis demandé si la programmation serait mieux de les gérer. J'ai abandonné.

Le code de scraping que j'ai écrit ressemble à ceci.

scraping.py


import urllib
from bs4 import BeautifulSoup

URL = "https://ja.wikipedia.org/wiki/%E3%83%88%E3%83%AA%E3%83%93%E3%82%A2%E3%81%AE%E6%B3%89_%E3%80%9C%E7%B4%A0%E6%99%B4%E3%82%89%E3%81%97%E3%81%8D%E3%83%A0%E3%83%80%E7%9F%A5%E8%AD%98%E3%80%9C"

def get_text(tag):
    text = tag.text
    text = text.replace('\n', '')
    text = text.replace('[18]', '')
    text = text.replace('[19]', '')
    text = text.replace('[20]', '')
    text = text.replace('[21]', '')
    text = text.replace('[22]', '')
    return text

if __name__ == "__main__":

    html = urllib.request.urlopen(URL)
    soup = BeautifulSoup(html, 'html.parser')

    trivia_table = soup.find('table', attrs={'class': 'sortable'})

    trivias_list = []
    for i, line in enumerate(trivia_table.tbody):

        if i < 3:
            continue
        if line == '\n':
            continue
        
        id = line.find('th')
        content, hee, man_hee = line.find_all('td')

        id, content, hee, man_hee = map(get_text, [id, content, hee, man_hee])

        if hee == '?':
            continue
        
        trivias_list.append({'id': id, 'content': content, 'hee': int(hee), 'man_hee': int(man_hee)})
    
    print(trivias_list)

Grosso modo,

--Utilisez soup.find () de BeautifulSoup pour trouver la table sur laquelle se trouvent toutes les anecdotes à partir de la page de trivia wikipedia.

C'est un flux.

Ingénierie de la quantité de fonctionnalités

Maintenant que nous avons toutes les données, c'est une vitrine pour un apprentissage automatique, l'ingénierie de la quantité de fonctionnalités. Pour être honnête, je le savais avant de le faire, mais je ne pouvais pas obtenir une bonne précision. Il y a au plus 1000 phrases d'environ 20 caractères. Cependant, il est absolument impossible de le faire, mais pour réduire au minimum, nous avons extrait les quantités de caractéristiques comme suit.

est.

Ceci est la variable objective pour le dernier "il numéro / plein il". Si le nombre de il est utilisé tel quel, il y a eu des moments où 200 il était plein à la réunion spéciale Trivia Fountain. L'échelle sera différente. Par conséquent, la variable objective est la valeur normalisée à 0-1 telle que "nombre de cheveux / cheveux pleins".

Le code ressemble à ceci.

feature.py


import re

import MeCab
import numpy as np
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer

# Mecab
tagger = MeCab.Tagger('-d /usr/local/lib/mecab/dic/mecab-ipadic-neologd')
tagger.parse('')

# lambda
re_hira = re.compile(r'^[Ah-Hmm]+$')
re_kata = re.compile(r'[\u30A1-\u30F4]+')
re_kanj = re.compile(r'^[\u4E00-\u9FD0]+$')
re_eigo = re.compile(r'^[a-zA-Z]+$')
is_hira = lambda word: not re_hira.fullmatch(word) is None
is_kata = lambda word: not re_kata.fullmatch(word) is None
is_eigo = lambda word: not re_eigo.fullmatch(word) is None
is_kanj = lambda word: not re_kanj.fullmatch(word) is None

# tl: trivias_list
def normalize_hee(tl):
    for i in range(len(tl)):
        tl[i]['norm_hee'] = tl[i]['hee'] / tl[i]['man_hee']
    return tl

def wakati(text):

    node = tagger.parseToNode(text)
    l = []
    while node:
        if node.feature.split(',')[6] != '*':
            l.append(node.feature.split(',')[6])
        else:
            l.append(node.surface)
        node = node.next
    return ' '.join(l)

def preprocess(tl):
    tl = normalize_hee(tl)
    for i in tqdm(range(len(tl))):
        tl[i]['wakati_content'] = wakati(tl[i]['content'])
    return tl

def count_len(sentence):
    return len(sentence)
def count_word(sentence):
    return len(sentence.split(' '))
def count_kata(sentence):
    cnt = 0; total=0
    for word in sentence.split(' '):
        if word == '': continue
        total += 1
        if is_kata(word): cnt += 1
    return cnt/total
def count_hira(sentence):
    cnt = 0; total=0
    for word in sentence.split(' '):
        if word == '': continue
        total += 1
        if is_hira(word): cnt += 1
    return cnt/total
def count_eigo(sentence):
    cnt = 0; total=0
    for word in sentence.split(' '):
        if word == '': continue
        total += 1
        if is_eigo(word): cnt += 1
    return cnt/total
def count_kanj(sentence):
    cnt = 0; total=0
    for word in sentence.split(' '):
        if word == '': continue
        total += 1
        if is_kanj(word): cnt += 1
    return cnt/total

def get_features(trivias_list, content=None, mode='learn'):

    trivias_list = preprocess(trivias_list)
    trivias_df = pd.DataFrame(trivias_list)
    
    wakati_contents_list = trivias_df['wakati_content'].values.tolist()

    word_vectorizer = TfidfVectorizer(max_features=5)
    word_vectorizer.fit(wakati_contents_list)

    if mode == 'inference':
        content = [{'content': content, 'wakati_content': wakati(content)}]
        content_df = pd.DataFrame(content)

        wakati_content_list = content_df['wakati_content'].values.tolist()
        tfidf = word_vectorizer.transform(wakati_content_list)
        content_df = pd.concat([
            content_df,
            pd.DataFrame(tfidf.toarray())
        ], axis=1)
        num_len_df = content_df['wakati_content'].map(count_len)
        num_word_df = content_df['wakati_content'].map(count_word)
        num_hira_df = content_df['wakati_content'].map(count_hira)
        num_kata_df = content_df['wakati_content'].map(count_kata)
        num_eigo_df = content_df['wakati_content'].map(count_eigo)
        num_kanj_df = content_df['wakati_content'].map(count_kanj)
        content_df['num_len'] = num_len_df.values.tolist()
        content_df['num_word'] = num_word_df.values.tolist()
        content_df['num_hira'] = num_hira_df.values.tolist()
        content_df['num_kata'] = num_kata_df.values.tolist()
        content_df['num_eigo'] = num_eigo_df.values.tolist()
        content_df['num_kanj'] = num_kanj_df.values.tolist()

        content_df = content_df.drop('content', axis=1)
        content_df = content_df.drop('wakati_content', axis=1)

        return content_df


    tfidf = word_vectorizer.transform(wakati_contents_list)
    all_df = pd.concat([
        trivias_df,
        pd.DataFrame(tfidf.toarray())
    ], axis=1)

    num_len_df = all_df['wakati_content'].map(count_len)
    num_word_df = all_df['wakati_content'].map(count_word)
    num_hira_df = all_df['wakati_content'].map(count_hira)
    num_kata_df = all_df['wakati_content'].map(count_kata)
    num_eigo_df = all_df['wakati_content'].map(count_eigo)
    num_kanj_df = all_df['wakati_content'].map(count_kanj)
    all_df['num_len'] = num_len_df.values.tolist()
    all_df['num_word'] = num_word_df.values.tolist()
    all_df['num_hira'] = num_hira_df.values.tolist()
    all_df['num_kata'] = num_kata_df.values.tolist()
    all_df['num_eigo'] = num_eigo_df.values.tolist()
    all_df['num_kanj'] = num_kanj_df.values.tolist()

    if mode == 'learn':
        all_df = all_df.drop('id', axis=1)
        all_df = all_df.drop('hee', axis=1)
        all_df = all_df.drop('man_hee', axis=1)
        all_df = all_df.drop('content', axis=1)
        all_df = all_df.drop('wakati_content', axis=1)

    return all_df

la modélisation

J'ai choisi lightgbm comme modèle car c'était ennuyeux.

Que puis-je utiliser avec d'autres modèles, nn je dois normaliser si je l'utilise ... Hmm, gradient boosting!

C'est un circuit de pensée.

Cependant, j'ai fait le minimum pour le faire.

Le meilleur MSE final était d'environ "0,014".

En d'autres termes, le carré de l'erreur pour un trivia est d'environ 0,014, donc si vous prenez la route, il est d'environ 0,118, et comme il a été normalisé, multipliez par 100, 11,8.

Par conséquent, l'erreur était d'environ 11,8. (Hé ...)

Le code ressemble à ceci.

train_lgb.py


import os

import optuna
import numpy as np
import pandas as pd
import lightgbm as lgb
from sklearn.model_selection import KFold

from data.loader import load_data
from data.feature  import get_features
from data.trivias_list import trivias_list



def objective(trial):

    max_depth = trial.suggest_int('max_depth', 1, 20)
    learning_rate = trial.suggest_uniform('learning_rate', 0.001, 0.1)
    params = {
        'metric': 'l2',
        'num_leaves': trial.suggest_int("num_leaves", 2, 70),
        'max_depth': max_depth,
        'learning_rate': learning_rate,
        'objective': 'regression',
        'verbose': 0
    }

    mse_list = []
    kfold = KFold(n_splits=5, shuffle=True, random_state=1)
    for train_idx, valid_idx in kfold.split(X, y):
        X_train = X.iloc[train_idx]
        y_train = y.iloc[train_idx]
        X_valid = X.iloc[valid_idx]
        y_valid = y.iloc[valid_idx]
        lgb_train = lgb.Dataset(X_train, y_train)
        lgb_valid = lgb.Dataset(X_valid, y_valid)

        model = lgb.train(params,
                          lgb_train,
                          valid_sets=lgb_valid,
                          verbose_eval=10,
                          early_stopping_rounds=30)

        # f-measure
        pred_y_valid = model.predict(X_valid, num_iteration=model.best_iteration)
        true_y_valid = np.array(y_valid.data.tolist())
        mse = np.sum((pred_y_valid - true_y_valid)**2) / len(true_y_valid)
        mse_list.append(mse)

    return np.mean(mse_list)

def build_model():

    study = optuna.create_study()
    study.optimize(objective, n_trials=500)

    valid_split = 0.2
    num_train = int((1-valid_split)*len(X))
    X_train = X[:num_train]
    y_train = y[:num_train]
    X_valid = X[num_train:]
    y_valid = y[num_train:]
    lgb_train = lgb.Dataset(X_train, y_train)
    lgb_valid = lgb.Dataset(X_valid, y_valid)

    lgb_data = lgb.Dataset(X, y)

    params = study.best_params
    params['metric'] = 'l2'
    model = lgb.train(params,
                      lgb_data,
                      valid_sets=lgb_valid,
                      verbose_eval=10,
                      early_stopping_rounds=30)
    
    return model

X, y = load_data(trivias_list)

if __name__ == "__main__":

    model = build_model()
    
    content = 'Le miel que les abeilles recueillent au cours de leur vie est d'environ une cuillère à café.'
    content_df = get_features(trivias_list, content=content, mode='inference')
    output = model.predict(content_df)
    hee = int(output*100)

    print(f"{content}")
    print(f"{hee}Hey")

Anti-motif

En créant le modèle d'apprentissage automatique, je décrirai les anti-modèles qui ont échoué. J'espère que cela vous sera utile à vous et à vous-même à l'avenir.

Échec ① Mise à l'échelle

En tant que mise à l'échelle de la variable objective, je l'ai réglé sur «he number / full he». Heureusement, j'ai été immédiatement remarqué, mais au début j'étais tout à fait prêt à utiliser «he number» comme variable objective. Si tel était le cas, j'aurais renoncé à dire "Eh bien, c'est ..." sans obtenir l'exactitude.

Échec: ajustement oublié de word_vectorizer

Je suis désolé, c'est une histoire très détaillée ... Cependant, c'est mon deuxième échec, alors laissez-moi écrire ...

Lors de l'incorporation de tfidf à partir d'une phrase, vous devez suivre les trois étapes suivantes.

#Génération d'instance
word_vectorizer = TfidfVectorizer(max_features=max_features)
#Entrez dans la liste de textes divisés,En forme.
word_vectorizer.fit(wakati_contents_list)
#Incorporer la liste de texte partitionnée souhaitée.
tfidf = word_vectorizer.transform(wakati_contents_list)

Puisque tfidf doit prendre en compte la rareté d'un mot dans la phrase parmi toutes les phrases, il est nécessaire de passer toutes les phrases à l'instance du vectorizer une fois, c'est-à-dire "incorporer cette phrase". Vous ne pouvez pas simplement passer une phrase.

Cependant, je ne suis pas directement connecté à l'image que tfidf est un processus déterministe et doit être ajusté, et j'essaie de l'intégrer avec transform de manière appropriée ...

Tout le monde devrait être prudent.

Échec ③ Supprimer les fonctionnalités inutilisées

Je me demandais s'il fallait écrire ceci parce que c'est trop basique, mais c'est un fait qu'il est resté bloqué pendant environ 1 minute, alors je vais l'écrire.

Oui ici.

all_df = all_df.drop('id', axis=1)
all_df = all_df.drop('hee', axis=1)
all_df = all_df.drop('man_hee', axis=1)
all_df = all_df.drop('content', axis=1)
all_df = all_df.drop('wakati_content', axis=1)

Les informations qui ne sont pas saisies dans lightgbm, telles que ʻid, content` (chaîne de caractères d'origine), etc., doivent être correctement supprimées du bloc de données pandas.

Je pense que c'est étonnamment facile à faire.

Dans le processus de génération de fonctionnalités, nous ajoutons hoihoi à la trame de données pandas comme indiqué ci-dessous, il est donc facile d'oublier qu'il y a encore des choses inutiles (peut-être parce que nous sommes nouveaux dans les pandas. J'ai trop appris.)

all_df['num_eigo'] = num_eigo_df.values.tolist()

Libération

Eh bien, comme mentionné ci-dessus, le produit fini a été publié sur Google Colab.

Je l'ai intégré dans un code unique avec stickytape et l'ai conçu pour qu'il puisse être lu avec une interface utilisateur facile à comprendre, même sur Google Colab. Il y a un article que j'ai écrit sur stickytape hier, alors jetez un œil.

Créez plusieurs fichiers python en un seul fichier python @wataoka

Expérience!

Maintenant que je l'ai fait, jouons avec moi-même.Après avoir appris, je vais entrer les anecdotes que j'ai dans Hee-AI et les laisser en déduire.

Dure ... スクリーンショット 2020-06-26 16.06.43.png

スクリーンショット 2020-06-26 16.05.22.png

Impressions

Dans l'ensemble, c'est une évaluation difficile, je n'ai jamais atteint 90. Est-ce que 5 sur 5 sont Tamori-san?

Faites-moi savoir quand quelqu'un atteint 90 ans.

Auto-introduction

Si vous l'écrivez au début, cela vous gênera, alors laissez-moi vous présenter tranquillement à la fin.

Nom Aki Wataoka
école École supérieure de l'université de Kobe
Recherche de premier cycle Apprentissage automatique,Traitement de la voix
Recherche diplômée Apprentissage automatique,justice,Modèle de génération, etc
Twitter @Wataoka_Koki

Suivez-nous sur Twitter!

Recommended Posts

J'ai créé une IA qui prédit des anecdotes et m'a fait déduire mes anecdotes. Hee-AI
J'ai créé une application qui m'avertit si je joue avec mon smartphone pendant mes études avec OpenCV
J'ai créé une IA qui recadre joliment une image en utilisant Saliency Map
J'ai créé un robot Line qui devine le sexe et l'âge d'une personne à partir de l'image
Avec LINEBot, j'ai fait une application qui m'informe de "l'heure du bus"
J'ai créé un package extenum qui étend enum
J'ai créé mon propre middleware Django afin de pouvoir accéder aux informations de demande de n'importe où
J'ai créé une application Android qui affiche Google Map
J'ai créé un installateur Ansible
Des livres et des liens qui m'ont aidé à faire mes débuts avec PyPI
J'ai créé un modèle de détection d'anomalies qui fonctionne sur iOS