[PYTHON] Classer les publications Qiita sans analyse morphologique avec Tweet2Vec

table des matières

――Ce que je voulais faire --Obtenir des messages Qiita --Utiliser Tweet2Vec --Utiliser l'instance GPU

Ce que je voulais faire

――Je souhaite classer de courts documents japonais (tweets, etc.) --Je veux utiliser un réseau de neurones --Je veux le faire sans analyse morphologique

Lors du traitement des messages SNS, etc., il existe de nombreuses erreurs typographiques, omissions, argot, nouveaux mots, pictogrammes, caractères faciaux, langues étrangères, termes techniques, fluctuations de notation, etc., donc l'approche utilisant un analyseur morphologique semble être désavantageuse. En lisant des articles récents de PNL, il semble qu'ils apprennent au niveau du caractère plutôt qu'au niveau du mot, alors suivons cette tendance. Je pense que le japonais est plus avantageux que l'anglais car il contient une grande quantité d'informations par caractère.

Comme le document n'est pas trop long et qu'il semble difficile d'analyser la morphologie, j'ai décidé de classer les articles de Qiita uniquement par titre comme un sujet avec un certain degré de cohésion dans le sujet. Le texte de Qiita est markdown ou HTML, ce qui semble difficile à gérer, donc je ne l'utiliserai pas cette fois.

Obtenir les messages Qiita

Utilisez l 'API Qiita.

Étant donné que le texte et d'autres méta-informations peuvent être utilisés à l'avenir, j'ai enregistré l'intégralité du JSON renvoyé par l'API dans PostgreSQL en utilisant Peewee en Python.


from peewee import *
from playhouse.postgres_ext import PostgresqlExtDatabase, BinaryJSONField

db = PostgresqlExtDatabase(
    'mytable',
    user='myuser',
    password='mypassword',
    register_hstore=False,
    autocommit=True,
    autorollback=True
)
db.connect()

#Définition du modèle
class BaseModel(Model):
    """A base model that will use our Postgresql database"""
    class Meta:
        database = db

class QiitaItem(BaseModel):
    item_id = CharField(unique=True)
    created_at = DateTimeField(index=True)
    raw = BinaryJSONField()
    
    class Meta:
        db_table = 'qiita_items'

#Créer une table
db.create_tables([QiitaItem])

Appel de l'API Qiita

Notez que le nombre d'appels est restreint si vous n'utilisez pas de jetons. Notez que même si vous utilisez des jetons, la limite est de 1 000 demandes par heure.

import json, requests

url = 'http://qiita.com/api/v2/items'
headers = {'Authorization': 'Bearer myauthorizationtoken1234567890'}

#Pour le moment, les 10 derniers,Exemple d'obtention de 000 messages
#Pour plus de messages, le même champ de requête que la recherche Web peut être utilisé pour les paramètres, je ferai donc de mon mieux en l'utilisant pleinement.
for i in range(100):
    resp = requests.get(url, params={'per_page': 100, 'page': 1+i}, headers=headers)
    for item in resp.json():
        #S'il y a un caractère Null, l'enregistrement dans JSONB échouera, alors supprimez-le.
        item['body'] = item['body'].strip('\x00')
        item['rendered_body'] = item['rendered_body'].strip('\x00')
        try:
            QiitaItem.create_or_get(item_id=item['id'], created_at=item['created_at'], raw=item)
        except DataError:
            print(False)

Utilisez Tweet2Vec

Tout d'abord, je voudrais convertir le titre de Qiita, qui a différentes longueurs, en une représentation vectorielle avec un certain nombre de dimensions. Une fois qu'un modèle d'espace vectoriel est utilisé, diverses méthodes d'apprentissage automatique peuvent être appliquées.

Au départ, j'ai essayé le [Doc2Vec] de gensim (http://radimrehurek.com/gensim/models/doc2vec.html), mais la précision n'était pas bonne.

À la suite de recherches dans divers articles, j'ai trouvé une méthode utilisant RNN (GRU) appelée Tweet2Vec. De plus, le code est disponible sur GitHub. Dans l'article original, 2 millions de tweets ont été classés en 2000 types de balises de hachage, et la précision était supérieure à la méthode basée sur les mots.

La plupart des messages de Qiita ont plusieurs balises et la longueur du texte est à peu près la même que celle du tweet, j'ai donc pensé qu'il pourrait être utilisé.

Tweet2Vec est implémenté dans Theano + Lasagne (Python2.7)

Créer un fichier de données

Utilise 393 balises différentes avec plus de 100 messages. Sur les quelque 63 000 messages avec ces balises, 5% étaient destinés aux tests, 5% aux CV et les 90% restants à l'apprentissage.

Les publications en anglais et en chinois sont également mixtes, mais je ne les exclut pas en particulier. Le document original n'utilisait que des tweets anglais et incluait un prétraitement pour convertir les majuscules et les minuscules, mais cela ne se fait pas non plus.

Les données d'apprentissage contiennent 2281 types de caractères, et il semble que le nombre de dimensions d'entrée soit nettement plus petit que lors de l'utilisation de BOW.


from collections import Counter
from random import shuffle
import io

cnt = Counter()
for item in QiitaItem.select():
    for tag in item.raw['tags']:
        cnt[tag['name']] += 1

common_tags = [name for name, c in cnt.most_common(393)]

samples_all = []

for item in QiitaItem.select():
    tags = [tag['name'] for tag in item.raw['tags']]
    intersection = list(set(common_tags) & set(tags))
    if len(intersection) > 1:
        samples_all.append((item, intersection))
        
shuffle(samples_all)
n_all = len(samples_all)
n_test = n_cv = int(n_all / 20)
n_train = n_all - n_test - n_cv
samples_train = samples_all[:n_train]
samples_test = samples_all[n_train:(n_train+n_test)]
samples_cv = samples_all[(n_train+n_test):]

with io.open('misc/qiita_train.txt', 'w') as f:
    for item, tags in samples_train:
        for tag in tags:
            f.write(tag + '\t' + item.raw['title'] + '\n')

with io.open('misc/qiita_test.txt', 'w') as f:
    for item, tags in samples_test:
        f.write(','.join(tags) + '\t' + item.raw['title'] + '\n')

with io.open('misc/qiita_cv.txt', 'w') as f:
    for item, tags in samples_cv:
        f.write(','.join(tags) + '\t' + item.raw['title'] + '\n')

Courir

Trois types de scripts shell sont fournis, alors réécrivez le chemin du fichier à l'intérieur et exécutez-le.

Lors de l'apprentissage

./tweet2vec_trainer.sh

Lorsque vous voulez voir la précision

./tweet2vec_tester.sh

Lorsque vous souhaitez ajouter une balise (pour une balise correcte inconnue)

./tweet2vec_encoder.sh

En guise de mise en garde

Utiliser une instance GPU

Lorsque je me suis entraîné avec 8 000 lignes de fichier de données d'entraînement à titre d'essai, cela a pris environ 2,7 heures sur mon MacBook. Le fichier de données d'entraînement final compte environ 140000 lignes, et si vous l'entraînez sur une machine locale, cela prendra 48 heures, j'ai donc décidé de configurer une instance GPU sur AWS.

Installez Theano sur une instance GPU d'AWS en vous reportant à cet article. J'étais impatient car matplotlib n'est pas entré avec pip, mais j'ai entré avec sudo apt-get install python-matplotlib.

Si la connexion est coupée au milieu, ce sera un problème, alors filtrez et exécutez. Après cela, le terminal s'est écrasé comme si je le visais, mais grâce à cela, il était en sécurité.

screen -S "qiita"
./tweet2vec_trainer.sh

Terminé en 12 époques pendant environ 3,5 heures. Cela valait la peine car on estimait qu'il était 10 fois plus rapide.

Résultat de prédiction de balise

Précision de prédiction des balises

Données de test avec une précision de 72,10%, Une précision de 70,49% a été obtenue même avec des données CV qui n'étaient pas du tout référencées pendant la formation.

./tweet2vec_tester.sh (qiita_test.txt)

Precision @ 1 = 0.721040939384
Recall @ 10 = 0.777895906062
Mean rank = 4.29197080292

./tweet2vec_tester.sh (qiita_cv.txt)

Precision @ 1 = 0.704855601396
Recall @ 10 = 0.779064847138
Mean rank = 4.05744208188

La précision de la prédiction du hashtag de Tweet dans l'article original était d'environ 24%, elle semble donc assez précise même si le sujet est très différent.

Voici un exemple de prédiction de balises pour les données CV.

Un exemple qui peut être bien prédit

# Titre de l'article Balise réelle Balise prédite(TOP 10)
1 Comment exécuter un programme Java depuis Maven Java,Maven Maven,Java,java8,Eclipse,gradle,Android,Tomcat,Groovy,JavaEE,JUnit
2 Boucle infinie asynchrone en JavaScript JavaScript,Node.js JavaScript,HTML,jQuery,Node.js,CSS,HTML5,CoffeeScript,js,Ajax,es6
3 Que faire de l'achat d'un Mac au développement de Rails Mac,Rails Ruby,Rails,Mac,Rails4,rbenv,RubyOnRails,MacOSX,Zsh,homebrew,Gem
4 [Unity] Interface utilisateur de connaissances acquise en développant des jeux pour smartphones dans une société de jeux .NET,Unity3D,C#,Unity Unity,C#,Unity3D,.NET,Android,iOS,LINQ,VisualStudio,développement android,Java
5 Langue R-Calcul haute performance R,statistics R,statistics,Math,Calcul numérique,L'analyse des données,Ruby,statistiques,Traitement du langage naturel,Python,Langage NLP R
6 Configuration initiale du serveur Sakura VPS et construction de l'environnement LAMP PHP,MacOSX,Sakura VPS Sakura VPS,vps,Apache,CentOS,PHP,MySQL,Linux,CentOS6.x,WordPress,postfix
7 Cadrer avec animation(StoryBoard) Xcode,Objective-C iOS,Swift,Storyboard,Xcode,Objective-C,Xcode6,Android,UI,iOS8,iPhone
8 Bienvenue dans Reject Hell d'Apple, que j'ai trouvé en créant une application de rencontres Xcode,iOS iOS,Swift,Xcode,Objective-C,Android,iPhone,CocoaPods,JavaScript,Mac,AdventCalendar
9 Afficher les éléments non transparents à l'intérieur des éléments translucides HTML,CSS CSS,HTML,HTML5,CSS3,JavaScript,jQuery,bootstrap,Android,js,Java
10 Pour docker japonais golang 做 1 phrase chinoise individuelle golang,docker Go,golang,docker,Ruby,Slack,vagrant,Rails,GitHub,OSX,Erlang

―― Fondamentalement, si le nom de la balise apparaît dans le titre tel quel, il peut être correctement répertorié en tant que meilleur candidat.

Exemples qui ne peuvent pas être bien prédits

# Titre de l'article Balise réelle Balise prédite(TOP 10)
1 Un amateur complet a participé à la course principale ISUCON 5 golang,MySQL,Go,nginx iOS,Unity,C#,Objective-C,JavaScript,Swift,Ruby,.NET,IoT,JSON
2 Identifier les champignons comestibles MachineLearning,Python,matplotlib Linux,iOS,ShellScript,Ruby,Python,Objective-C,CentOS,PHP,Bash,Swift
3 Basculer l'entrée japonaise sur Alt droite dans gnome3(Style de clavier AX) CentOS,Linux JavaScript,Java,OSX,homebrew,Mac,api,HTML,Node.js,MacOSX,Intelligence artificielle
4 Les fichiers statiques sont mis en cache (sinon le cache du navigateur) VirtualBox,Apache JavaScript,IoT,CSS,Chrome,firefox,MacOSX,Windows,HTML5,jQuery,Arduino
5 Nom d'utilisateur de la personne qui a tweeté avec le hashtag souhaité(@Derrière la pièce)Obtenir Twitter,TwitterAPI,Gem,Ruby Ruby,Rails,AWS,JavaScript,Python,Go,Java,jq,golang,PHP
6 Forme en vue: could not find implicit value for parameter messages: play.api.i18n.Que faire lorsque des messages apparaissent Scala,PlayFramework Mac,MacOSX,OSX,Xcode,Android,Linux,Ruby,Ubuntu,Java,Windows

――Bien sûr, s'il y a trop peu d'indices ou trop vague dans le titre, la prédiction n'est pas bonne.

Essayez de publier un message similaire

Tweet2Vec donne une représentation vectorielle de chaque titre de message, donc sortons un article similaire basé sur la distance cosinus.

Modifiez un peu encode_char.py pour qu'il affiche les fichiers nécessaires.

encode_char.py



  print("Encoding...")
  out_data = []
  out_pred = []
  out_emb = []
  numbatches = len(Xt)/N_BATCH + 1
  for i in range(numbatches):
      xr = Xt[N_BATCH*i:N_BATCH*(i+1)]
      x, x_m = batch.prepare_data(xr, chardict, n_chars=n_char)
      p = predict(x,x_m)
      e = encode(x,x_m)
      ranks = np.argsort(p)[:,::-1]

      for idx, item in enumerate(xr):
          out_data.append(item)
          out_pred.append(' '.join([inverse_labeldict[r] for r in ranks[idx,:5]]))
          out_emb.append(e[idx,:])

  # Save
  print("Saving...")
  with open('%s/data.pkl'%save_path,'w') as f:
      pkl.dump(out_data,f)
  with io.open('%s/predicted_tags.txt'%save_path,'w') as f:
      for item in out_pred:
          f.write(item + '\n')
  with open('%s/embeddings.npy'%save_path,'w') as f:
      np.save(f,np.asarray(out_emb))

Environ 130 000 messages Qiita sont utilisés, y compris les messages qui n'ont pas été utilisés pour l'apprentissage ou les tests.

with io.open('../misc/qiita_all_titles.txt', 'w') as f:
    for item in QiitaItem.select():
        f.write(item.raw['title'] + '\n')

Réécrivez et exécutez pour que le résultat soit sorti dans un répertoire appelé qiita_result_all.

./tweet2vec_encoder.sh

Cela a pris environ 3 heures lorsque je l'ai exécuté sur ma machine locale. J'aurais dû également utiliser une instance GPU pour cela.

Le code Python qui affiche des articles similaires ressemble à ceci.

import cPickle as pkl
import numpy as np
from scipy import spatial
import random

with io.open('qiita_result_all/data.pkl', 'rb') as f:
    titles = pkl.load(f)

with io.open('qiita_result_all/embeddings.npy','r') as f:
    embeddings = np.load(f)

n = len(titles)
def most_similar(idx):
    sims = [(i, spatial.distance.cosine(embeddings[idx],embeddings[i])) for i in range(n) if i != idx]
    sorted_sims = sorted(sims, key=lambda sim: sim[1])
    print titles[idx]
    for sim in sorted_sims[:5]:
        print "%.3f" % (1 - sim[1]), titles[sim[0]]

Résultat d'exécution

Les nombres dans la colonne de gauche sont la similitude (1 est le maximum, -1 est le minimum), et la colonne de droite est le titre.

>>> most_similar(random.randint(0,n-1))
Plusieurs Google Maps et jquery wrap()Phénomène mystérieux lors de l'utilisation
0.678 Combiner plusieurs calendriers Google
0.619 [GM] Je veux connaître la latitude et la longitude des adresses recherchées sur Google Map.
0.601 Afficher rapidement Google Maps (version API v3)
0.596 Mesure de la vitesse lors de l'utilisation de jQuery Sizzle et de l'API de filtrage sur un navigateur de smartphone
0.593 Comment afficher Google Map sur le site à partir d'une adresse

>>> most_similar(random.randint(0,n-1))
Je veux juste ajouter scipy, mais c'est un mémo amusant. Ubuntu,Python3.
0.718 Installation de SciPy et matplotlib(Python)
0.666 SciPy+Créer un diagramme de dispersion 3D avec matplotlib(Python)
0.631 scipy est python 2.7.S'il est 8, l'installation de pip échouera
0.622 [Note] Déclaration future ~ Python ~
0.Essayez d'utiliser 610 scipy

>>> most_similar(random.randint(0,n-1))
Déplacez le texte de l'étiquette d'interface utilisateur multiligne vers le coin supérieur gauche
0.782 IB aligne le texte de l'étiquette de l'interface utilisateur
0.Exporter 699 NGUI UI Label caractère par caractère
0.624 Center UILabel texte dans Swift
0.624 Insérer un saut de ligne du texte IB à UILabel
0.624 Synchroniser le défilement de deux UITableViews

>>> most_similar(random.randint(0,n-1))
[Unity]Que faire avant de gérer votre projet avec git ou svn
0.810 Éléments de configuration lors de la gestion de Git avec Unity
0.800 [Unity]Ignorer les fichiers lors de la gestion avec git(.gitignore)Réglage
0.758 [Unity]Un exemple simple d'échange de messages sur un réseau
0.751 [Opération de base Unity] Touche V pratique et accrochage lors de la création d'un donjon de carte
0.746 [Unity]Comment charger un fichier texte avec un script et afficher son contenu

>>> most_similar(random.randint(0,n-1))
Dessiner un cercle à l'aide d'une matrice de rotation en Java
0.911 Dessin de ligne Java utilisant une matrice de rotation
0.898 Conversion mutuelle d'entiers et de tableaux d'octets en Java
0.897 Afficher la séquence de Fibonacci en Java
0.896 Dessin d'un cercle à l'aide d'une matrice de rotation Java 2D
0.873 Efficacité de la jointure de chaîne Java confirmée

>>> most_similar(random.randint(0,n-1))
Liste des chansons jouées sur WWDC14
0.830 Participation à la WWDC15
0.809 WWDC 2014 Remarque
0.J'ai essayé de résumer 797 WWDC 2015
0.772 Résumé des questions que j'ai posées à la WWDC 2015
0.744 L'e-mail rejeté de la WWDC 2016 était un e-mail gagnant

Considération et défis

«Je suis extrêmement reconnaissant que ce niveau de précision puisse être atteint simplement par un entraînement sans analyse morphologique, sans créer de dictionnaire utilisateur et avec presque aucun prétraitement. ――Peut-il être appliqué à des documents plus difficiles à traiter en langage naturel, comme le texte Qiita? ――Cette fois, je viens d'utiliser Tweet2Vec tel quel, donc je veux comprendre exactement le contenu et créer un meilleur modèle. ――Si vous l'utilisez comme produit réel, vous souhaitez également régler les hyper paramètres. Je veux combiner diverses méta-informations que j'ai ignorées cette fois.

Recommended Posts

Classer les publications Qiita sans analyse morphologique avec Tweet2Vec
[Python] Analyse morphologique avec MeCab
Analyse morphologique japonaise avec Python
[PowerShell] Analyse morphologique avec SudachiPy
Text mining avec Python ① Analyse morphologique
J'ai joué avec Mecab (analyse morphologique)!
Python: analyse morphologique simplifiée avec des expressions régulières
Text mining avec Python ① Analyse morphologique (re: version Linux)
Faire un bot d'analyse morphologique de manière lâche avec LINE + Flask
Collecte d'informations sur Twitter avec Python (analyse morphologique avec MeCab)
Classer les articles avec des balises spécifiées par Qiita par apprentissage non supervisé