J'ai créé Hee-AI.
Ce Google Colab est publié non modifiable. Veuillez le copier sur votre disque et jouer avec! (Environnement PC recommandé)
Le code source est également disponible, veuillez donc vous y référer pour référence.
Tout cela se passe le 26 juin 2020.
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 ... !!!".
J'ai immédiatement lancé le bureau.
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.
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.
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.
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.
continue
. Lisez ligne par ligne tout en effectuant le traitement des exceptions.get_text
.
--Store id, phrases, nombre d'enfer, etc. dans trivial_list
.C'est un flux.
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
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")
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.
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.
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.
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()
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
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 ...
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.
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 |
@Wataoka_Koki |
Suivez-nous sur Twitter!
Recommended Posts