概要
感情分析は自然言語処理の重要なタスクであり、分類問題として扱われます。ここでは、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)