【実装解説】日本語版BERTでlivedoorニュース分類：Google Colaboratoryで（PyTorch）

本記事では、Google Colaboratoryで日本語版BERTを使用し、livedoorニュース9カテゴリを分類する自然言語処理の実装について解説します。

Google Colaboratoryで日本語版のBERTを利用し、文章をベクトル化するまでの内容はこれまでの連載記事で解説したので、まずはこちらをご覧ください。


**連載一覧**
[[1]【実装解説】日本語版BERTをGoogle Colaboratoryで使う方法（PyTorch）](https://qiita.com/sugulu/items/e522a38b812b8edb8a54)
[[2] ※本記事【実装解説】日本語版BERTでlivedoorニュース分類：Google Colaboratoryで（PyTorch）](https://qiita.com/sugulu/items/697bd03499c1de9cf082)
[[3]【実装解説】脳科学と教師なし学習。情報量最大化クラスタリングでMNISTを分類](https://qiita.com/sugulu/items/e3c01a0776a5df3ad552)
[[4]【実装解説】日本語BERT × 教師なし学習（情報量最大化クラスタリング）でlivedoorニュースを分類](https://qiita.com/sugulu/items/6a887e612edda9e35c0b)

---


本記事では、HuggingFaceさんのBERTライブラリを使いますが、BERT本体のモデルのみを使用し、残りはスクラッチで実装します。

HugggingFaceさんのBertForSequenceClassificationを使って、クラス分類もできるのですが、
あまりライブラリに頼り過ぎると、全体像をつかみにくいです。

お勉強段階では、自分で実装をおすすめします。

本記事では、拙著[「つくりながら学ぶ！PyTorchによる発展ディープラーニング」](https://www.amazon.co.jp/dp/B07VPDVNKW/)、
の第8章 自然言語処理による感情分析(BERT)にて、

英語のIMDb（Internet Movie Database）データで、クラス分類をしている内容を、
日本語のlivedoorニュースバージョンで実装します。

本実装の流れは、

1. livedoorニュースをダウンロードして、tsvファイルに変換
2. tsvファイルをPyTorchのtorchtextのDataLoaderに変換
3. BERTのクラス分類用のモデルを用意する
4. ファインチューニングの設定
5. 学習の実施
6. テストデータでの性能を確認

となります。


なお本投稿内容の実装コードは以下のGitHubリポジトリに置いています。

[GitHub：日本語版BERTのGoogle Colaboratoryでの使用方法：実装コード](https://github.com/YutaroOgawa/BERT_Japanese_Google_Colaboratory)
の、2_BERT_livedoor_news_on_Google_Colaboratory.ipynbです。

## 1. livedoorニュースをダウンロードして、tsvファイルに変換

ここでは、livedoorニュースをダウンロードし、

本文[tab]クラスラベル
本文[tab]クラスラベル
本文[tab]クラスラベル

の構成のtsvファイルへと変換していきます。


まず、LiveDoorニュースをダウンロードします（乱数のシード固定は掲載を省略しています）。

```python
# Livedoorニュースのファイルをダウンロード
! wget "https://www.rondhuit.com/download/ldcc-20140209.tar.gz"
```

ダウンロードした圧縮ファイルを解凍しカテゴリーの数と内容を確認します。

```python
# ファイルを解凍し、カテゴリー数と内容を確認
import tarfile
import os

# 解凍
tar = tarfile.open("ldcc-20140209.tar.gz", "r:gz")
tar.extractall("./data/livedoor/")
tar.close()

# フォルダのファイルとディレクトリを確認
files_folders = [name for name in os.listdir("./data/livedoor/text/")]
print(files_folders)

# カテゴリーのフォルダのみを抽出
categories = [name for name in os.listdir(
    "./data/livedoor/text/") if os.path.isdir("./data/livedoor/text/"+name)]

print("カテゴリー数:", len(categories))
print(categories)
```

出力は

```
['dokujo-tsushin', 'README.txt', 'it-life-hack', 'CHANGES.txt', 'smax', 'sports-watch', 'kaden-channel', 'movie-enter', 'topic-news', 'livedoor-homme', 'peachy']
カテゴリー数: 9
['dokujo-tsushin', 'it-life-hack', 'smax', 'sports-watch', 'kaden-channel', 'movie-enter', 'topic-news', 'livedoor-homme', 'peachy']
```
です。

カテゴリーではない、ファイルなどもあるので、それを無視します。

ひとつ、ファイルの中身を確認してみましょう。

```python
# ファイルの中身を確認してみる
file_name = "./data/livedoor/text/movie-enter/movie-enter-6255260.txt"

with open(file_name) as text_file:
    text = text_file.readlines()
    print("0：", text[0])  # URL情報
    print("1：", text[1])  # タイムスタンプ
    print("2：", text[2])  # タイトル
    print("3：", text[3])  # 本文

    # 今回は4要素目には本文は伸びていないが、4要素目以降に本文がある場合もある
```

出力は以下のようになります。

```
0： http://news.livedoor.com/article/detail/6255260/
1： 2012-02-07T09:00:00+0900
2： 新しいヴァンパイアが誕生！　ジョニデ主演『ダーク・シャドウ』の公開日が決定
3： 　こんなヴァンパイアは見たことがない！　ジョニー・デップとティム・バートン監督が・・・
```

今回は、4要素目に本文が入っていませんが、4要素目以降に本文が入っている場合もあります。

この各ファイルから、**タイトルは除いて**、本文だけを抽出したtsvファイルを作成したいです。

タイトルの除くのは、タイトルは文章内容の要約であり、さすがにクラス分類のための情報量が多すぎるからです。


本文を取得する前処理関数を定義します。ここでは改行や全角スペースも削除しています。

```python
# 本文を取得する前処理関数を定義


def extract_main_txt(file_name):
    with open(file_name) as text_file:
        # 今回はタイトル行は外したいので、3要素目以降の本文のみ使用
        text = text_file.readlines()[3:]

        # 3要素目以降にも本文が入っている場合があるので、リストにして、後で結合させる
        text = [sentence.strip() for sentence in text]  # 空白文字(スペースやタブ、改行)の削除
        text = list(filter(lambda line: line != '', text))
        text = ''.join(text)
        text = text.translate(str.maketrans(
            {'\n': '', '\t': '', '\r': '', '\u3000': ''}))  # 改行やタブ、全角スペースを消す
        return text
```

この定義した前処理関数を利用して、全ファイルを変換します。
livedoorニュースの9カテゴリについて、各カテゴリーごとに処理を実施します。

```python
# リストに前処理した本文と、カテゴリーのラベルを追加していく
import glob

list_text = []
list_label = []

for cat in categories:
    text_files = glob.glob(os.path.join("./data/livedoor/text", cat, "*.txt"))

    # 前処理extract_main_txtを実施して本文を取得
    body = [extract_main_txt(text_file) for text_file in text_files]

    label = [cat] * len(body)  # bodyの数文だけカテゴリー名のラベルのリストを作成

    list_text.extend(body)  # appendが要素を追加するのに対して、extendはリストごと追加する
    list_label.extend(label)
```

格納したデータを確認してみます。

```python
# 0番目の文章とラベルを確認
print(list_text[0])
print(list_label[0])
```

出力は、以下の通りです。
以下の出力はもしかしたら、環境によって変化するかもしれません。
（カテゴリーの順番を今回、きちんと整列して統一していないので・・・）

```
婚活中の綾さん(29歳)の理想の結婚相手は転勤のない職種で実家の近くに住んでくれる人。綾さんは・・・
dokujo-tsushin
```

リストをpandasのDataFrameに変換します。サイズを確認すると、7,376の文章があることが確認できます。

```python
# pandasのDataFrameにする
import pandas as pd

df = pd.DataFrame({'text': list_text, 'label': list_label})

# 大きさを確認しておく（7,376文章が存在）
print(df.shape)

df.head()
```

続いて、カテゴリー名を数値に変換する辞書を作成します。
そして、その辞書で数値に置き換えたDataFrameを用意します。

```python
# カテゴリーの辞書を作成
dic_id2cat = dict(zip(list(range(len(categories))), categories))
dic_cat2id = dict(zip(categories, list(range(len(categories)))))

print(dic_id2cat)
print(dic_cat2id)

# DataFrameにカテゴリーindexの列を作成
df["label_index"] = df["label"].map(dic_cat2id)
df.head()

# label列を消去し、text, indexの順番にする
df = df.loc[:, ["text", "label_index"]]
df.head()
```

出力は以下のような感じです。

```
{0: 'dokujo-tsushin', 1: 'it-life-hack', 2: 'smax', 3: 'sports-watch', 4: 'kaden-channel', 5: 'movie-enter', 6: 'topic-news', 7: 'livedoor-homme', 8: 'peachy'}
{'dokujo-tsushin': 0, 'it-life-hack': 1, 'smax': 2, 'sports-watch': 3, 'kaden-channel': 4, 'movie-enter': 5, 'topic-news': 6, 'livedoor-homme': 7, 'peachy': 8}
text	label_index
0	婚活中の綾さん(29歳)の理想の結婚相手は転勤のない職種で実家の近くに住んでくれる人。綾さん...	0
1	佐々木倫子といえば80年代に『動物のお医者さん』を大ヒットさせたベテラン漫画家。獣医を目指す...	0
2	猛暑続きの今年の夏。7月末、ストッキングの生産数も10年間で4分の1になっているというニュー...	0
3	監督失格という“タイトルに惹かれて”、マスコミ試写に出かけた。映画『監督失格』は、女優・林由...	0
4	SNSで学生時代の旧友と再会することも当たり前になってきた。なかには、密かに片思いをしていた...	0
```

データがシャッフルされておらず、カテゴリーごとに固まっているので、シャッフルします。

```python
# 順番をシャッフルする
df = df.sample(frac=1, random_state=123).reset_index(drop=True)
df.head()
```

出力は以下のような感じです。

```
	text	label_index
0	『アヒルと鴨のコインロッカー』（2007年）や『ゴールデンスランバー』（2010年）など、こ...	5
1	「銀のさら」のWeb限定CMが、「差別的な内容だ」と27日からツイッターなどで物議を醸してい...	6
2	TBS「S1」（1日放送分）には、野球クラブチーム・茨城ゴールデンゴールズの片岡安祐美が出演...	3
3	『容疑者Xの献身』『白夜行』『麒麟の翼』など原作、映画ともに大ヒットを記録している国民的作家...	5
4	多くの人と出会うことができるSNS。そのため、恋人作りのために利用している人も多いのではない...	4
```

シャッフルされたデータの前2割をテストデータ、残りの8割は訓練&検証データとします。

結果、テストデータが1,475件、訓練&検証データが5,901件となります。

これをtest.tsv、train_eval.tsvとして、それぞれ保存します。

```python
# tsvファイルで保存する

# 全体の2割の文章数
len_0_2 = len(df) // 5

# 前から2割をテストデータとする
df[:len_0_2].to_csv("./test.tsv", sep='\t', index=False, header=None)
print(df[:len_0_2].shape)

# 前2割からを訓練&検証データとする
df[len_0_2:].to_csv("./train_eval.tsv", sep='\t', index=False, header=None)
print(df[len_0_2:].shape)
```

以上でlivedoorニュースのデータをtsvファイルに変換することができました。

なお、tsvファイルをGoogle Colaboratoryからダウンロードしたい場合は以下のfile.downloadのコメントを外して実行します。

```python
# tsvファイルをダウンロードしたい場合
from google.colab import files

# ダウンロードする場合はコメントを外す
# 少し時間がかかる（4MB）
# files.download("./test.tsv")


# ダウンロードする場合はコメントを外す
# 少し時間がかかる（18MB）
# files.download("./train_eval.tsv")
```



## 2. tsvファイルをPyTorchのtorchtextのDataLoaderに変換

続いて、作成したtsvファイルを、PyTorchで扱えるDataLoaderに変換します。
torchtextを使用します。

前回の記事でも解説しましたが、形態素解析のMeCabやHuggingFaceのtransformersをインストールします。

[●【実装解説】日本語版BERTをGoogle Colaboratoryで使う方法（PyTorch）](https://qiita.com/sugulu/items/e522a38b812b8edb8a54)

```python
# MeCabとtransformersの用意
!apt install aptitude swig
!aptitude install mecab libmecab-dev mecab-ipadic-utf8 git make curl xz-utils file -y
!pip install mecab-python3
!pip install transformers==2.9.0
```

日本語BERT用のtokenizerを用意します。


```python
import torch
import torchtext  # torchtextを使用
from transformers.modeling_bert import BertModel
from transformers.tokenization_bert_japanese import BertJapaneseTokenizer

# 日本語BERTの分かち書き用tokenizerです
tokenizer = BertJapaneseTokenizer.from_pretrained(
    'bert-base-japanese-whole-word-masking')

```

そして、分かち書き部分を関数にします。
tokenizer.encodeの出力の[0]番目を取得する点に注意が必要です。

またDatasetを作成する際のTEXT部分とLABEL部分の設定を与えます。

日本語版BERTの最大単語数（サブワード数）は512なので、512に設定します。
return_tensors='pt'はPyTorchのテンソルにする設定です。

```python
# データを読み込んだときに、読み込んだ内容に対して行う処理を定義します

max_length = 512  # 東北大学_日本語版の最大の単語数（サブワード数）は512


def tokenizer_512(input_text):
    """torchtextのtokenizerとして扱えるように、512単語のpytorchでのencodeを定義。ここで[0]を指定し忘れないように"""
    return tokenizer.encode(input_text, max_length=512, return_tensors='pt')[0]


TEXT = torchtext.data.Field(sequential=True, tokenize=tokenizer_512, use_vocab=False, lower=False,
                            include_lengths=True, batch_first=True, fix_length=max_length, pad_token=0)
# 注意：tokenize=tokenizer.encodeと、.encodeをつけます。padding[PAD]のindexが0なので、0を指定します。

LABEL = torchtext.data.Field(sequential=False, use_vocab=False)

# (注釈)：各引数を再確認
# sequential: データの長さが可変か？文章は長さがいろいろなのでTrue.ラベルはFalse
# tokenize: 文章を読み込んだときに、前処理や単語分割をするための関数を定義
# use_vocab：単語をボキャブラリーに追加するかどうか
# lower：アルファベットがあったときに小文字に変換するかどうか
# include_length: 文章の単語数のデータを保持するか
# batch_first：ミニバッチの次元を用意するかどうか
# fix_length：全部の文章をfix_lengthと同じ長さになるように、paddingします
# init_token, eos_token, pad_token, unk_token：文頭、文末、padding、未知語に対して、どんな単語を与えるかを指定

```

上記の設定でDatasetを作成します。
訓練&検証用のDatasetと、テスト用のDatasetを用意します。
この処理には少し時間がかかります（2分程度）。

```python
# 各tsvファイルを読み込み、分かち書きをしてdatasetにします
# 少し時間がかかります
# train_eval：5901個、test：1475個
dataset_train_eval, dataset_test = torchtext.data.TabularDataset.splits(
    path='.', train='train_eval.tsv', test='test.tsv', format='tsv', fields=[('Text', TEXT), ('Label', LABEL)])
```

訓練&検証用のDatasetを、訓練Datasetと検証Datasetに分割します。
3つのDatasetの長さを確認しておきます。


```python
# torchtext.data.Datasetのsplit関数で訓練データと検証データを分ける
# train_eval：5901個、test：1475個

dataset_train, dataset_eval = dataset_train_eval.split(
    split_ratio=1.0 - 1475/5901, random_state=random.seed(1234))

# datasetの長さを確認してみる
print(dataset_train.__len__())
print(dataset_eval.__len__())
print(dataset_test.__len__())
```

出力は次の通りです。

```
4426
1475
1475
```

Datasetの中身を確認してみましょう。

```python
# datasetの中身を確認してみる
item = next(iter(dataset_train))
print(item.Text)
print("長さ：", len(item.Text))  # 長さを確認 [CLS]から始まり[SEP]で終わる。512より長いと後ろが切れる
print("ラベル：", item.Label)
```

出力は、以下のような形になります。

```
tensor([    2,  8454,  6172,  1704,  5408,    16,  2109,    20,    16,    33,
　　　　　・・・
            5,     3])
長さ： 512
ラベル： 7
```

単語idになっているので、文字に戻してみます。

```python
# datasetの中身を文章に戻し、確認

print(tokenizer.convert_ids_to_tokens(item.Text.tolist()))  # 文章
dic_id2cat[int(item.Label)]  # id
```

出力は以下のような感じです

```
['[CLS]', 'ビール', 'って', 'どう', 'やっ', 'て', '作ら', 'れ', 'て', 'いる', 'か', '、', 'ご', '##存', '##知', '?', '各社', 'から', '発売', 'さ', 'れ', 'て', 'いる', 'ビール', 'に', 'は', '、', 'さまざま', 'な', 
・・・
駄', 'な', 'タンパク質', 'を', '除去', '。', 'これ', 'により', 'クリア', 'で', '美しい', '黄金', '色', 'の', '[SEP]']
'livedoor-homme'
```

最後にDatasetをDataLoaderにします。
ミニバッチのサイズは16あたりが限界なので、16にします。

```python
# DataLoaderを作成します（torchtextの文脈では単純にiteraterと呼ばれています）
batch_size = 16  # BERTでは16、32あたりを使用する

dl_train = torchtext.data.Iterator(
    dataset_train, batch_size=batch_size, train=True)

dl_eval = torchtext.data.Iterator(
    dataset_eval, batch_size=batch_size, train=False, sort=False)

dl_test = torchtext.data.Iterator(
    dataset_test, batch_size=batch_size, train=False, sort=False)

# 辞書オブジェクトにまとめる
dataloaders_dict = {"train": dl_train, "val": dl_eval}
```

DataLoaderの動作を確認しておきましょう。

```python
# DataLoaderの動作確認

batch = next(iter(dl_test))
print(batch)
print(batch.Text[0].shape)
print(batch.Label.shape)
```

出力は以下となります。

```
[torchtext.data.batch.Batch of size 16]
	[.Text]:('[torch.LongTensor of size 16x512]', '[torch.LongTensor of size 16]')
	[.Label]:[torch.LongTensor of size 16]
torch.Size([16, 512])
torch.Size([16])
```

## 3. BERTのクラス分類用のモデルを用意する

続いて、クラス分類用のBERTモデルを用意します。

日本語版BERTモデルを用意し、その出力の先頭、[CLS]のベクトル768要素を全結合層に入力して、分類させます。

この構成はHugginFaceさんのtransformersには、BertForSequenceClassificationとして用意されていますが、自前で実装してみましょう。

まずはベースの日本語BERTモデルを用意します。

```python
from transformers.modeling_bert import BertModel

# BERTの日本語学習済みパラメータのモデルです
model = BertModel.from_pretrained('bert-base-japanese-whole-word-masking')
print(model)
```

続いて、このベースの日本語BERTモデルに、入力が768次元、出力が9クラスの全結合層を用意します。

そして、順伝搬のforward関数を用意します。

```python
from torch import nn


class BertForLivedoor(nn.Module):
    '''BERTモデルにLivedoorニュースの9クラスを判定する部分をつなげたモデル'''

    def __init__(self):
        super(BertForLivedoor, self).__init__()

        # BERTモジュール
        self.bert = model  # 日本語学習済みのBERTモデル

        # headにクラス予測を追加
        # 入力はBERTの出力特徴量の次元768、出力は9クラス
        self.cls = nn.Linear(in_features=768, out_features=9)

        # 重み初期化処理
        nn.init.normal_(self.cls.weight, std=0.02)
        nn.init.normal_(self.cls.bias, 0)

    def forward(self, input_ids):
        '''
        input_ids： [batch_size, sequence_length]の文章の単語IDの羅列
        '''

        # BERTの基本モデル部分の順伝搬
        # 順伝搬させる
        result = self.bert(input_ids)  # reult は、sequence_output, pooled_output

        # sequence_outputの先頭の単語ベクトルを抜き出す
        vec_0 = result[0]  # 最初の0がsequence_outputを示す
        vec_0 = vec_0[:, 0, :]  # 全バッチ。先頭0番目の単語の全768要素
        vec_0 = vec_0.view(-1, 768)  # sizeを[batch_size, hidden_size]に変換
        output = self.cls(vec_0)  # 全結合層

        return output
```

定義した、Livedoorニュース用のBERTのオブジェクトを用意します。


```python
# モデル構築
net = BertForLivedoor()

# 訓練モードに設定
net.train()

print('ネットワーク設定完了')
```

## 4. ファインチューニングの設定

続いて、学習の設定です。
今回はファインチューニングとし、最後の全結合層、そして、BERTのself-Attentionの繰り返し12層のうちの、最後の層のみをパラメータ学習させる設定とします。

```python
# 勾配計算を最後のBertLayerモジュールと追加した分類アダプターのみ実行

# 1. まず全部を、勾配計算Falseにしてしまう
for param in net.parameters():
    param.requires_grad = False

# 2. BertLayerモジュールの最後を勾配計算ありに変更
for param in net.bert.encoder.layer[-1].parameters():
    param.requires_grad = True

# 3. 識別器を勾配計算ありに変更
for param in net.cls.parameters():
    param.requires_grad = True
```

続いて、最適化関数の設定です。Adamを使用することにします。

ファインチューニングですので、最後の全結合層は学習率を高めにして、BERTの最終層は学習率を低めにします。

損失関数はただのクラス分類の損失関数である、CrossEntropyLoss()を使用します。


```python
# 最適化手法の設定
import torch.optim as optim


# BERTの元の部分はファインチューニング
optimizer = optim.Adam([
    {'params': net.bert.encoder.layer[-1].parameters(), 'lr': 5e-5},
    {'params': net.cls.parameters(), 'lr': 1e-4}
])

# 損失関数の設定
criterion = nn.CrossEntropyLoss()
# nn.LogSoftmax()を計算してからnn.NLLLoss(negative log likelihood loss)を計算
```

## 5. 学習の実施

以上で、準備が完了しました。

実際に学習を回します。

学習の関数を定義します。

```python
# モデルを学習させる関数を作成


def train_model(net, dataloaders_dict, criterion, optimizer, num_epochs):

    # GPUが使えるかを確認
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    print("使用デバイス：", device)
    print('-----start-------')

    # ネットワークをGPUへ
    net.to(device)

    # ネットワークがある程度固定であれば、高速化させる
    torch.backends.cudnn.benchmark = True

    # ミニバッチのサイズ
    batch_size = dataloaders_dict["train"].batch_size

    # epochのループ
    for epoch in range(num_epochs):
        # epochごとの訓練と検証のループ
        for phase in ['train', 'val']:
            if phase == 'train':
                net.train()  # モデルを訓練モードに
            else:
                net.eval()   # モデルを検証モードに

            epoch_loss = 0.0  # epochの損失和
            epoch_corrects = 0  # epochの正解数
            iteration = 1

            # データローダーからミニバッチを取り出すループ
            for batch in (dataloaders_dict[phase]):
                # batchはTextとLableの辞書型変数

                # GPUが使えるならGPUにデータを送る
                inputs = batch.Text[0].to(device)  # 文章
                labels = batch.Label.to(device)  # ラベル

                # optimizerを初期化
                optimizer.zero_grad()

                # 順伝搬（forward）計算
                with torch.set_grad_enabled(phase == 'train'):

                    # BERTに入力
                    outputs = net(inputs)

                    loss = criterion(outputs, labels)  # 損失を計算

                    _, preds = torch.max(outputs, 1)  # ラベルを予測

                    # 訓練時はバックプロパゲーション
                    if phase == 'train':
                        loss.backward()
                        optimizer.step()

                        if (iteration % 10 == 0):  # 10iterに1度、lossを表示
                            acc = (torch.sum(preds == labels.data)
                                   ).double()/batch_size
                            print('イテレーション {} || Loss: {:.4f} || 10iter. || 本イテレーションの正解率：{}'.format(
                                iteration, loss.item(),  acc))

                    iteration += 1

                    # 損失と正解数の合計を更新
                    epoch_loss += loss.item() * batch_size
                    epoch_corrects += torch.sum(preds == labels.data)

            # epochごとのlossと正解率
            epoch_loss = epoch_loss / len(dataloaders_dict[phase].dataset)
            epoch_acc = epoch_corrects.double(
            ) / len(dataloaders_dict[phase].dataset)

            print('Epoch {}/{} | {:^5} |  Loss: {:.4f} Acc: {:.4f}'.format(epoch+1, num_epochs,
                                                                           phase, epoch_loss, epoch_acc))

    return net
```

今回は5エポック学習させることにします。
だいたい、1エポックに2分ほどで、トータルで10分ほどで学習は終了します。

5エポックほどで、検証データの正解率やlossはまずまず安定しています。

```python
# 学習・検証を実行する。1epochに2分ほどかかります
num_epochs = 5
net_trained = train_model(net, dataloaders_dict,
                          criterion, optimizer, num_epochs=num_epochs)

```


## 6. テストデータでの性能を確認

モデルを学習できれば、テストで性能を確認します。


```python
from tqdm import tqdm

# テストデータでの正解率を求める
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

net_trained.eval()   # モデルを検証モードに
net_trained.to(device)  # GPUが使えるならGPUへ送る

# epochの正解数を記録する変数
epoch_corrects = 0

for batch in tqdm(dl_test):  # testデータのDataLoader
    # batchはTextとLableの辞書オブジェクト
    # GPUが使えるならGPUにデータを送る
    inputs = batch.Text[0].to(device)  # 文章
    labels = batch.Label.to(device)  # ラベル

    # 順伝搬（forward）計算
    with torch.set_grad_enabled(False):

        # BertForLivedoorに入力
        outputs = net_trained(inputs)

        loss = criterion(outputs, labels)  # 損失を計算
        _, preds = torch.max(outputs, 1)  # ラベルを予測
        epoch_corrects += torch.sum(preds == labels.data)  # 正解数の合計を更新

# 正解率
epoch_acc = epoch_corrects.double() / len(dl_test.dataset)

print('テストデータ{}個での正解率：{:.4f}'.format(len(dl_test.dataset), epoch_acc))
```

出力結果は以下のようになりました。

```
100%|██████████| 93/93 [00:24<00:00,  3.79it/s]テストデータ1475個での正解率：0.9261
```

正解率92.6%で9クラスの記事を分類できるようになりました。

以上、日本語版BERTでlivedoorニュース分類の実装解説でした。

なお、本投稿内容の実装コードは以下のGitHubリポジトリに置いています。

[GitHub：日本語版BERTのGoogle Colaboratoryでの使用方法：実装コード](https://github.com/YutaroOgawa/BERT_Japanese_Google_Colaboratory)
の、2_BERT_livedoor_news_on_Google_Colaboratory.ipynbです。


## さいごに

今回、テストデータでの正解率は92.6%でした。

以下の「BERT with SentencePiece for Japanese text.」では、入力テキストにタイトルを含めていますが、
本記事ではタイトルは除いています。

https://yoheikikuta.github.io/bert-japanese/
https://github.com/yoheikikuta/bert-japanese

タイトルは文章内容の要約であり、さすがにクラス分類のための情報量が多すぎるので。


同様にタイトルを抜いている、以下の投稿

[BERTを用いた日本語文書分類タスクの学習・ハイパーパラメータチューニングの実践例](https://medium.com/karakuri/bert%E3%82%92%E7%94%A8%E3%81%84%E3%81%9F%E6%97%A5%E6%9C%AC%E8%AA%9E%E6%96%87%E6%9B%B8%E5%88%86%E9%A1%9E%E3%82%BF%E3%82%B9%E3%82%AF%E3%81%AE%E5%AD%A6%E7%BF%92-%E3%83%8F%E3%82%A4%E3%83%91%E3%83%BC%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%83%81%E3%83%A5%E3%83%BC%E3%83%8B%E3%83%B3%E3%82%B0%E3%81%AE%E5%AE%9F%E8%B7%B5%E4%BE%8B-2fa5e4299b16)


では、正解率が92%ちょっとになっており、ほぼ同じ正解率が得られました。


以上、日本語BERTでの教師あり学習のクラス分類として、livedoorニュースを対象とした実装例、実装解説でした。



以上、ご一読いただき、ありがとうございました。

なお、今回は教師あり学習での分類を実施しました。

以下の投稿では、「日本語BERT × 教師なし学習（情報量最大化クラスタリング）」で
livedoorニュースをクラス分類する実装例を解説しています。

合わせて、どうぞご覧ください。

[●【実装解説】日本語BERT × 教師なし学習（情報量最大化クラスタリング）でlivedoorニュースを分類：Google Colaboratory（PyTorch）](https://qiita.com/sugulu/items/6a887e612edda9e35c0b)

---

【備考】[私がリードする、AIテクノロジー部開発チームはメンバ募集中です。興味がある方はこちらから](https://www.isidgroup.com/u/job.phtml?job_code=430)


【免責】本記事の内容そのものは著者の意見/発信であり、著者が属する企業等の公式見解ではございません

---

参考：https://github.com/yoheikikuta/bert-japanese/blob/master/notebook/finetune-to-livedoor-corpus.ipynb