ディープラーニングでネガポジ分析アプリを作ってみた(python)【前編】

スポンサードリンク



こんにちは。sinyです。

この記事ではchABSA-datasetという日本語のデータセットを使ってネガポジ分類アプリを作った際のまとめ記事です。
今回は、自然言語処理(NLP)のTransformerという手法を用いてDjangoでネガポジ分類アプリを作ってみました。

早速ですが、まずはデモ動画から。

 

chABSA-datasetデータセットから有価証券報告に関するインプットデータを入力すると、入力した文章が「Negative or Positive」かを判定します。

また、Transformerで利用されているAttentionの重み値を利用して、文章にAttentionが強くかかっている(=Attentionの重み値が大きい)単語の背景色をより濃い赤色に着色することで「どの単語を重視して判定したか?」を可視化しています。

chABSA-datasetについては以下の関連記事を参照してください。

 

本アプリは書籍「つくりながら学ぶ! PyTorchによる発展ディープラーニング」の「第7章:自然言語処理による感情分析(Transformer)」を元に作成していますので、基本コードについては書籍を参照してください。

   

上記書籍では、英語の映画レビューのデータセット(IMDB)を使うことを前提に書かれていますが、実務などで活用することを考えると日本語データセットでモデルを構築したいところです。

そこで、書籍のコードをベースに日本語モデルの感情分析モデルの構築を行いDjangoアプリ化まで実施してみました。

Transformerの詳細や細かいコード内容については書籍のコードを見ていただくこととし、「つくりながら学ぶ! PyTorchによる発展ディープラーニング」のコードをベースに「どこをどのようにカスタマイズすれば日本語データセットのモデルを構築できるか?」を中心に解説していきます。

なお、書籍のコードについては以下のGithubで公開されていますのである程度知識のある方はソースを見ればわかると思います。
初級者の方は、一度書籍を元に英語ベースのモデル構築を学習後に本記事の内容を試してみることをお勧めします。

日本語モデルに作り直した全コードは私のGithubで公開しています。

 発展ディープラーニングの著者である小川さんとは何度か実際にお会いしたことがありますが、とても丁寧に教えてくださる方です!
書籍についてもかなり丁寧に解説してくれていますので、ディープラーニングを一歩深く学びたい方にはお勧めです。

 

chABSA-datasetから訓練、テストデータを作成

まずは、chABSA-datasetから訓練、テストデータを作成します。

作成手順については以下の関連記事で紹介していますので参考にしてみてください。

上記手順を踏むと、以下のデータが生成されます。

  • train.tsv(訓練データ7割)
  • test.tsv(テストデータ3割)

 

chABSA-datasetからDataLoaderを作成

続いて、作成したtrain.tsvとtest.tsvを元にpytorchでDataLoaderを作成します。

今回は日本語データが対象ということもあり、以下の点が書籍とは異なっています。

単語分割にMeCab(neologd)を利用する。

日本語データの形態素解析用にMeCabを利用するように変更しています。
具体的には以下のtokenizer_mecabメソッドを新たに定義しています。

import MeCab

j_t = Tokenizer()


def tokenizer_mecab(text):
    m_t = MeCab.Tagger('-Owakati -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd')
    text = m_t.parse(text)  # これでスペースで単語が区切られる
    ret = text.strip().split()  # スペース部分で区切ったリストに変換
    return ret

テキストデータの前処理をカスタマイズ

 

テキストデータの前処理はpreprocessing_textメソッドに定義されていますが、以下の処理を追加しました。

・preprocessing_textメソッドにテキストデータすべて半角→全角へ変換する処理を追加(ライブラリーmojimojiを利用)
・preprocessing_textメソッドに数値を0化する処理を追加。

※text = re.sub(r'[0-9 0-9]+', '0', text) 

前処理関数を以下のように変更しています。

# 前処理関数
def tokenizer_with_preprocessing(text):
    text = preprocessing_text(text)  # 前処理の正規化
    ret = tokenizer_mecab(text)  # MeCabの単語分割

    return ret

 

これで、日本語の入力データを以下のように形態素解析してくれます。

==================================================
原文
==================================================
また、当事業年度末の店舗数につきましては、前事業年度において決定しておりました2店舗、大幅な修繕を要する1店舗及び業績不振の6店舗を加えた合計9店舗を閉鎖しました結果、133店舗となり前事業年度末に比べ9店舗減少いたしました
==================================================
処理後
==================================================
['また', '、', '当', '事業年度', '末', 'の', '店舗', '数', 'に', 'つき', 'まし', 'て', 'は', '、', '前', '事業年度', 'において', '決定', 'し', 'て', 'おり', 'まし', 'た', '0', '店舗', '、', '大幅', 'な', '修繕', 'を', '要する', '0', '店舗', '及び', '業績不振', 'の', '0', '店舗', 'を', '加え', 'た', '合計', '0', '店舗', 'を', '閉鎖', 'し', 'まし', 'た', '結果', '、', '0', '店舗', 'と', 'なり', '前', '事業年度', '末', 'に', '比べ', '0', '店舗', '減少', 'いたし', 'まし', 'た']

 

DataLoaderの作成

続いてDataLoaderの作成です。

ここは、書籍のコードと同じです。

import torchtext

max_length = 256
TEXT = torchtext.data.Field(sequential=True, tokenize=tokenizer_with_preprocessing, use_vocab=True,
                            lower=True, include_lengths=True, batch_first=True, fix_length=max_length, init_token="<cls>", eos_token="<eos>")
LABEL = torchtext.data.Field(sequential=False, use_vocab=False)
# init_token:全部の文章で、文頭に入れておく単語
# eos_token:全部の文章で、文末に入れておく単語

train_ds, test_ds = torchtext.data.TabularDataset.splits(
    path='./data/', train='train.tsv',
    validation='test.tsv', format='tsv',
    fields=[('Text', TEXT), ('Label', LABEL)])

 

なお、データ数は以下の通りです。

訓練データの数 1970
テストデータの数 843

 

fastTextの学習済み日本語モデルを利用する。

書籍ではfastTextの英語版の学習済みモデルを利用していましたが、今回は日本語データを扱うため日本語学習済みのfastTextモデル(model.vecファイル)を使います。

ダウンロード手順は以下の通りです。

1.fastTextの日本語学習済みモデルをhttps://qiita.com/Hironsan/items/8f7d35f0a36e0f99752cからダウンロードする。

2.「URL2:Download Word Vectors(NEologd)」部分のリンクhttps://drive.google.com/open?id=0ByFQ96A4DgSPUm9wVWRLdm5qbmcからGoogle Driveのリンクに飛んで「vector_neologd.zip」をダウンロードして解凍したfastTextの日本語学習済みモデル「model.vec」を利用する。

日本語のボキャブラリーを作成する。

ここも書籍とほぼ同じコードですが、読み込むモデルを「model.vec」に変更します。

# torchtextで単語ベクトルとして英語学習済みモデルを読み込みます

from torchtext.vocab import Vectors

japanese_fastText_vectors = Vectors(name='./data/model.vec')


print("1単語を表現する次元数:", japanese_fastText_vectors.dim)
print("単語数:", len(japanese_fastText_vectors.itos))

 

日本語学習済みのfastTextモデルは、以下の通り300次元になっていますが、書籍で扱っている英語版は200次元なのですべてのコードで200次元になっている箇所は300次元を扱うように変更する必要があります。

1単語を表現する次元数: 300
単語数: 351122

 

あとは、書籍と同じように以下のコードで日本語データのボキャブラリーデータセットが出来上がります。

# ベクトル化したバージョンのボキャブラリーを作成
TEXT.build_vocab(train_ds, vectors=japanese_fastText_vectors, min_freq=5)

# ボキャブラリーのベクトルを確認
print(TEXT.vocab.vectors.shape)  # 1015474個の単語が200次元のベクトルで表現されている
TEXT.vocab.vectors

# ボキャブラリーの単語順番を確認
TEXT.vocab.stoi

 

 {'<unk>': 0,
             '<pad>': 1,
             '<cls>': 2,
             '<eos>': 3,
             '0': 4,
             '、': 5,
             'の': 6,
             'は': 7,
             'た': 8,
             'まし': 9,
             '円': 10,
~省略~~~

 

データローダの作成も書籍と基本的に同じです。

# DataLoaderを作成
train_dl = torchtext.data.Iterator(train_ds, batch_size=8, train=True)

val_dl = torchtext.data.Iterator(
    test_ds, batch_size=8, train=False, sort=False)


# 動作確認 検証データのデータセットで確認

batch = next(iter(val_dl))
print("="*50)
#単語が数字に変換され、max_lengthに満たない部分はpad=1になっていることが確認できる。
print(batch.Text[0][2])
print("="*50)
print(batch.Label)

 

以下の通り、TEXT部分にINDEX化された日本語データが、LABEL部分にネガティブorポジティブを表す1,0データセットが生成されます。

==================================================
tensor([  2,  69,   5, 252,  72,   7,   5, 172,   6,   0,  14, 348,  52, 246,
          5, 112,   7,   0,  30,   0, 186,  23,   0,  11,  16,  28,  78,   5,
        169,  66, 185, 177,   0,  41,   0,  14,   0, 124, 316,  88,  11,  82,
         79,   3,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
          1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
          1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
          1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
          1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
          1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
          1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
          1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
          1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
          1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
          1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
          1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
          1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
          1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
          1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
          1,   1,   1,   1])
==================================================
tensor([0, 0, 0, 0, 0, 0, 0, 0])

 

Transformerモデルによるネガポジ分類実装

モデル実装部分も基本的に書籍と同じですが、これまで説明してきた通り日本語データセットを扱うようなget_chABSA_DataLoaders_and_TEXTメソッドを新規に作成しています。

def get_chABSA_DataLoaders_and_TEXT(max_length=256, batch_size=8):
    """chABSAのDataLoaderとTEXTオブジェクトを取得する。 """


    def preprocessing_text(text):
        
        # 半角・全角の統一
        text = mojimoji.han_to_zen(text) 
        # 改行、半角スペース、全角スペースを削除
        text = re.sub('\r', '', text)
        text = re.sub('\n', '', text)
        text = re.sub(' ', '', text)
        text = re.sub(' ', '', text)
        # 数字文字の一律「0」化
        text = re.sub(r'[0-9 0-9]+', '0', text)  # 数字

        # カンマ、ピリオド以外の記号をスペースに置換
        for p in string.punctuation:
            if (p == ".") or (p == ","):
                continue
            else:
                text = text.replace(p, " ")

        return text

    # 分かち書き
    def tokenizer_mecab(text):
        m_t = MeCab.Tagger('-Owakati -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd')
        text = m_t.parse(text)  # これでスペースで単語が区切られる
        ret = text.strip().split()  # スペース部分で区切ったリストに変換
        return ret

    # 前処理と分かち書きをまとめた関数を定義
    def tokenizer_with_preprocessing(text):
        text = preprocessing_text(text)  # 前処理の正規化
        ret = tokenizer_mecab(text)  # MeCabの単語分割

        return ret


    # データを読み込んだときに、読み込んだ内容に対して行う処理を定義します
    max_length = 256
    TEXT = torchtext.data.Field(sequential=True, tokenize=tokenizer_with_preprocessing, use_vocab=True,
                            lower=True, include_lengths=True, batch_first=True, fix_length=max_length, init_token="<cls>", eos_token="<eos>",unk_token='<unk>')
    LABEL = torchtext.data.Field(sequential=False, use_vocab=False)

    # フォルダ「data」から各tsvファイルを読み込みます
    train_ds, val_ds = torchtext.data.TabularDataset.splits(
        path='./data/', train='train.tsv',
        validation='test.tsv', format='tsv',
        fields=[('Text', TEXT), ('Label', LABEL)])

    # torchtextで日本語ベクトルとして日本語学習済みモデルを読み込む
    japanese_fastText_vectors = Vectors(name='./data/model.vec')

    # ベクトル化したバージョンのボキャブラリーを作成します
    TEXT.build_vocab(train_ds, vectors=japanese_fastText_vectors, min_freq=5)

    # DataLoaderを作成します(torchtextの文脈では単純にiteraterと呼ばれています)
    train_dl = torchtext.data.Iterator(
        train_ds, batch_size=batch_size, train=True)
    
    val_dl = torchtext.data.Iterator(
        val_ds, batch_size=batch_size, train=False, sort=False)


    return train_dl, val_dl, TEXT

 

また、fastText日本語学習済みモデルは300次元なので、PositionalEncoder、Attention、ClassificationHeadクラス内で定義している「d_model=200」という部分をすべて300次元に変更しています。

モデル実装部分で変更している点は以上です。

 

 Transformerの学習・推論、判定根拠の可視化

学習、推論、可視化の部分についてはほぼ書籍通りの内容でそのまま利用できました。

ちなみにchABSA-datasetデータセットを利用して学習した結果は以下の通りです。

30エポック学習させましたが、正解率は85%前後で頭打ちになりました。
今回は14エポック目でベストスコア(バリデーションの正解率85.8%)となりました。

以上でchABSA-datasetデータセットを用いたTransformerによるネガポジ分類機の実装と学習、可視化までが完了します。

詳細なコードは以下のGithubで公開していますので気になる方はぜひチャレンジしてみてください。

記事が長くなってしまったので、次回の記事でDjangoアプリ実装部分を簡単に解説したいと思います。

 

 

おすすめの記事