青空文庫作品の作者推定(character-level cnn）

# 青空文庫作品の作者推定
青空文庫から, 作者1人につき20作品を学習データとし, 1作品をテストデータとして, 20人の作者の作品(テストデータ)で作者推定を行い, 精度を示します。
以下に作成したコードを載せています。
https://github.com/yuu7777/aozora_colassification_2023

## 初めに
青空文庫作品のテキストを利用し, 作品の作者を判定するモデルを作成しました。
参考にした記事です。とても勉強になりました。改めて感謝申し上げます。

https://qiita.com/cvusk/items/c1342dd0fff16dc37ddf 

## 概要
character-level cnnをtensorflow + kerasで実装し, 作者推定を行うための分類モデルを作成しました。
character-level cnnの論文は以下のサイトになります。
https://papers.nips.cc/paper/5782-character-level-convolutional-networks-for-text-classification.pdf

詳しくは参考にした記事に書いてあるのでそちらを参照してください。
文レベルで自然言語処理を行うので, 形態素解析を行う必要がないことが利点です。

## データ

データは青空文庫の作者から, 21作品以上作品がある作者から厳選しました。
https://www.aozora.gr.jp/
対象の作者は "sakusya_list.txt"に記載
データの配置は以下の通り。
・
├── 作者1
│   ├── テストデータ
│   ├── ├──..._ruby...
│   ├── ├── ├── ..._ruby... .txt　　
│   └── 学習データ
│   ├── ├──..._ruby...
│   ├── ├── ├── ..._ruby... .txt　　
├── 作者2
│   ├── //
│   └── //
├── ...
├── 作者20
│   ├── //
│   └── //


収集したデータは UTF-8 に変換し, 句点で分割・不要文字列を削除しcsvファイルにします。


## character-level cnn
csvファイルを元に分類モデルを作成します。
ここでは, 参考サイトから変更したコードを示します。

自分はtensorflow + keras(tensorflow = 2.10)で実装したのでimport分や関数の書き方が変わっています。
(参考コードそのままだとverの違いからかエラーが出た)

```
from tensorflow.keras.models import Model
import tensorflow as tf
```
作者の人数20に合わせて出力次元を変更しています。


```
# Creating character-level convolutional neural network model.

def create_model(embed_size=128, max_length=100, filter_sizes=(2, 3, 4, 5), filter_num=64):
    inp = tf.keras.layers.Input(shape=(max_length,))
    emb = tf.keras.layers.Embedding(0xffff, embed_size)(inp)
    emb_ex = tf.keras.layers.Reshape((max_length, embed_size, 1))(emb)
    convs = []
    for filter_size in filter_sizes:
        conv = tf.keras.layers.Convolution2D(filter_num, (filter_size, embed_size), activation="relu")(emb_ex)
        pool = tf.keras.layers.MaxPooling2D(pool_size=(max_length - filter_size + 1,1))(conv)
        convs.append(pool)
    convs_merged = tf.keras.layers.concatenate(convs, axis=-1)
    #convs_merged = tf.keras.layers.concatenate(convs, mode='concat')
    reshape = tf.keras.layers.Reshape((filter_num * len(filter_sizes),))(convs_merged)
    fc1 = tf.keras.layers.Dense(64, activation="relu")(reshape)
    bn1 = tf.keras.layers.BatchNormalization()(fc1)
    do1 = tf.keras.layers.Dropout(0.5)(bn1)
    fc2 = tf.keras.layers.Dense(20, activation='sigmoid')(do1)
    model = Model(inputs=inp, outputs=fc2)
    return model
```

## training

5エポックと50エポックで行いました。
どちらのエポック数でも val_accuracy (検証用データの正答率) は低い精度になってしまっています。(モデルの汎化性能が低い ?, 作者数が20と出力次元が大きいから ? )

・5エポック
![washasuitei_20.ipynb - Visual Studio Code 2023_05_09 16_10_27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3349148/b655fc2d-2177-c0d3-8504-596d91a7840f.png)

・50エポック
![● washasuitei_20.ipynb - Visual Studio Code 2023_05_09 19_13_19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3349148/2cf2b71d-33bd-5e31-5b88-7038ab2fb01e.png)





## 作者推定

作成したモデルを利用し, テストデータ(20作品)の作者推定を行っていきます。
具体的には, 作者ごとにテストデータを一文ずつ分類モデルで作者推定を行い, 最終的に最も多く予想された作者をその作品の作者とし, 正誤判定を行います。

コードは以下の通りです。

```
target_author = sakusya_list
#テストデータの文一行ずつ分類モデルで作者を予想する
#作者の数だけ
sakusya_count = []
sakusya_num = int(len(sakusya_list))
sakusya_count = [0] * sakusya_num
sakuisya_suitei_rank = []
seikai_num = 0

header = ["テストデータの作者","推定した作者1位","推定した作者2位","推定した作者3位","合否"]
#結果をcsvで出力
with open(aozora_dir + "/result.csv",'w') as f:
    writer = csv.writer(f)
    writer.writerow(header)
    #print(sakusya_count)
    for i in range(0,len(sakusya_list)):
        #カウント初期化
        sakusya_count = [0] * sakusya_num
        date_path = ""
        sakusya_name = sakusya_list[i]
        print(sakusya_name)
        date_path = os.path.join(aozora_dir, sakusya_name)
        #作者/テストデータ/
        date_path = os.path.join(date_path,"テストデータ")
        date_list = os.listdir(date_path)
        for j in date_list:
            if "integ.csv" in j:
                date_path = os.path.join(date_path,j)
                csv_input = pds.read_csv(filepath_or_buffer=date_path,encoding="utf-8",sep=",")
                #print(csv_input.values)
                for l in range(0,len(csv_input.values)):
                    #print(csv_input.values[l,2])
                    txt = csv_input.values[l,2]
                    txt = str(txt)
                    txt = text_encoding(txt)
                    #print(txt)
                    predict_result = predict(np.array([txt]), model_filepath=model_file)
                    pds_predict_result = pds.DataFrame(predict_result, columns=target_author)
                    #print(pds_predict_result)
                    #一番高い類似度の作者をテストデータの作者とする それを一文ずつ行う
                    max_ruigido = 0
                    #一番類似度が高かった作者 0 ~ 19
                    max_sakusya_num = 0
                    for num in range(0,len(pds_predict_result.values[0])):
                        now_r = pds_predict_result.values[0][num]
                        if now_r > max_ruigido:
                            max_ruigido = now_r
                            max_sakusya_num = num
                    #ここでカウントを保存
                    sakusya_count[max_sakusya_num] += 1
        print("モデルの作者推定の結果")
        print(sakusya_count)
        sort_list = sorted(set(sakusya_count))
        ans = sakusya_count.index(sort_list[-1])
        first = ans
        ans = sakusya_count.index(sort_list[-2])
        second = ans
        ans = sakusya_count.index(sort_list[-3])
        third = ans
        if first == i:
            print("正解")
            seikai_num += 1
            gouhi = "正解"
            print("一文ずつの精度", int(( int(sort_list[-1])/ int(len(csv_input.values))) * 100), "%")
        else:
            print("不正解")
            gouhi = "不正解"

        writer.writerow([sakusya_name,sakusya_list[first],sakusya_list[second],sakusya_list[third],gouhi])
print("正答率",seikai_num,"/",sakusya_num)
seido = seikai_num / sakusya_num

```


最終的な精度は以下の通りです。
| エポック数 | 精度(?/20)
|:-----------|------------
| 5       | 17/20 = 85%        
| 50     | 16/20 = 80%      

val_accuracyの精度がよくなかったわりにそこそこ良い結果になりました。
エポック数が低いほうが精度がいい... (ほぼ変わらないが)
50エポックだと過学習を起こしていると考えられるかもしれません。



## まとめ
青空文庫作品の作者推定(作者20名)を行いました。
文単位での作者推定でしたが, そこそこ良い結果が出たと思います。
過学習を回避するためにもっと学習データを増やす必要があるかもしれません。
また, 作者ごとに文章量も違うので, そこを均等にすると精度が改善するかもしれないと思いました。
(テキストの学習量に差があると, データが多い作品の作者を推定しがちになるから)