BARTというモデルを聞いたことはありますか?自然言語処理の分野でデファクトスタンダードとなりつつあるモデルです。BARTは自然言語を翻訳・生成をすることができ、それらに対して高いスコアが出ているモデルです。今回はその自然言語から特徴抽出の部分を活用し、入力された自然言語がポジティブなのかネガティブなのかを識別させていこうと思います。
BERTとは?
BERT(Bidirectional Encoder Representations from Transformers)とは、2018年にGoogleが発表した自然言語処理モデルです。自然言語処理タスクといった翻訳、質疑応答、文書分類のタスクにおいて、軽量でかつ、ハイスコアを叩き出し注目されました。TransformerというAttention Networkを用いたモデルをベースに作られました。最近では、Googleの検索エンジンに組み込まれたり、チャットボットへの応用、中国語や日本語などにも対応しています。広告、サポートセンター、金融などへの導入が活発に行われています。
ポジネガ判定について
文章がポジティブな文面かネガティブは文面か、中性的な文面かを分類する自然言語処理タスクの1つです。これは感情分析の1つとして、アンケートやレビューに用いられる手法です。
例えば、
「甘くて美味しい。」
と
「甘ったるくて、胃もたれしそう。」
とでは同じ甘いを示していますが、「美味しい」というポジティブな表現と「胃もたれしそう」のネガティブな要素を含む表現があります。
これを分類することで、特定の商品に対する顧客の反応やその発言者自身の指向をざっくり分析することができます。
BERTをポジネガ判定に応用させるには?
BERTは通常、翻訳や質疑応答など対話をメインに開発されたモデルではあります。しかし、このモデルは、使い方ではポジネガ分析などの感情分析にも応用できるのです。自然言語に対しプログラムが応答するには、まず、自然言語を機械が解釈、分類可能な状態する必要があります。この解釈、分類可能な状態とは特徴ベクトルという入力固有の数列に変換することです。BERTのモデルでは、この過程を経ているため、部分的に用いることで自然言語から特徴を抽出することが可能になります。さらに、今までの自然言語のモデルに比べると言語タスクのスコアが良いので、その特徴抽出能力も高いといえます。
BERTで自然言語を特徴ベクトルに変換したあと、SVMやニューラルネットワーク、LightGBMなどの機械学習で分類問題用にモデルを構築し、学習させられればBERTを用いてポジネガ判定のモデルができます。
チュートリアル
では、早速やってみましょう!
データセット
今回用いるデータは、Kaggleから取得したTwitterの感情分析のデータセットです。 ネガティブな文言が格納されているprocessedNegative.csv、中性的な文言が格納されているprocessedNeutral.csv、ポジティブな文言が格納されているprocessedPositive.csvの三つのCSVファイルで校正されており、以下のように、行に文言が敷き詰められています。
How unhappy some dogs like it though | talking to my over driver about where I’m goinghe said he’d love to go to New York too but since Trump it’s probably not | Does anybody know if the Rand’s likely to fall against the dollar? I got some money I need to change into R but it keeps getting stronger unhappy | I miss going to gigs in Liverpool unhappy |
このデータを整形していきます。それぞれのCSVを読み込みそれぞれ0、1、2にラベルを生成してから、結合して一つのデータフレームを作ります。
positive_raw = pd.read_csv("TwitterPsiNegaPrediction/processedPositive.csv", header=None)
negative_raw = pd.read_csv("TwitterPsiNegaPrediction/processedNegative.csv", header=None)
neutral_raw = pd.read_csv("TwitterPsiNegaPrediction/processedNeutral.csv", header=None)
positive_raw_T = positive_raw.T
positive_const = pd.DataFrame(np.ones(len(positive_raw_T)))
positive = pd.concat([positive_raw_T, positive_const], axis=1)
negative_raw_T = negative_raw.T
negative_const = pd.DataFrame(np.zeros(len(negative_raw_T)))
negative = pd.concat([negative_raw_T, negative_const], axis=1)
neutral_raw_T = neutral_raw.T
neutral_const = pd.DataFrame(2*np.ones(len(neutral_raw_T)))
neutral = pd.concat([neutral_raw_T, neutral_const], axis=1)
dataset_pre = pd.concat([positive, negative])
dataset_pre.columns = ['text', 'target']
del positive_raw, positive_raw_T, positive_const, positive
del negative_raw, negative_raw_T, negative_const, negative
del neutral_raw_T, neutral_const, neutral
BERTによる特徴抽出
次に、文字をベクトルかした後に、BERTで特徴量を抽出します。今回は、”bert-base-uncased”という事前学習されたモデルを使い、トークン化とBERTによる特徴抽出を行います。トークンに変換する際に、文字の前後に”[CLS]”と”[SEP]”を入れます。使いやすかったので、Pytorchのライブラリを使ってトークン化しました。
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
bert_model = BertModel.from_pretrained('bert-base-uncased')
bert_model.eval()
def embedding(text):
bert_tokens = tokenizer.tokenize(str(text))
ids = tokenizer.convert_tokens_to_ids(["[CLS]"] + bert_tokens[:126] + ["[SEP]"])
tokens_tensor = torch.tensor(ids).reshape(1, -1)
with torch.no_grad():
output = bert_model(tokens_tensor)
del bert_tokens, ids, tokens_tensor
return output[1].numpy()
dataset_text_values = dataset_pre["text"].values
dataset_tokens = embedding(dataset_text_values[0])
for i in tqdm(range(1,dataset_text_values.shape[0])):
dataset_tokens = np.append(dataset_tokens, embedding(dataset_text_values[i]), axis=0)
del dataset_text_values
機械学習
今回は、チュートリアルということもあり、簡単なモデルであるSVMを使って識別していきます。トレーニングデータとテストデータを8:2に分け、SVMで学習します。ここで、パラメータチューニングをするとより精度は上がるとは思いますが、今回はBERTを用いたチュートリアルということで、割愛します。他にもニューラルネットワークやRandomForest、LightGBMなどがあります。興味があればそちらのモデルも試してみ良いかもしれません。
x_train, x_test, y_train, y_test = train_test_split(dataset_tokens, dataset_pre["target"].values, test_size=0.2)
del dataset_tokens, dataset_pre
print(f"train shape: {x_train.shape} {x_test.shape}")
print(f"test shape: {y_train.shape} {y_test.shape}")
clf = svm.SVC(gamma="scale")
clf.fit(x_train, y_train)
score = clf.score(x_train, y_train)
print(f"score: {score:.4f}")
score = clf.score(x_test, y_test)
print(f"score: {score:.4f}")
結果
結果は以下のようになりました。まずまずの結果ですね。。。
score: 0.8073#トレーニングデータの識別率
score: 0.7787#テストデータの識別率
コード全体
コード全体は以下のようになっています。
import os
import sys
import random
import fasttext
import fasttext.util
import torch
import numpy as np
import pandas as pd
from sklearn import svm
from sklearn.model_selection import KFold, train_test_split
from tqdm import tqdm
from sklearn.metrics import roc_auc_score
from transformers import BertTokenizer, BertModel
sys.setrecursionlimit(10**6)
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
bert_model = BertModel.from_pretrained('bert-base-uncased')
bert_model.eval()
positive_raw = pd.read_csv("TwitterPsiNegaPrediction/processedPositive.csv", header=None)
negative_raw = pd.read_csv("TwitterPsiNegaPrediction/processedNegative.csv", header=None)
neutral_raw = pd.read_csv("TwitterPsiNegaPrediction/processedNeutral.csv", header=None)
positive_raw_T = positive_raw.T
positive_const = pd.DataFrame(np.ones(len(positive_raw_T)))
positive = pd.concat([positive_raw_T, positive_const], axis=1)
negative_raw_T = negative_raw.T
negative_const = pd.DataFrame(np.zeros(len(negative_raw_T)))
negative = pd.concat([negative_raw_T, negative_const], axis=1)
neutral_raw_T = neutral_raw.T
neutral_const = pd.DataFrame(2*np.ones(len(neutral_raw_T)))
neutral = pd.concat([neutral_raw_T, neutral_const], axis=1)
dataset_pre = pd.concat([positive, negative])
dataset_pre.columns = ['text', 'target']
del positive_raw, positive_raw_T, positive_const, positive
del negative_raw, negative_raw_T, negative_const, negative
del neutral_raw_T, neutral_const, neutral
def embedding(text):
bert_tokens = tokenizer.tokenize(str(text))
ids = tokenizer.convert_tokens_to_ids(["[CLS]"] + bert_tokens[:126] + ["[SEP]"])
tokens_tensor = torch.tensor(ids).reshape(1, -1)
with torch.no_grad():
output = bert_model(tokens_tensor)
del bert_tokens, ids, tokens_tensor
return output[1].numpy()
dataset_text_values = dataset_pre["text"].values
dataset_tokens = embedding(dataset_text_values[0])
for i in tqdm(range(1,dataset_text_values.shape[0])):
dataset_tokens = np.append(dataset_tokens, embedding(dataset_text_values[i]), axis=0)
del dataset_text_values
x_train, x_test, y_train, y_test = train_test_split(dataset_tokens, dataset_pre["target"].values, test_size=0.2)
del dataset_tokens, dataset_pre
print(f"train shape: {x_train.shape} {x_test.shape}")
print(f"test shape: {y_train.shape} {y_test.shape}")
clf = svm.SVC(gamma="scale")
clf.fit(x_train, y_train)
score = clf.score(x_train, y_train)
print(f"score: {score:.4f}")
score = clf.score(x_test, y_test)
print(f"score: {score:.4f}")
まとめ
いかがだったでしょうか?今回はBERTというモデルを用いて文字データの特徴変換を用いた感情分析を行いました。結果はまずまずな結果でしたが、SVMでこれくらいの結果が出るということは、他のモデルを用いることでより高い精度を狙えそうですね!日々、自然言語処理の分野も新しい技術が出てきているので、ふかぼってみても面白いかもしれませんね。ではまた!
参考資料
https://www.kaggle.com/orjiugochukwu/twitter-sentiment-analysis
https://aismiley.co.jp/ai_news/bert/
https://qiita.com/toshiyuki_tsutsui/items/604f92dbe6e20a18a17e