MindSporeを使用したRNNによる感情分析

概要

感情分析は自然言語処理の重要なタスクであり、分類問題として扱われます。ここでは、MindSporeを使ってRNNネットワークをベースとした感情分析モデルを構築します。以下のような結果が期待できます。
入力: This film is terrible
正しいラベル: Negative
予測ラベル: Negative

入力: This film is great
正しいラベル: Positive
予測ラベル: Positive

データ準備

本節では、IMDB映画レビューのデータセットを使用します。このデータセットにはPositiveとNegativeの2つのカテゴリが含まれています。サンプルデータは以下の通りです。
レビュー ラベル
「戒烟」は、事前に設定されたアイデンティティを脱却することと、薬物撤収と同様であるかもしれません。北京にやってきた農村人の視点から考えると、クラスと成功は彼のルートから離れ、父親の俳優としての成功を遥かに上回ることへの誘惑となります。しかし、新しい人が слишком新しい場合、家族、歴史、自然、個人のアイデンティティから大きく切り離れることが求められると、問題が生じます。その後の分裂、現実と想像との間の混同、普通とヒーローとの不和は、一方で直感的なチェックの対象となり、もう一方で自己からの完全な逃避となります。 Negative
この映画は、リアルな人々が自分自身と自分のリアルな人生経験を描き、それを非常にうまく行い、過去を再体験するかのように見せているため、驚くほど素晴らしいです。賈洪生は自分自身を演じ、音楽と薬物以外のすべてを捨て、抑うつと闘いながら人生の意味を探し、特に最も自分のことを気にかける人々に対して怒っている。 Positive
自然言語の単語をエンコードするために、事前学習された単語ベクトルを使用します。ここではGloVe単語ベクトルを選択します。

データダウンロードモジュール

import os
import shutil
import requests
import tempfile
from tqdm import tqdm
from typing import IO
from pathlib import Path

cache_dir = Path.home() / '.mindspore_examples'

def http_get(url: str, temp_file: IO):
    req = requests.get(url, stream=True)
    content_length = req.headers.get('Content-Length')
    total = int(content_length) if content_length is not None else None
    progress = tqdm(unit='B', total=total)
    for chunk in req.iter_content(chunk_size=1024):
        if chunk:
            progress.update(len(chunk))
            temp_file.write(chunk)
    progress.close()

def download(file_name: str, url: str):
    if not os.path.exists(cache_dir):
        os.makedirs(cache_dir)
    cache_path = os.path.join(cache_dir, file_name)
    cache_exist = os.path.exists(cache_path)
    if not cache_exist:
        with tempfile.NamedTemporaryFile() as temp_file:
            http_get(url, temp_file)
            temp_file.flush()
            temp_file.seek(0)
            with open(cache_path, 'wb') as cache_file:
                shutil.copyfileobj(temp_file, cache_file)
    return cache_path

imdb_path = download('aclImdb_v1.tar.gz', 'https://mindspore-website.obs.myhuaweicloud.com/notebook/datasets/aclImdb_v1.tar.gz')

IMDBデータセットの読み込み

ダウンロードしたIMDBデータセットは`tar.gz`ファイルです。Pythonの`tarfile`ライブラリを使用して読み込み、訓練データとテストデータをそれぞれ格納します。
import re
import six
import string
import tarfile

class IMDBData():
    label_map = {"pos": 1, "neg": 0}
    def __init__(self, path, mode="train"):
        self.mode = mode
        self.path = path
        self.docs, self.labels = [], []
        self._load("pos")
        self._load("neg")

    def _load(self, label):
        pattern = re.compile(r"aclImdb/{}/{}/.*\.txt$".format(self.mode, label))
        with tarfile.open(self.path) as tarf:
            tf = tarf.next()
            while tf is not None:
                if bool(pattern.match(tf.name)):
                    self.docs.append(str(tarf.extractfile(tf).read().rstrip(six.b("\n\r"))
                                         .translate(None, six.b(string.punctuation)).lower()).split())
                    self.labels.append([self.label_map[label]])
                tf = tarf.next()

    def __getitem__(self, idx):
        return self.docs[idx], self.labels[idx]

    def __len__(self):
        return len(self.docs)

imdb_train = IMDBData(imdb_path, 'train')
len(imdb_train)

データセットの読み込み

`mindspore.dataset`の`GeneratorDataset`を使用してデータセットを読み込みます。
import mindspore.dataset as ds

def load_imdb(imdb_path):
    train_dataset = ds.GeneratorDataset(IMDBData(imdb_path, "train"), column_names=["text", "label"], shuffle=True, num_samples=10000)
    test_dataset = ds.GeneratorDataset(IMDBData(imdb_path, "test"), column_names=["text", "label"], shuffle=False)
    return train_dataset, test_dataset

train_dataset, test_dataset = load_imdb(imdb_path)

事前学習済み単語ベクトルの読み込み

事前学習済み単語ベクトルは、入力単語の数値表現です。ここではGloVeを使用します。
import zipfile
import numpy as np

def load_glove(glove_path):
    glove_100d_path = os.path.join(cache_dir, 'glove.6B.100d.txt')
    if not os.path.exists(glove_100d_path):
        with zipfile.ZipFile(glove_path) as z:
            z.extractall(cache_dir)

    embeddings = []
    tokens = []
    with open(glove_100d_path, encoding='utf-8') as gf:
        for line in gf:
            word, vec = line.split(maxsplit=1)
            tokens.append(word)
            embeddings.append(np.fromstring(vec, dtype=np.float32, sep=' '))
    embeddings.append(np.random.rand(100))
    embeddings.append(np.zeros((100,), np.float32))
    vocab = ds.text.Vocab.from_list(tokens, special_tokens=["<unk>", "<pad>"], special_first=False)
    embeddings = np.array(embeddings).astype(np.float32)
    return vocab, embeddings

glove_path = download('glove.6B.zip', 'https://mindspore-website.obs.myhuaweicloud.com/notebook/datasets/glove.6B.zip')
vocab, embeddings = load_glove(glove_path)

データセットの前処理

データセットをトークン化しましたが、モデルの訓練には適していないため、追加の前処理が必要です。 - すべてのトークンをインデックスIDに変換します。 - テキストシーケンスを統一した長さにします。不足している場合は``で埋め、長すぎる場合は切り取ります。 ここでは`mindspore.dataset`のインターフェースを使用します。
import mindspore as ms

lookup = ds.text.Lookup(vocab, unknown_token='<unk>')
padding = ds.transforms.PadEnd([500], pad_value=vocab.tokens_to_ids('<pad>'))
cast = ds.transforms.TypeCast(ms.float32)

train_dataset = train_dataset.map(operations=[lookup, padding], input_columns=['text'])
train_dataset = train_dataset.map(operations=[cast], input_columns=['label'])

test_dataset = test_dataset.map(operations=[lookup, padding], input_columns=['text'])
test_dataset = test_dataset.map(operations=[cast], input_columns=['label'])

train_dataset, valid_dataset = train_dataset.split([0.7, 0.3])

モデルの構築

データセットの前処理が完了したら、感情分析のためのモデルを設計します。ここでは、LSTMを使用して特徴抽出を行い、全結合層に通して分類を行います。

Embeddingレイヤー

Embeddingレイヤーは、インデックスIDに対応する重み行列のベクトルを検索します。
embedding_layer = nn.Embedding(vocab_size=len(vocab), embedding_dim=100, embedding_table=Tensor(embeddings))

LSTMレイヤー

LSTMは、RNNの一種で、長期的な依存関係を学習する能力があります。
lstm_layer = nn.LSTM(input_size=100, hidden_size=128, num_layers=1, batch_first=True)

Denseレイヤー

最後に、全結合層を使用して出力を2クラスの確率に変換します。
dense_layer = nn.Dense(in_channels=128, out_channels=1)

モデルの訓練と保存

モデルの訓練と評価のロジックを設計し、モデルを訓練します。
import mindspore.nn as nn
from mindspore import Model
from mindspore.train.callback import LossMonitor

net = nn.SequentialCell([
    embedding_layer,
    lstm_layer,
    nn.Flatten(),
    dense_layer,
    nn.Sigmoid()
])

loss_fn = nn.BCELoss()
optimizer = nn.Adam(net.trainable_params(), learning_rate=0.001)

model = Model(network=net, loss_fn=loss_fn, optimizer=optimizer, metrics={'acc': nn.Accuracy()})

model.train(epoch=5, train_dataset=train_dataset, callbacks=[LossMonitor()], dataset_sink_mode=True)

タグ: MindSpore RNN 感情分析 LSTM GloVe

6月1日 17:25 投稿