[PYTHON] Ich habe eine KI erstellt, die aus Trivia vorhersagt, und mich dazu gebracht, auf meine Trivia zu schließen. Hee-AI

Hee-AI

Ich habe Hee-AI gemacht.

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

Dieses Google Colab wird unbearbeitet veröffentlicht. Bitte kopieren Sie es auf Ihr Laufwerk und spielen Sie damit! (PC-Umgebung empfohlen)

Google Colab-Link

Der Quellcode ist ebenfalls verfügbar. Bitte beziehen Sie sich als Referenz darauf.

Produktionsgeschichte

Dies alles geschieht am 26. Juni 2020.

Motivation

Mein Hobby ist es, Wikipedia zu erkunden, aber eines Tages [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) Alle früheren Trivia und deren Vorlieben wurden im Programm aufgeführt Ich habe herausgefunden, dass das aufgelistet ist.

Ohne zu zögern dachte ich: "Dies wird ein Rückgabeproblem sein ... !!!". スクリーンショット 2020-06-26 14.55.12.png

Ich habe den Desktop sofort gestartet.

planen

Am Ende wollte ich, dass viele Leute spielen, also dachte ich zuerst darüber nach, es zu einem Webdienst zu machen, aber ich wollte meine Recherche nicht länger als einen Tag einstellen, also gab ich es auf. Führen Sie es auf Google Colab aus Indem ich es möglich machte, konnte ich es leicht spielen, wenn ich es auf jedes Laufwerk kopieren ließ, also machte ich einen Plan auf dieser Route.

Google Colab Google Colab (Google Colaboratory) ist ein von Google bereitgestellter Dienst, mit dem Sie Jupyter Notebook auf Google Drive auf dem Google-Computer ausführen können. Sie können auch die GPU verwenden. Dies ist sehr nützlich, da Sie nicht lokal berechnen müssen. Da es sich jedoch um ein Jupyter-Notizbuch handelt, ist es nicht für Leute wie mich geeignet, die hierarchische Strukturen lieben.

Aber diesmal kann ich das nicht sagen. Außerdem sollten Sie, wenn möglich, eine Zelle ausführen, um alles zu vervollständigen und den Benutzer so weit wie möglich zu entlasten. Machen Sie jedoch den Lernprozess und den Inferenzprozess zu derselben Zelle. In diesem Fall wird das Lernen jedes Mal durchgeführt, wenn Sie darauf schließen, sodass Sie nur diesen Teil trennen müssen. Daher haben wir als ausgefülltes Formular Google Colab geplant, das nur aus zwei Zellen besteht (siehe Abbildung unten).

Und ich wollte es mit der Verarbeitung veröffentlichen, die so weit wie möglich weggelassen werden kann. Deshalb habe ich beschlossen, das Github-Repository mit einer Verzeichnisstruktur in Google Golab nicht zu klonen, sondern es bis auf das Installationssystem zu vervollständigen. Daher werden Wikipedia-Daten usw. auch zuerst im Python-Wörterbuchtyp gespeichert, und ein Python-Paket namens stickytape wird verwendet, um eine Python-Datei zu erstellen, und es wurden verschiedene Ideen entwickelt.

Qiita Sommerfest 2020

Daher wurde es zu einer starken Einschränkung, eine KI-Anwendung nur mit Python auf Colab zu erstellen, und zwar auf dem Qiita Summer Festival 2020, das derzeit stattfindet Ich habe mich auch entschieden, mich zu bewerben.

In diesem Projekt suchen wir nach Artikeln, die auf den folgenden drei Themen basieren.

  1. Wenn Sie △△ (App) jetzt nur mit 〇〇 (Sprache) erstellen möchten
  2. Teilen Sie vergangene Fehler in der Systementwicklung und wie Sie sie überwinden können!
  3. [Maschinelles Lernen] "Nicht tun" Lassen Sie uns Anti-Muster teilen!

Ich fing an zu schreiben und dachte, ich sei 1., aber während ich es tat, dachte ich, ich könnte das Anti-Muster des maschinellen Lernens von 3 berühren. Also denke ich, ich werde auch ein Thema dazu setzen.

Die Geschichte war es.

Kehren wir zur Produktionsgeschichte zurück.

Schaben

Zuerst habe ich HTML kopiert und ungefähr 15 Minuten lang gekämpft, um zu sehen, ob es automatisch mit Vim-Makros formatiert werden kann, aber es gab einige Ausreißer, und ich habe mich gefragt, ob die Programmierung besser wäre, um damit umzugehen. Ich gab auf.

Der Scraping-Code, den ich tatsächlich geschrieben habe, sieht so aus.

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)

Grob gesagt,

--Verwenden Sie die Datei "oup.find () "von BeautifulSoup, um die Tabelle, auf der sich alle Trivia befinden, auf der Wikipedia-Trivia-Seite zu finden.

Es ist ein Fluss.

Feature Quantity Engineering

Jetzt, da wir alle Daten haben, ist es ein Schaufenster für einen Maschinenlerner, Feature Quantity Engineering. Um ehrlich zu sein, ich wusste es, bevor ich es tat, aber ich konnte keine gute Genauigkeit erzielen. Es gibt höchstens 1000 Sätze mit ungefähr 20 Zeichen. Es ist jedoch absolut unmöglich, dies zu tun. Um jedoch die geringste Reduzierung zu erzielen, haben wir die Merkmalsmengen wie folgt extrahiert.

ist.

Dies ist die Zielvariable für die letzte "er Nummer / voll er". Wenn die Nummer von ihm so verwendet wird, wie es ist, gab es Zeiten, in denen 200 er beim Sondertreffen des Trivia-Brunnens voll war. Die Skala ist unterschiedlich. Daher ist die Zielvariable der auf 0-1 normierte Wert, z. B. "Anzahl der Haare / volles Haar".

Der Code sieht so aus.

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

Modellieren

Ich habe lightgbm als Modell gewählt, weil es nervt.

Was kann ich mit anderen Modellen verwenden, nn muss ich normalisieren, wenn ich es verwende ... Hmm, Gradientenverstärkung!

Es ist eine Gedankenschaltung.

Ich habe jedoch das Minimum getan, um es zu tun.

Die endgültig beste MSE lag bei 0,014.

Mit anderen Worten, das Quadrat des Fehlers für eine Kleinigkeit beträgt ungefähr 0,014. Wenn Sie also die Route nehmen, beträgt sie ungefähr 0,118, und da sie normalisiert wurde, multiplizieren Sie sie mit 100, 11,8.

Daher war der Fehler ungefähr 11.8. (Hey ...)

Der Code sieht so aus.

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 = 'Der Honig, den die Bienen im Laufe ihres Lebens sammeln, ist ungefähr ein Teelöffel.'
    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}Hallo")

Anti-Muster

Bei der Erstellung des Modells für maschinelles Lernen werde ich die fehlgeschlagenen Anti-Muster beschreiben. Ich hoffe, dass es Ihnen und Ihnen in Zukunft nützlich sein wird.

Fehler ① Skalierung

Als Skalierung der Zielvariablen habe ich sie auf "er Zahl / voll er" gesetzt. Glücklicherweise war ich sofort vorsichtig, aber zuerst war ich vollständig darauf vorbereitet, "die Zahl" als Zielvariable zu verwenden. Wenn ja, hätte ich es aufgegeben, ohne Genauigkeit zu sagen: "Nun, das ist ...".

Fehler ② Passung von word_vectorizer vergessen

Es tut mir leid. Es ist eine sehr detaillierte Geschichte ... Dies ist jedoch mein zweiter Fehler, also lassen Sie mich schreiben ...

Wenn Sie tfidf aus einem Satz einbetten, müssen Sie die folgenden drei Schritte ausführen.

#Instanzgenerierung
word_vectorizer = TfidfVectorizer(max_features=max_features)
#Geben Sie die geteilte Textliste ein,Passen.
word_vectorizer.fit(wakati_contents_list)
#Betten Sie die gewünschte partitionierte Textliste ein.
tfidf = word_vectorizer.transform(wakati_contents_list)

Da tfidf berücksichtigen muss, wie selten ein Wort aus allen Sätzen im Satz vorhanden ist, müssen alle Sätze einmal an die Vektorisierungsinstanz übergeben werden. Das heißt, "diesen Satz einbetten". Sie können nicht nur einen Satz schreiben.

Ich bin jedoch nicht direkt mit dem Bild verbunden, dass tfidf ein deterministischer Prozess ist und angepasst werden muss, und ich versuche, ihn mit Transformation entsprechend einzubetten ...

Jeder sollte vorsichtig sein.

Fehler ③ Löschen Sie nicht verwendete Funktionen

Ich habe mich gefragt, ob ich das schreiben soll, weil es zu einfach ist, aber es ist eine Tatsache, dass es ungefähr 1 Minute lang hängen geblieben ist, also werde ich es schreiben.

Ja, hier.

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)

Informationen, die nicht in lightgbm eingegeben wurden, wie z. B. "id", "content" (ursprüngliche Zeichenfolge) usw., sollten ordnungsgemäß aus dem Pandas-Datenrahmen gelöscht werden.

Ich denke, das ist überraschend einfach.

Beim Generieren von Features fügen wir dem Pandas-Datenrahmen wie unten gezeigt Hoihoi hinzu, sodass leicht vergessen werden kann, dass wir es nicht benötigen. (Vielleicht, weil wir neu in Pandas sind. Deep Ich habe zu viel gelernt.)

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

Veröffentlichung

Wie oben erwähnt, wurde das fertige Produkt auf Google Colab veröffentlicht.

Ich habe es mit stickytape in einen einzigen Code eingebaut und es so konzipiert, dass es auch in Google Colab mit einer leicht verständlichen Benutzeroberfläche abgespielt werden kann. Es gibt einen Artikel, den ich gestern über stickytape geschrieben habe.

Machen Sie mehrere Python-Dateien zu einer Python-Datei @wataoka

Experiment!

Nachdem ich es geschafft habe, lass uns selbst damit spielen. Nachdem du gelernt hast, gib die Trivia, die du hast, in Hee-AI ein und lass es schließen.

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

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

Impressionen

Insgesamt ist es eine schwierige Bewertung. Ich habe nie 90 erreicht. Ist 5 von 5 Tamori-san?

Bitte lassen Sie mich wissen, wenn jemand 90 erreicht.

Vorstellen

Wenn Sie es am Anfang schreiben, wird es Ihnen im Weg stehen, also lassen Sie mich mich am Ende ruhig vorstellen.

Name Aki Wataoka
Schule Kobe University Graduate School
Bachelorforschung Maschinelles Lernen,Sprachverarbeitung
Graduiertenforschung Maschinelles Lernen,Gerechtigkeit,Generationsmodell, etc
Twitter @Wataoka_Koki

Folge uns auf Twitter!

Recommended Posts

Ich habe eine KI erstellt, die aus Trivia vorhersagt, und mich dazu gebracht, auf meine Trivia zu schließen. Hee-AI
Ich habe eine App erstellt, die mich warnt, wenn ich während des Studiums mit OpenCV mit meinem Smartphone herumspiele
Ich habe eine KI erstellt, die ein Bild mit Saliency Map gut zuschneidet
Ich habe einen Linienbot erstellt, der das Geschlecht und das Alter einer Person anhand des Bildes errät
Mit LINEBot habe ich eine Anwendung erstellt, die mich über die "Buszeit" informiert.
Ich habe ein Extenum-Paket erstellt, das die Enumeration erweitert
Ich habe meine eigene Django Middleware erstellt, damit ich von überall auf Anforderungsinformationen zugreifen kann
Ich habe eine Android-App erstellt, die Google Map anzeigt
Ich habe einen Ansible-Installer gemacht
Bücher und Links, die mir bei meinem PyPI-Debüt geholfen haben
Ich habe ein Anomalieerkennungsmodell erstellt, das unter iOS funktioniert