[PYTHON] Classification des documents avec une phrase

J'ai appliqué Sentence Piece, un tokenizer pour le traitement du langage neuronal, à la classification de documents.

Déclencheur

L'autre jour, j'ai découvert Phrase Piece comme option de fractionnement de mots dans le traitement du langage naturel. Il semble que la traduction automatique ait atteint un score qui dépassait la méthode conventionnelle de division des mots, et cela m'intéressait simplement, et je me demandais ce qui se passerait avec la classification de documents sur laquelle je travaille actuellement, alors je l'ai essayé.

SentencePiece(GitHub) Article de l'auteur taku910 (Qiita)

Les données

J'ai utilisé KNB Analyzed Blog Corpus. Il s'agit d'un corpus de blog analysé avec un total de 4 186 phrases réparties en quatre catégories: «Kyoto sightseeing», «mobile phone», «sports» et «gourmet», et comprend la morphologie et le cas. Cette fois, nous n'utiliserons que des catégories et des phrases pour résoudre le problème de classification de la catégorie à laquelle appartient chaque phrase. 10% de toutes les données ont été divisées en données de test, et les 90% restants ont été utilisés pour la formation de SentencePiece et de réseau neuronal.

la mise en oeuvre

Je l'ai exécuté sur Python 3.6.1 sur Bash sous Windows. Voir required.txt pour la version détaillée du module Python.

Le code est résumé dans GitHub. Je suis encore immature, alors j'apprécierais si vous pouviez signaler des erreurs ou me donner des conseils.

separator.py

En gros, SentencePiece semble être utilisé depuis la ligne de commande, mais je voulais l'utiliser depuis Python et je voulais l'utiliser facilement avec mecab, donc ce n'est pas très intelligent, mais je l'ai appelé à partir d'un sous-processus.

def train_sentencepiece(self, vocab_size):
    cmd = "spm_train --input=" + self.native_text + \
            " --model_prefix=" + self.model_path + \
            " --vocab_size=" + str(vocab_size)
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout_data, stderr_data = p.communicate()

Lorsque vous écrivez séparément à l'aide du modèle appris, le traitement de chaque ligne prend trop de temps, donc je l'écris une fois dans un fichier texte et je le fais en même temps. Je ne l'ai pas utilisé cette fois, mais j'ai également ajouté une fonction de fractionnement caractère par caractère pour l'apprentissage au niveau du personnage.

Cette fois, nous comparerons Sentence Piece et mecab + neologd

net.py

Je voulais utiliser LSTM pour le réseau, mais cela prend beaucoup de temps à apprendre, j'ai donc choisi CNN cette fois. Nous avons implémenté un réseau avec trois types de filtres: 3, 4 et 5 mots.

class CNN(Chain):

    def __init__(self, n_vocab, n_units, n_out, filter_size=(3, 4, 5), stride=1, use_dropout=0.5, ignore_label=-1):
        super(CNN, self).__init__()
        initializer = initializers.HeNormal()
        with self.init_scope():
            self.word_embed=L.EmbedID(n_vocab, n_units, ignore_label=-1)
            self.conv1 = L.Convolution2D(None, n_units, (filter_size[0], n_units), stride, pad=(filter_size[0], 0), initialW=initializer)
            self.conv2 = L.Convolution2D(None, n_units, (filter_size[1], n_units), stride, pad=(filter_size[1], 0), initialW=initializer)
            self.conv3 = L.Convolution2D(None, n_units, (filter_size[2], n_units), stride, pad=(filter_size[2], 0), initialW=initializer)
            self.norm1 = L.BatchNormalization(n_units)
            self.norm2 = L.BatchNormalization(n_units)
            self.norm3 = L.BatchNormalization(n_units)
            self.l1 = L.Linear(None, n_units)
            self.l2 = L.Linear(None, n_out)
        self.use_dropout = use_dropout
        self.filter_size = filter_size

    def forward(self, x, train):
        with using_config('train', train):
            x = Variable(x)
            x = self.word_embed(x)
            x = F.dropout(x, ratio=self.use_dropout)
            x = F.expand_dims(x, axis=1)
            x1 = F.relu(self.norm1(self.conv1(x)))
            x1 = F.max_pooling_2d(x1, self.filter_size[0])
            x2 = F.relu(self.norm2(self.conv2(x)))
            x2 = F.max_pooling_2d(x2, self.filter_size[1])
            x3 = F.relu(self.norm3(self.conv3(x)))
            x3 = F.max_pooling_2d(x3, self.filter_size[2])
            x = F.concat((x1, x2, x3), axis=2)
            x = F.dropout(F.relu(self.l1(x)), ratio=self.use_dropout)
            x = self.l2(x)
        return x

D'autres paramètres ont été définis comme suit.

Nombre d'unités Mini taille de lot max epoch WeightDecay GradientClipping Optimizer
256 32 30 0.001 5.0 Adam

résultat

Tokenizer mecab+neologd SentencePiece
Meilleure précision 0.68496418 0.668257773

Hmmm ... pas très bien Le nombre de phrases était-il trop petit parce que seules les données textuelles du train étaient utilisées pour l'apprentissage de SentencePiece? J'ai essayé d'apprendre le modèle de Sentence Piece avec jawiki (2017/05/01 dernière version).

Tokenizer mecab+neologd SentencePiece SentencePiece(Apprenez avec jawiki)
Meilleure précision 0.68496418 0.668257773 0.758949876

Cette fois, ça a l'air bien.

La précision pour chaque époque est la suivante.

img.png

Séparation

J'ai essayé d'échantillonner certaines des phrases qui étaient en fait divisées

【SentencePiece】
 C'est trop petit / te / press / press / spicy /.
 Combien / combien / lance / prend / ga / continuation / ku / kana / a / ♪
 Un autre / un / têtu / tension / ri /, / je pense / je pense /.

 [Phrase Piece (apprise sur jawiki)]
 Small / Sa / Too / Te / Button / Press / Spicy / Spicy / of /.
 Do / no / ku / rai / ya / ri / take / continue / no / kana / a / ♪
 A / et / Déjà / Un / Têtu / Zhang / Ri /, / Shi / Yo / Ka / To / Think / U /.

【mecab + neologd】
 Petit / trop / te / bouton / appuyez / épicé / de / est / est /.
 Combien / combien / échange / est / continue / / ou / hé / ♪
 Ah / et un autre / un / fais de mon mieux /, / allons / ou / pense / pense /.

Même avec la même phrase, je pense que l'apprentissage avec jawiki est plus détaillé. Mecab + neologd est sensuellement proche des humains, mais il est intéressant de noter que cela ne signifie pas que l'apprentissage des réseaux de neurones donnera de bons résultats.

à partir de maintenant

Cette fois, tous les ajustements de paramètres tels que le nombre d'unités ont été fixés, donc je pense qu'il est nécessaire de faire les ajustements appropriés et de comparer avec chaque meilleur. Aussi, comme je l'ai mentionné un peu dans la section sur les séparateurs, j'aimerais essayer une comparaison avec l'apprentissage au niveau des caractères. Après cela, j'ai utilisé une phrase différente des données du train pour apprendre le fragment de phrase, cette fois jawiki, mais j'aimerais vérifier comment d'autres phrases affectent la précision.

Note

Au début, je l'ai essayé sur CentOS (docker), mais je n'ai pas pu installer Sentence Piece avec succès. J'ai abandonné, mais il semble qu'il puisse être installé sur CentOS si vous incluez ce qui suit en plus de la procédure de GitHub.

$ yum install protobuf-devel boost-devel gflags-devel lmdb-devel

Recommended Posts

Classification des documents avec une phrase
Classification des documents avec texte toch de PyTorch
Génération de phrases avec GRU (keras)
Documenter le code Python avec Doxygen
[Classification de texte] J'ai essayé d'implémenter des réseaux de neurones convolutifs pour la classification des phrases avec Chainer
J'ai essayé d'implémenter la classification des phrases par Self Attention avec PyTorch
Apprentissage automatique par python (1) Classification générale
Modèle de classification simple avec réseau neuronal
Apprendre la catégorisation de documents avec la CLI spaCy
Classification d'images avec un jeu de données d'images de fond d'oeil grand angle
Langage naturel: Doc2Vec Part2 - Classification des documents
J'ai essayé la génération de phrases avec GPT-2