ココアのお勉強ブログ

理工学研究科の大学院1年生です。主に深層学習を中心に勉強/研究しています。まだまだ初心者なので優しくしてくれると嬉しいです。きまぐれでイラストも描きます。イラスト : https://hotcocoagallery.jimdosite.com/

bert as service のベクトル表現を用いて文書分類メモ

日本語BERTで文表現ベクトルを得る環境を作る

日本語BERTによってベクトルを出せるようにする

以下の記事の通りに、日本語BERTによって文表現ベクトルを計算するサーバーを作ります。
https://qiita.com/shimaokasonse/items/97d971cd4a65eee43735
Google Colabolatoryでやる場合は

!pip install bert-serving-client
!pip install -U bert-serving-server[http]
!nohup bert-serving-start -model_dir=./bert-jp/ > out.file 2>&1 &
from bert_serving.client import BertClient
bc = BertClient()

としないと動かないです。

以上でbert-as-serviceから文表現ベクトルを得ることができました。

文書分類

分類するためのデータセットの準備

live コーパスを使っていきます(9クラス分類)。
live コーパスをダウンロードし,解凍します。

$ wget https://www.rondhuit.com/download/ldcc-20140209.tar.gz
$ tar zxf ldcc-20140209.tar.gz > /dev/null

データセットの整形

とりあえず以下のような形にもっていきます。

Text Label
0 text1 label1
1 text2 label2
2 text3 label3
... ... ...
import os 
import random
import pandas as pd
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
import numpy as np
import glob

def parse_to_wakati(text):
    return text

categories = [
    "sports-watch", "topic-news", "dokujo-tsushin", "peachy",
    "movie-enter", "kaden-channel", "livedoor-homme", "smax",
    "it-life-hack",
]

docs = []
for category in categories:
    for f in glob.glob(f"./text/{category}/{category}*.txt"):
        # 1ファイルごとに処理
        with open(f, "r") as fin:
            # nextで1行取得する(__next__を呼ぶ)
            url = next(fin).strip()
            date = next(fin).strip()
            title = next(fin).strip()
            body = "\n".join([line.strip() for line in fin if line.strip()])
      
        docs.append((category, url, date, title, body))

df = pd.DataFrame(
        docs,
        columns=["category", "url", "date", "title", "body"],
        dtype="category"
)
# 日付は日付型に変更(今回使うわけでは無い)
df["date"] = pd.to_datetime(df["date"])

# wakati body
df = df.assign(
    Text=lambda df: df['body'].apply(parse_to_wakati)
)

# ラベルエンコーダは、ラベルを数値に変換する
le = LabelEncoder()

# ラベルをエンコードし、エンコード結果をyに代入する
df = df.assign(
    Label=lambda df: pd.Series(le.fit_transform(df.category))
)

labels = np.sort(df['Label'].unique())
labels = [str(f) for f in labels]

idx = df.index.values

idx_train, idx_val = train_test_split(idx, random_state=123)
train_df = df.loc[idx_train, ['Text', 'Label']]
val_df = df.loc[idx_val, ['Text', 'Label']]

↑のコードを実行するとTextとLabelのペアを作ることができます。
↓train_dfの一例

自作データとかの場合train_dfのような形 Text, Label となるようにCSVファイルを既に用意してもらった方が簡単化と思います。

Kerasで分類する

Kerasを用いてbert as serviceから得られたベクトル表現に対して分類をします。
以下でネットワークを構築します。

import numpy as np

from sklearn import datasets
from sklearn.model_selection import train_test_split

from keras.models import Sequential
from keras.layers.core import Dense, Activation
from keras.utils import np_utils
from sklearn import preprocessing

# BERTから得るベクトル表現をこのモデルに流し込む
def build_multilayer_perceptron():
    model = Sequential()
    # 隠れ層512は好きに変えて良い
    model.add(Dense(512, input_shape=(768,)))
    model.add(Activation('relu'))
    # liveコーパスは9クラス分類なので9
    model.add(Dense(9))
    model.add(Activation('softmax'))
    return model
    

x = train_df["Text"].values.tolist()
x = list(map(parse,x))
X = bc.encode(x,is_tokenized=True)
Y = train_df["Label"].values
train_X, test_X, train_Y, test_Y = train_test_split(X, Y, train_size=0.8)
print(train_X.shape, test_X.shape, train_Y.shape, test_Y.shape)
model = build_multilayer_perceptron()
model.compile(optimizer='adam',
              loss='categorical_crossentropy',
                metrics=['accuracy'])

分類を実行します。

# epoch数,batch_sizeは適宜変更する
model.fit(train_X, train_Y, nb_epoch=200, batch_size=16, verbose=1)

"""
4420/4420 [==============================] - 1s 184us/step - loss: 0.0265 - acc: 0.9930
Epoch 198/200
4420/4420 [==============================] - 1s 177us/step - loss: 0.1637 - acc: 0.9493
Epoch 199/200
4420/4420 [==============================] - 1s 184us/step - loss: 0.2781 - acc: 0.9120
Epoch 200/200
4420/4420 [==============================] - 1s 179us/step - loss: 0.0248 - acc: 0.9943
"""

評価します。

# モデル評価
loss, accuracy = model.evaluate(test_X, test_Y, verbose=0)
print("Accuracy = {:.2f}".format(accuracy))

transformersのBERT

色々めんどくさいことしましたが transformers を使えば日本語学習済BERTを非常に簡単に利用できるようになりました(つい最近)。
https://twitter.com/huggingface/status/1205283603128758277
huggingface/transformers の日本語BERTで文書分類器を作成する記事 https://qiita.com/nekoumei/items/7b911c61324f16c43e7e
試してみたら非常に簡単でした。
分類 BertForSequenceClassification だけでなく以下から色々なタスクを扱えるようです。

# Each architecture is provided with several class for fine-tuning on down-stream tasks, e.g.
BERT_MODEL_CLASSES = [BertModel, BertForPreTraining, BertForMaskedLM, BertForNextSentencePrediction,
                      BertForSequenceClassification, BertForTokenClassification, BertForQuestionAnswering]

bert as serviceは一応CPUでも動くようなのでGPUないけど回したいみたいな人にはいいのかなと思いました。
NLP界隈の人間ではないので詳しいことまでは分かりませんが。