文章がどの作家に似ているか判別するモデルを作った話

# 概要

文章を入れたとき, それがどの作家か当てれる分類モデルを作りました.
具体的に言うと, BERTをファインチューニングすることで, 入れた文章から作者を分類する多値分類モデルを作りました.

作者としては
* 太宰治
* 寺田寅彦
* 江戸川乱歩
* 芥川龍之介
* 夢野久作
の五人を選びました.
選定理由は, 青空文庫から著作がダウンロードできること, どの作家も作品数がそこそこあることと, 筆者の趣味です.

# 前処理

青空文庫の情報は[github](https://github.com/aozorabunko/aozorabunko/tree/master/cards)の方から引っ張って来ました.

取得したhtmlからまずは前処理として
* ルビの削除
* 特殊な処理の削除
* 注の削除

を行いました.

```python
from bs4 import BeautifulSoup

# soup = BeautifulSoup(html, features="lxml")
def soup2text(soup):
    ele = soup.find("div", class_="main_text")

    # ルビ削除
    for tag in ele.findAll(["rt", "rp"]):
        tag.decompose() 

    # 特殊文書削除
    for tag in ele.findAll("div"):
        tag.clear()
    text = ele.text

    # 脚注削除
    text = re.sub(r"［＃.*］", "", text)

    return text
```

次に各段落ごとに文章を区切り, 区切った一段落を学習データにしました.

```python
def splitParagraph(text, length_min=30):
    pghs = text.split("\n\u3000")
    pghs = [pgh.strip("\n") for pgh in pghs]
    pghs = [pgh for pgh in pghs if len(pgh) > length_min] #短すぎる段落は除外
    return pghs
```
最後に, 作品を8:2に分け, それぞれtrainデータとvalidデータにしました.

# モデル

モデルはtransformersライブラリのBertForSequenceClassificationを用いました.
日本語BERTのプリトレインモデルをファインチューニングする形で分類問題を学習させます.
ここの箇所は[BERTによる自然言語処理入門](https://www.amazon.co.jp/dp/427422726X?)を参考にしました.

## データセット作成

まず, 準備したテキストをtokenizerでプリトレインモデルが受け取れる形式に処理.
次にこれをDataloaderにつっこんで学習･訓練データセットとします.

```python
MODEL_NAME = 'cl-tohoku/bert-base-japanese-whole-word-masking'

tokenizer = BertJapaneseTokenizer.from_pretrained(MODEL_NAME)
max_length = 128
batch_size = 32

def encoding_text(text,label=None):
    encoding = tokenizer(
            text,
            max_length=max_length, 
            padding='max_length',
            truncation=True
        )
    encoding = { k: torch.tensor(v) for k, v in encoding.items() }
    return encoding

dataset_train=[encoding_text(text,label) for text,label in zip(df_train["text"],df_train["label"])]
dataset_valid=[encoding_text(text,label) for text,label in zip(df_valid["text"],df_valid["label"])]

dataset_train=DataLoader( dataset_train, batch_size=batch_size, shuffle=True )
dataset_valid=DataLoader( dataset_valid, batch_size=batch_size, shuffle=False )
```
## モデル作成
```python
class BertForSequenceClassification_pl(pl.LightningModule):
    def __init__(self, model_name, num_labels, lr):
        super().__init__()
        self.save_hyperparameters() 

        self.bert_sc = BertForSequenceClassification.from_pretrained(
            model_name,
            num_labels=num_labels
        )
    
    def forward(self,batch):
        return self.bert_sc(**batch)
    
    def training_step(self, batch, batch_idx):
        output = self.forward(batch)
        loss = output.loss
        self.log('train_loss', loss)
        return loss
        
    def validation_step(self, batch, batch_idx):
        output = self.bert_sc(**batch)
        val_loss = output.loss
        self.log('val_loss', val_loss)

    def test_step(self, batch, batch_idx):
        labels = batch.pop('labels')
        output = self.bert_sc(**batch)
        labels_predicted = output.logits.argmax(-1)
        num_correct = ( labels_predicted == labels ).sum().item()
        self.log('accuracy', num_correct/labels.size(0) )

    def configure_optimizers(self):
        return torch.optim.Adam(self.parameters(), lr=self.hparams.lr)
```
## モデル実行
```python
checkpoint = pl.callbacks.ModelCheckpoint(
    monitor='val_loss',
    mode='min',
    save_top_k=1,
    save_weights_only=True,
    dirpath='model/',
)
trainer = pl.Trainer(
    gpus=1, 
    max_epochs=10,
    callbacks=[checkpoint]
)
model = BertForSequenceClassification_pl(
    MODEL_NAME, num_labels=5, lr=1e-5
)
trainer.fit(model, dataset_train, dataset_valid) 
```
# 結果

訓練データの正答率は94.5%でした.
正直いって高すぎて驚いています.

いくつか文章を書いてみたのですが, どうやら自分の文章は寺田寅彦に似ているようです.
一人だけ随筆家なことが原因かも......

このモデルのスコアが高くなるように文章を書いたら, 各作家風の文章になるのではないかと思い試してみました.

### 太宰治
> 可笑しな話。私はかわで、どんぶらこと、桃を拾いました。

句読点を多くして一人称視点の文にしたらスコアが上がった気がします.

### 寺田寅彦
> おばあさんが川で洗濯したときに流れてきた桃から桃太郎伝説は始まったものであるらしい。

説明的で一歩引いた文章にしたらスコアが出ました.

### 江戸川乱歩
> 彼女がかわにいき、そこで大きな桃を拾ってきたことがすべてのはじまりなのかもしれません。

どの作家でもないテキトーな文を入れると江戸川乱歩率が高くなります(多分サンプル数のせい).
気持ち一文を長くするとスコアが出るかも.

### 芥川龍之介
>どんぶらこというやうに、老婆の前に大きな桃が流れて来るやうでございます。

古めかしい文章

### 夢野久作
>……ドンブラコ……ドンブラコ……
ハハア……川の上流……あれは大きな桃で御座いまショウ。

三点とカタカナ