LoginSignup
17
11

More than 1 year has passed since last update.

文章分類タスクでSentencepieceとMeCabを比較してみた

Last updated at Posted at 2021-05-21

はじめに

Sentencepieceが何ものかについては以下の記事がとても参考になりました。

実際のところSentencepieceを使ってみるとどんな感じなのかの感覚を掴むために、文章分類タスクをSentencepieceとMeCabそれぞれを使ったときの性能比較をしてみました。

扱うデータはlivedoorニュースコーパスとし、ニュースコーパスの本文を9つのカテゴリーに分類するタスクを扱うこととします。
分類モデルのアルゴリズムは単純なLSTMで行うこととします。
実行環境はGoogle Colab Proを使っています。

実装

Sentencepieceの準備

Sentencepieceはpipでインストールできます。

!pip install sentencepiece

まずはSentencepieceのモデルを学習します。以下ではlivedoorニュースコーパスを事前にDataFrameに変換したものを用意したものを使っています。
livedoorデータを学習データ:テストデータ=7:3で分けて、学習データのほうでSentencepieceのトークナイズを学習させます。

# google driveをcolabにマウント
from google.colab import drive
drive.mount('/content/drive')

# 諸々の必要なライブラリをインポートします。
import pickle
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score
import numpy as np
import random
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torchtext

from torchtext.legacy.data import Field
from torchtext.legacy.data import TabularDataset
from torchtext.legacy.data import Iterator
from torchtext.data.functional import generate_sp_model
from torchtext.data.functional import load_sp_model
from torchtext.data.functional import sentencepiece_tokenizer
from torchtext.data.functional import sentencepiece_numericalizer

import sentencepiece as spm
from sentencepiece import SentencePieceTrainer
from sentencepiece import SentencePieceProcessor

seed = 1
np.random.seed(seed)
random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)

# livedoorニュースコーパス格納先
drive_dir = '/content/drive/MyDrive/ColabNotebooks/livedoor_data/'

# 事前にDataFrameをpickleで固めたものをロードしています。
with open(drive_dir + 'livedoor_data.pickle', 'rb') as r:
    livedoor_df = pickle.load(r)

# データにcategory id列を追加しています。
categories = livedoor_df['category'].unique().tolist()
livedoor_df['category_id'] = livedoor_df['category'].map(lambda x: categories.index(x))

# データからスペースを除外しています。
# Sentencepieceではスペースを '▁' に置き換えますが、日本語はスペースで区切る言語ではないため、前処理の段階で除外しています。
# 妥当な前処理かどうかは正直よくわかっていません。。。
livedoor_df['body'] = livedoor_df['body'].map(lambda x: x.replace(' ', ''))
train_df, test_df = train_test_split(livedoor_df[['body', 'category_id']], train_size=0.7)
print('train size', train_df.shape)
print('test size', test_df.shape)
# train size (5163, 2)
# test size (2213, 2)

train_df.to_csv(drive_dir + 'train.tsv', sep='\t', index=False, header=False)
test_df.to_csv(drive_dir + 'test.tsv', sep='\t', index=False, header=False)

Sentencepieceの学習

Sentencepieceの学習用データは外部ファイルとして保存する必要があるようで、一旦テキストファイルとして保存して、SentencePieceTrainer.Trainで学習させます。今回はとりあえず語彙数は8000を指定しています。このように語彙数を予め指定してデータからその語彙数に収まるようにいい感じにトークナイズを学習してくれるのがSentencepieceの魅力の1つだと思います。MeCabとかで語彙数を指定する場合は、各単語の頻度を算出して、頻度の少ない単語をサブワードにしたりあれこれしないといけないですかね。


# sentencepieceの学習用のデータを準備する
with open(drive_dir + 'sp_corpus.txt', 'w') as w:
    w.write('\n'.join(train_df['body'].tolist()))

# Sentencepieceのモデルを構築
# pad idは3から指定可能。0,1,2は予約されています。
# 0 -> <unk>
# 1 -> <s>
# 2 -> </s>
# --character_coverrageは日本語の場合0.9995が推奨される。英語は1.0
# --ad_dummy_predixをTrueにすると、文頭にスペースのトークン'▁'が自動で挿入されますが、日本語を扱うときはFalseを指定したほうが良いのかなと思って、今回はFalseを指定しています。
SentencePieceTrainer.Train(
    '--input='+drive_dir+'sp_corpus.txt, --model_prefix='+drive_dir+'livedoor_sentencepiece --character_coverage=0.9995 --vocab_size=8000 --pad_id=3 --add_dummy_prefix=False'
)

ちなみにSentencepieceの学習はtorchtextからも行えます。torchtext.data.functional.generate_sp_modelで上のSentencePieceTrainer.Trainと同じことを実行できるようですが、torchtextからだと、character_coverageadd_dummy_prefixとかを(現時点では)指定できないようで、正直torchtextのSentencepiece用のライブラリは使う必要性はないかなーと思っています。

SentencePieceTrainer.Trainの実行が完了すると、--model_prefixで指定したファイル名で.model.vocabファイルが作成されます。.vocabファイルのほうはSentencepieceで学習された単語を確認することができます。(行数が単語ID)
.modelのほうのファイルをSentencePieceProcessorで読み込むことでSentencepieceのトークナイズを行うことができるようになります。

# Sentencepieceのモデルをロード
# .modelのファイルを指定します。
sp = SentencePieceProcessor(model_file=drive_dir+'livedoor_sentencepiece.model')

# 以下のようにロードしてもOK
# sp = SentencePieceProcessor()
# sp.Load(drive_dir + 'livedoor_sentencepiece.model')

Sentencepieceでトークナイズ

トークナイズはencodeで行うことができます。試しに3文を分割してみます。out_type=strを指定すれば、トークンIDではなく、文字列で表示されます。

# 学習されたSentencepieceで文章を分割してみます。
# テストしてみる
# 0は<unk>トークン
text = ['私は唐揚げに檸檬をかけない派です。😅',
        '人工知能は人間の仕事を奪った。',
        '私は「ゼロから始めるスマートフォン」を毎日購読しています。']
print(sp.encode(text))
#[[1438, 7100, 7067, 546, 10, 0, 8, 910, 67, 1382, 62, 6, 0],
# [68, 1460, 322, 1881, 9, 845, 4, 2786, 3538, 102, 6],
# [1438, 13, 1146, 11, 8, 780, 7981, 1757, 459, 6]]

# out_type=strを指定すれば分割文字列が確認できる
print(sp.encode(text, out_type=str))
# [['私は', '唐', '揚', 'げ', 'に', '檸檬', 'を', 'かけ', 'ない', '派', 'です', '。', '😅'],
# ['人', '工', '知', '能', 'は', '人間', 'の', '仕事を', '奪', 'った', '。'],
# ['私は', '「', 'ゼロから始めるスマートフォン', '」', 'を', '毎日', '購', '読', 'しています', '。']]

ここでちょっとポイントなのが、上の分割の結果を見ると、「ゼロから始めるスマートフォン」が1つのトークンとして認識されています。これは別に辞書として指定したわけでもないのに、Sentencepieceが学習の段階で1つのトークンにしよう、と学習した結果です。Sentencepieceの学習データに「ゼロから始めるスマートフォン」という文字列が多く含まれていたために、「ゼロから始めるスマートフォン」は1つのトークンにしたほうがよい、とSentencepieceが学習してくれたんですね。MeCabとかだと、予め辞書を作成しておかないと、「ゼロから始めるスマートフォン」が1つのトークンになることはないですね。

参考(Sentencepieceを使ってデータの水増しを行う)

参考文献として挙げたこちらの記事にもあるようにSentencepieceを使うと、データの水増しができるようです。方法としてはSampleEncodeAsPiecesを使って、Sentencepieceのトークナイズの結果を確率的に変動させることができます。

text = '自然言語処理をする上で、データを増やす作業はとても大変なことなんです。'
for _ in range(3):
    print(sp.SampleEncodeAsPieces(text, nbest_size=5, alpha=0.1))
#['自然', '言', '語', '処理', 'を', 'する', '上', 'で', '、', 'データ', 'を', '増', 'や', 'す', '作業', 'は', 'とても', '大変', 'な', 'こと', 'なんです', '。']
#['自然', '言', '語', '処理', 'をする', '上で', '、', 'データ', 'を', '増', 'や', 'す', '作業', 'は', 'とても', '大変', 'な', 'こと', 'なんです', '。']
#['自然', '言', '語', '処理', 'をする', '上', 'で', '、', 'データ', 'を', '増', 'や', 'す', '作業', 'は', 'とても', '大変', 'な', 'こと', 'なんです', '。']

torchtextでDataLoader作成

上で学習したSentencepieceのトークナイザーを使って、torhctextでDataLoaderを作成します。
Fieldtokenizeに指定しているsp.encodeはSentencepieceでトークン化(ID化)まで行っているので、use_vocab=Falseを指定しています。vocabularyの作成(単語のID化)はSentencepiece側で行っているので、torchtextでは行っていません。

# tokenizeが既に数値化されたものになっているので、use_vocab=Falseにしている
TEXT = Field(sequential=True, tokenize=sp.encode, lower=False, pad_first=True, use_vocab=False,
                                    include_lengths=True, batch_first=True, pad_token=3, unk_token=0)
LABEL = Field(sequential=False, use_vocab=False)

train_data, test_data = TabularDataset.splits(
    path=drive_dir, train='train.tsv', test='test.tsv', format='tsv', fields=[('Text', TEXT), ('Label', LABEL)])

BATCH_SIZE = 64
train_loader = Iterator(train_data, batch_size=BATCH_SIZE, train=True)
test_loader = Iterator(test_data, batch_size=BATCH_SIZE, train=False, sort=False)

分類用のモデル定義

以下のような単純なLSTMのネットワークでクラス分類を行うこととします。

class LSTMNet(nn.Module):
    def __init__(self, vocab_size, embedding_dim, hidden_dim, padding_idx):
        super(LSTMNet, self).__init__()
        # 単語分散表現はランダムベクトルを使う
        self.word_embeddings = nn.Embedding(vocab_size, embedding_dim, padding_idx=padding_idx)
        self.lstm = nn.LSTM(embedding_dim, hidden_dim, batch_first=True)
        self.linear = nn.Linear(hidden_dim, 9)

    def forward(self, input_ids):
        embeds = self.word_embeddings(input_ids)
        _, vec = self.lstm(embeds)
        vec = vec[0]
        vec = self.linear(vec)
        vec = vec.squeeze(0)
        return vec

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# Sentencepieceのvocab sizeはGetPieceSize()で取得できます。
# 学習の際、語彙数は8000を指定していたので、当然sp.GetPieceSize()=8000です。
VOCAB_SIZE = sp.GetPieceSize()
EMBEDDING_DIM = 200
HIDDEN_DIM = 128
PAD_ID = sp.PieceToId('<pad>') # 3

net = LSTMNet(VOCAB_SIZE, EMBEDDING_DIM, HIDDEN_DIM, padding_idx=PAD_ID)
net.to(device)

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr=0.001)

ネットワークの学習&精度確認

とりあえず30エポックほど回してみます。エポック毎に学習データ、テストデータの精度(f1-score)を算出しています。ついでに処理時間も計算しときます。

%%time

sp_train_losses = []
sp_test_losses = []
sp_train_fscores = []
sp_test_fscores = []
for epoch in range(30):

    # 学習
    train_loss = 0.0
    train_predict = []
    train_answer = []
    net.train()
    for batch in train_loader:
        optimizer.zero_grad()
        input_ids = batch.Text[0].to(device)
        y = batch.Label.to(device)
        out = net(input_ids)
        loss = criterion(out, y)
        train_loss += loss.item()
        loss.backward()
        optimizer.step()
        train_predict += out.argmax(dim=1).cpu().detach().numpy().tolist()
        train_answer += y.cpu().detach().numpy().tolist()
    sp_train_losses.append(train_loss)
    train_fscore = f1_score(train_answer, train_predict, average='macro')
    sp_train_fscores.append(train_fscore)

    # テスト
    test_loss = 0.0
    test_predict = []
    test_answer = []
    net.eval()
    with torch.no_grad():
        for batch in test_loader:
            input_ids = batch.Text[0].to(device)
            y = batch.Label.to(device)
            out = net(input_ids)
            loss = criterion(out, y)
            test_loss += loss.item()
            test_predict += out.argmax(dim=1).cpu().detach().numpy().tolist()
            test_answer += y.cpu().detach().numpy().tolist()
    sp_test_losses.append(test_loss)
    test_fscore = f1_score(test_answer, test_predict, average='macro')
    sp_test_fscores.append(test_fscore)

    print('epoch', epoch,
          '\ttrain loss', round(train_loss, 4), '\ttrain fscore', round(train_fscore, 4),
          '\ttest loss', round(test_loss, 4), '\ttest fscore', round(test_fscore, 4)
          )
# CPU times: user 4min 54s, sys: 52 s, total: 5min 46s
# Wall time: 5min 44s

処理時間は5分46秒


epoch毎の損失、F1-scoreはこちら
epoch 0     train loss 120.4921     train fscore 0.5271     test loss 38.3847   test fscore 0.6362
epoch 1     train loss 68.6138  train fscore 0.7205     test loss 28.744    test fscore 0.7136
epoch 2     train loss 52.5799  train fscore 0.7881     test loss 26.0421   test fscore 0.7397
epoch 3     train loss 38.2132  train fscore 0.8503     test loss 24.5308   test fscore 0.765
epoch 4     train loss 26.7084  train fscore 0.9012     test loss 24.2424   test fscore 0.7579
epoch 5     train loss 16.3464  train fscore 0.9429     test loss 22.5958   test fscore 0.7832
epoch 6     train loss 9.4959   train fscore 0.9711     test loss 22.3193   test fscore 0.7967
epoch 7     train loss 5.5236   train fscore 0.9885     test loss 22.475    test fscore 0.801
epoch 8     train loss 3.4832   train fscore 0.9935     test loss 23.4526   test fscore 0.7948
epoch 9     train loss 2.0342   train fscore 0.9966     test loss 23.4369   test fscore 0.7999
epoch 10    train loss 1.7133   train fscore 0.9966     test loss 24.5152   test fscore 0.7997
epoch 11    train loss 1.1556   train fscore 0.9983     test loss 24.6399   test fscore 0.8107
epoch 12    train loss 0.7154   train fscore 0.9987     test loss 25.3319   test fscore 0.8102
epoch 13    train loss 0.6672   train fscore 0.9986     test loss 26.6143   test fscore 0.8068
epoch 14    train loss 0.4661   train fscore 0.9991     test loss 25.8841   test fscore 0.8147
epoch 15    train loss 0.4777   train fscore 0.9988     test loss 26.6295   test fscore 0.8134
epoch 16    train loss 1.5553   train fscore 0.9951     test loss 27.349    test fscore 0.8069
epoch 17    train loss 1.178    train fscore 0.9957     test loss 26.4447   test fscore 0.8005
epoch 18    train loss 0.8552   train fscore 0.9984     test loss 25.9991   test fscore 0.8123
epoch 19    train loss 0.3411   train fscore 0.9991     test loss 26.0604   test fscore 0.8156
epoch 20    train loss 0.2808   train fscore 0.999  test loss 26.54     test fscore 0.8199
epoch 21    train loss 0.3581   train fscore 0.9989     test loss 27.4113   test fscore 0.815
epoch 22    train loss 0.2389   train fscore 0.999  test loss 27.2458   test fscore 0.8198
epoch 23    train loss 0.1964   train fscore 0.9994     test loss 27.4656   test fscore 0.8208
epoch 24    train loss 0.197    train fscore 0.9994     test loss 27.7908   test fscore 0.8211
epoch 25    train loss 0.1781   train fscore 0.9992     test loss 27.8535   test fscore 0.8262
epoch 26    train loss 0.1669   train fscore 0.9992     test loss 28.1779   test fscore 0.8222
epoch 27    train loss 0.1629   train fscore 0.9991     test loss 28.3673   test fscore 0.823
epoch 28    train loss 0.1592   train fscore 0.9994     test loss 28.4181   test fscore 0.8265
epoch 29    train loss 0.1626   train fscore 0.9992     test loss 28.7844   test fscore 0.8272


MeCab側でもモデルを構築して学習させる

MeCab側でも同様の処理でDataLoaderを作成し、単純なLSTMのネットワークで学習&精度検証します。

Sentencepieceは語彙数を抑えられることがメリットの1つだと思うので、MeCabでは、torchtextで語彙を作成する際、min_freq=1をあえて設定しております。MeCabだと語彙数が大量にできてしまうことを確認するためです。

pipでインストール

!pip install mecab-python3
!pip install unidic-lite

DataLoader作成まで

MeCabだと語彙数は61628になりました。

import MeCab

tagger = MeCab.Tagger("-Owakati")
def mecab_tokenizer(sentence):
    sentence = tagger.parse(sentence)
    wakati = sentence.split(" ")
    wakati = list(filter(("").__ne__, wakati))
    wakati = wakati[:-1]
    return wakati


TEXT = Field(sequential=True, tokenize=mecab_tokenizer, lower=False, pad_first=True,
                                    include_lengths=True, batch_first=True, pad_token='<pad>', unk_token='<unk>')
LABEL = Field(sequential=False, use_vocab=False)

train_data, test_data = TabularDataset.splits(
    path=drive_dir, train='train.tsv', test='test.tsv', format='tsv', fields=[('Text', TEXT), ('Label', LABEL)])

TEXT.build_vocab(train_data, min_freq=1)

BATCH_SIZE = 64
train_loader = Iterator(train_data, batch_size=BATCH_SIZE, train=True)
test_loader = Iterator(test_data, batch_size=BATCH_SIZE, train=False, sort=False)

class LSTMNet(nn.Module):
    def __init__(self, vocab_size, embedding_dim, hidden_dim, padding_idx):
        super(LSTMNet, self).__init__()
        # 単語分散表現はランダムベクトルを使う
        self.word_embeddings = nn.Embedding(vocab_size, embedding_dim, padding_idx=padding_idx)
        self.lstm = nn.LSTM(embedding_dim, hidden_dim, batch_first=True)
        self.linear = nn.Linear(hidden_dim, 9)

    def forward(self, input_ids):
        embeds = self.word_embeddings(input_ids)
        _, vec = self.lstm(embeds)
        vec = vec[0]
        vec = self.linear(vec)
        vec = vec.squeeze(0)
        return vec

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
VOCAB_SIZE = len(TEXT.vocab.stoi)
EMBEDDING_DIM=200
HIDDEN_DIM=128
PAD_ID = TEXT.vocab.stoi['<pad>']

net = LSTMNet(VOCAB_SIZE, EMBEDDING_DIM, HIDDEN_DIM, padding_idx=PAD_ID)
net.to(device)

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr=0.001)

学習&精度確認


こちらはSentencepieceのときと全く同じなので閉じておきます。
%%time

mecab_train_losses = []
mecab_test_losses = []
mecab_train_fscores = []
mecab_test_fscores = []
for epoch in range(30):

    train_loss = 0.0
    train_predict = []
    train_answer = []
    net.train()
    for batch in train_loader:
        optimizer.zero_grad()
        input_ids = batch.Text[0].to(device)
        y = batch.Label.to(device)
        out = net(input_ids)
        loss = criterion(out, y)
        train_loss += loss.item()
        loss.backward()
        optimizer.step()
        train_predict += out.argmax(dim=1).cpu().detach().numpy().tolist()
        train_answer += y.cpu().detach().numpy().tolist()
    mecab_train_losses.append(train_loss)
    train_fscore = f1_score(train_answer, train_predict, average='macro')
    mecab_train_fscores.append(train_fscore)

    test_loss = 0.0
    test_predict = []
    test_answer = []
    net.eval()
    with torch.no_grad():
        for batch in test_loader:
            input_ids = batch.Text[0].to(device)
            y = batch.Label.to(device)
            out = net(input_ids)
            loss = criterion(out, y)
            test_loss += loss.item()
            test_predict += out.argmax(dim=1).cpu().detach().numpy().tolist()
            test_answer += y.cpu().detach().numpy().tolist()
    mecab_test_losses.append(test_loss)
    test_fscore = f1_score(test_answer, test_predict, average='macro')
    mecab_test_fscores.append(test_fscore)

    print('epoch', epoch, '\ttrain loss', round(train_loss, 4), '\ttrain fscore', round(train_fscore, 4),
          '\ttest loss', round(test_loss, 4), '\ttest fscore', round(test_fscore, 4))
# CPU times: user 6min 4s, sys: 54.1 s, total: 6min 58s
# Wall time: 6min 56s



epoch毎の損失、F1-scoreはこちら
epoch 0     train loss 117.5845     train fscore 0.5344     test loss 35.3057   test fscore 0.6518
epoch 1     train loss 61.8279  train fscore 0.7427     test loss 24.6747   test fscore 0.7533
epoch 2     train loss 41.2987  train fscore 0.8371     test loss 19.8909   test fscore 0.7967
epoch 3     train loss 28.1072  train fscore 0.8884     test loss 19.5073   test fscore 0.8095
epoch 4     train loss 20.2023  train fscore 0.919  test loss 17.8201   test fscore 0.8322
epoch 5     train loss 12.1747  train fscore 0.9583     test loss 15.3678   test fscore 0.8528
epoch 6     train loss 12.1873  train fscore 0.956  test loss 15.5709   test fscore 0.8488
epoch 7     train loss 6.028    train fscore 0.9814     test loss 15.5984   test fscore 0.8609
epoch 8     train loss 3.6533   train fscore 0.9914     test loss 15.6728   test fscore 0.8608
epoch 9     train loss 2.2723   train fscore 0.9946     test loss 15.6894   test fscore 0.8725
epoch 10    train loss 1.5772   train fscore 0.9963     test loss 16.4935   test fscore 0.8731
epoch 11    train loss 12.7076  train fscore 0.9538     test loss 19.1291   test fscore 0.8326
epoch 12    train loss 5.5746   train fscore 0.9834     test loss 17.8947   test fscore 0.848
epoch 13    train loss 2.8394   train fscore 0.9927     test loss 16.9568   test fscore 0.8573
epoch 14    train loss 1.3217   train fscore 0.9971     test loss 17.2715   test fscore 0.8592
epoch 15    train loss 0.9888   train fscore 0.9978     test loss 17.8214   test fscore 0.8649
epoch 16    train loss 0.7672   train fscore 0.9984     test loss 18.3252   test fscore 0.8599
epoch 17    train loss 0.5628   train fscore 0.9986     test loss 18.5536   test fscore 0.8638
epoch 18    train loss 4.9127   train fscore 0.9843     test loss 18.734    test fscore 0.8581
epoch 19    train loss 1.1717   train fscore 0.997  test loss 19.0019   test fscore 0.8592
epoch 20    train loss 0.7327   train fscore 0.998  test loss 19.0872   test fscore 0.8682
epoch 21    train loss 0.6748   train fscore 0.9982     test loss 19.361    test fscore 0.8682
epoch 22    train loss 0.4693   train fscore 0.9988     test loss 20.3306   test fscore 0.8646
epoch 23    train loss 0.3393   train fscore 0.999  test loss 20.4864   test fscore 0.8679
epoch 24    train loss 0.27     train fscore 0.9992     test loss 20.7215   test fscore 0.8694
epoch 25    train loss 0.2346   train fscore 0.9991     test loss 21.0146   test fscore 0.868
epoch 26    train loss 0.2059   train fscore 0.9994     test loss 21.1525   test fscore 0.8702
epoch 27    train loss 0.2  train fscore 0.9991     test loss 21.4466   test fscore 0.8706
epoch 28    train loss 0.1753   train fscore 0.999  test loss 21.5888   test fscore 0.8686
epoch 29    train loss 0.1778   train fscore 0.9992     test loss 21.5458   test fscore 0.8722


SentencepieceとMeCabの結果比較

エポック毎における精度の推移はそれぞれのモデルで以下のようになりました。

import matplotlib.pyplot as plt

plt.figure(figsize=(15,5))

plt.subplot(1,2,1)
plt.plot(sp_train_losses, label='sp train loss')
plt.plot(sp_test_losses, label='sp test loss')
plt.plot(mecab_train_losses, label='mecab train loss')
plt.plot(mecab_test_losses, label='mecab test loss')
plt.title('loss')
plt.legend()
plt.grid()

plt.subplot(1,2,2)
plt.plot(sp_train_fscores, label='sp train fscore')
plt.plot(sp_test_fscores, label='sp test fscore')
plt.plot(mecab_train_fscores, label='mecab train fscore')
plt.plot(mecab_test_fscores, label='mecab test fscore')
plt.title('fscore')
plt.legend()
plt.grid()

plt.show()

image.png

精度はMeCabのほうが良いですね。MeCabだとテストデータでF1スコア0.8後半くらいまで言ってますが、Sentencepiece側だと0.8ちょいって感じ。
これはデータにもよると思うのですが、今回のlivedoorデータってクラス分類する上で特徴的な固有名詞が多いような気がするので、そういった特徴語を1つのトークンとして扱えている(であろう)MeCabのほうに軍配が上がったのだと思います。

とはいえ、Sentencepieceのメリットは別に精度だけじゃなくて、リソースを抑えることができる、とか早いとか色々あるので、その辺の数値も見てみます。

Sentencepiece MeCab
精度(epoch30時点) 0.8272 0.8722
学習データにおける語彙数 8,000 61,628
テストデータにおける未知語数 1,687 15,367
学習&検証の処理時間 5min 46s 6min 58s
パラメータ数 1,770,121 12,495,721


パラメータ数の確認の仕方
params = 0
for p in net.parameters():
    if p.requires_grad:
        params += p.numel()
print('parameters', params)


Sentencepieceのほうが語彙数を抑えられて、処理も早くて、未知語の数やパラメータ数なんかは10分の1くらいになってます。精度だけ見たら、今回のデータではMeCab優勢ですが、BERTのような巨大なモデルを扱う上ではSentencepieceなどが優位になったりするんでしょう。
(テストデータにおける未知語ってこんなに多いものなのか。。。)

おわりに

いろいろとSentencepieceを触ってみて、なんとなく使い方はわかりましたが、やはり詳しく知るには論文を読むしかなさそうです。
論文読むか〜。。。

おわり

17
11
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
11