【自然言語処理】文書ファイル内部のカテゴリ分類を前処理だけでなんとかしたかった

# はじめに
どうも。こんばんは。長い文書などから特定の話題の部分を当てたいなどと言う状況があると思います。雑誌や新聞、イベントなどの原稿から、特定の話題の部分のみを当てたいといった感じです。
イメージ的には雑誌の記事の中から特定のトピックのみの話題を取ってきてひたすら収集したいなどの場合が対応するでしょう。時系列を扱うようなRNNとかそういったもので解くのが良さそうな気もしますが、前処理を工夫して何とかしてシンプルな文書分類に持ち込んで解いてみます。
今回使用したソースは[GitHub](https://github.com/tomone-hata/text-preprocess-qiita)に置いてありますのでそちらを併せてご確認ください。

## 誰のための記事か（誰のための記事でないか）
かなり具体的な問題の解説となりますので、誰のための記事かを明確にしておきます。→以下に誰のための記事ではないかを書いておきます。
・基礎的なPythonのコーディング（特にnumpy,pandasのライブラリの使い方）がわかる人
→一行、一行の細かいコードの説明は行いません。
・自然言語処理を軽くやったことがある人
→初めてでも頑張れば読めるとは思いますが、基本的な話は省略気味にいきますので、初めてだけど文書分類をやってみたいという方がこの記事を参考にして行うのは非効率かと思います。
・自然言語処理のケーススタディを見てみたい人
→細かい理論的な話はほとんどするつもりはありませんので、原理から知りたい方は他を当たってください。一応私も[ナイーブベイズ](https://qiita.com/tomone_hata/items/67e7f9415dbf5c8ff8ba)の記事を準備していますので、よければご覧ください。こちらでは前処理のtf-idfと、多項分布を用いたナイーブベイズについて、原理面を手厚めに解説した記事を準備しています。


# 課題
今回は次のような課題を考えることにします。そのものズバリの時系列のデータではないのですが、国会の議事録が日毎にまとまって繋がっていcsvファイルを考えることにします。データセットについては国会議事録APIを用いて抽出した議事録ファイルを使用しています。「参考」のところにURLを挙げておきます。
議事録の構成は、各行、会話が会話内容になっており、衆参議員(houses)、委員会(committee)、日付(date)、speech_order(会話順)ごとに並んでいます。
すなわち、ある程度の行範囲で国会の議事録がまとまっており、会話順にソートされております。
今回はこのファイルの中から委員会(committee)の部分を当てると言うことを考えたいと思います。
会議は4択(本会議':0, '厚生労働委員会':1, '国土交通委員会':2, '予算委員会':3)、2016,2017年分のすべての会議の議事録の発言内容を学習させ、2018年分の会議議事内容を当てたいと思います。ファイル構成は以下のようになっています。

| houses  | committee  | speaker  | date  | speech_order  | speech_text |
|--:|:--|:--|:--|--:|--:|:--|
| 衆議院 or 参議院  | 厚生労働委員会  | 議員名  | 20161021  | 1  | 発言内容 |
| 衆議院 or 参議院  | 厚生労働委員会  | 議員名  | 20161021  | 2  | 発言内容 |
| ....            | ....          | ...    | ....      | ...  | ...  |
| 衆議院 or 参議院  | 厚生労働委員会  | 議員名  | 20161021  | 12  | 発言内容 |
| 衆議院 or 参議院  | 本会議 　 　 　  | 議員名  | 20161021  | 1  | 発言内容 |
| ....            | ....          | ...    | ....      | ...  | ...  |
| 衆議院 or 参議院  | 国土交通委員　  | 議員名  | 20160930  | 1  | 発言内容 |
| ....            | ....          | ...    | ....      | ...  | ...  |


| houses  | committee  | speaker  | date  | speech_order  | speech_text |
|--:|:--|:--|:--|--:|--:|:--|
| 衆議院  | 厚生労働委員会  | 丹羽秀樹  | 20161021  | 1  | これより会議を開きます。厚生労働関係の基本施策に関する件について調査を進めます。この際、お諮... |
| 衆議院  | 厚生労働委員会  | 丹羽秀樹  | 20161021  | 2  | 御異議なしと認めます。よって、そのように決しました。 |
| 衆議院  | 厚生労働委員会  | 丹羽秀樹  | 20161021  | 3  | 質疑の申し出がありますので、順次これを許します。田村憲久君。 |
| 衆議院  | 厚生労働委員会  | 田村憲久  | 20161021  | 4  | おはようございます。自民党の田村憲久でございます。きょうは、大臣への質疑、所信に対する質疑と... |
| 衆議院  | 厚生労働委員会  | 鈴木俊彦  | 20161021  | 5  | お答え申し上げます。御存じのように、年金は、将来年金を受給いたします現在の若い方たちが現在年... |

実際のデータはGitHubに圧縮しておいてありますが、サンプルで500行ほど絞ったものを生データでおいておりますので[そちら](https://github.com/tomone-hata/text-preprocess-qiita/blob/master/train_sample.csv)も併せてご確認ください。

今回は、あまり学習モデルにはフォーカスするつもりはないので、Kerasを用いて実装したシンプルな3層のニューラルネットで解くことを考えます。活性化関数にはReLU、評価関数にはbinary_crossentropy、最適化関数にはRMSpropを用いることにします。便宜上クラス化してますので、詳細はGitHub上のソースをご覧ください。ただし今回はこれ以上モデルについては触れませんので、理解できなくとも差し支えありません。

```models.py
    def build(self):
        self.model.add(keras.layers.Dense(units=self.hidden_size[0], \
                                     input_dim=self.x_dim,
                                     kernel_initializer='glorot_uniform', \
                                     bias_initializer='zeros', \
                                     activation='relu' \
                                            ))
        self.model.add(Dropout(self.dropuout))
        self.model.add(keras.layers.Dense(units=self.hidden_size[1], \
                                     input_dim=self.hidden_size[0],
                                     kernel_initializer='glorot_uniform', \
                                     bias_initializer='zeros', \
                                     activation='relu' \
                                            ))
        self.model.add(Dropout(self.dropuout))
        self.model.add(keras.layers.Dense(units=self.n_classes, \
                                     input_dim=self.hidden_size[1],
                                     kernel_initializer='glorot_uniform', \
                                     bias_initializer='zeros', \
                                     activation='softmax' \
                                            ))
        self.model.compile(loss='binary_crossentropy',
                      optimizer=RMSprop(lr=self.learning_rate),
                      metrics=['accuracy'])
```

#　前処理1回目
まずは何はともあれシンプルな文書分類だと思って、前処理を行ってみます。これでいい精度が出るならば、それほど楽な話はありません。speech_textを分かち書きし、名詞、動詞、形容詞のみを採用することにし、活用形はすべて原形に直します。

```execute_model.py
df_train = pd.read_csv('./train.csv', header=0, sep='\t')
df_test  = pd.read_csv('./test.csv', header=0, sep='\t')

#形態素解析についてはMecab_Analysisというクラスで実施。後述。
ma = Mecab_Analysis(dic_path='/usr/local/lib/mecab/dic/mecab-ipadic-neologd')

#名詞、動詞、形容詞以外を外すために使用するタプル。
inflection_accept = ('名詞', '動詞', '形容詞')
train_text = df_train['speech_text'].values.tolist()
test_text  = df_test['speech_text'].values.tolist()

df_train_ma = pd.DataFrame([' '.join([y[-3] if y[1] in inflection_tapple else y[0] for y in ma.Morphological_Analysis(x) if y[1] in inflection_accept]) \
                            for x in  tqdm_notebook(train_text)],\
                            columns=['speech_text_ma'])
df_test_ma  = pd.DataFrame([' '.join([y[-3] if y[1] in inflection_tapple else y[0] for y in ma.Morphological_Analysis(x) if y[1] in inflection_accept]) \
                            for x in  tqdm_notebook(test_text)],\
                           columns=['speech_text_ma'])

df_train = pd.concat((df_train, df_train_ma), axis=1)
df_test = pd.concat((df_test, df_test_ma), axis=1)
```

形態素解析にはMeCabを使用し、毎回、辞書を読み込むのが嫌なのでクラス化しておきます。Morphological_Analysisの関数にてMeCabによる形態素解析の結果をそのままsplitしてリストで返す関数とします。

```utils.py
class Mecab_Analysis(object):
    def __init__(self, dic_path=''):
        try:
            if dic_path:
                dic_path = '-d %s' % dic_path
                self.m = MeCab.Tagger(dic_path)
            else:
                self.m = MeCab.Tagger()
        except RuntimeError:
            print('Invalid dictionary path. Use the default dictionary.')
            self.m = MeCab.Tagger()

        self.m.parse('')


    def Morphological_Analysis(self, text):
        result_list = []
        m_texts = self.m.parse(text)
        m_texts = m_texts.split('\n')
        for m_text in m_texts:
            m_text = m_text.split('\t')
            word = m_text[0]
            if word == 'EOS':
                break

            pos = m_text[1]
            pos = pos.split(',')
            append_list = [word] + pos
            result_list.append(copy.copy(append_list))

        return result_list

```

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

| houses  | committee  | speaker  | date  | speech_order  | speech_text  | speech_text_ma |
|--:|:--|:--|:--|--:|--:|:--|:--|
| 衆議院  | 厚生労働委員会  | 丹羽秀樹  | 20161021  | 1  | これより会議を開きます。厚生労働関係の基本施策に関する件について調査を進めます。この際、お諮...  | これ 会議 開く 厚生 労働 関係 基本 施策 件 調査 進める 際 諮る いたす 本件 調... |
| 衆議院  | 厚生労働委員会  | 丹羽秀樹  | 20161021  | 2  | 御異議なしと認めます。よって、そのように決しました。  | 異議 ない 認める よう 決す |
| 衆議院  | 厚生労働委員会  | 丹羽秀樹  | 20161021  | 3  | 質疑の申し出がありますので、順次これを許します。田村憲久君。  | 質疑 申し出 ある これ 許す 田村憲久 君 |
| 衆議院  | 厚生労働委員会  | 田村憲久  | 20161021  | 4  | おはようございます。自民党の田村憲久でございます。きょうは、大臣への質疑、所信に対する質疑と...  | 自民党 田村憲久 きょうは 大臣 質疑 所信 質疑 こと 久しぶり いう そちら 答弁 する... |
| 衆議院  | 厚生労働委員会  | 鈴木俊彦  | 20161021  | 5  | お答え申し上げます。御存じのように、年金は、将来年金を受給いたします現在の若い方たちが現在年...  | お答え 申し上げる 御存じ よう 年金 将来 年金 受給 いたす 現在 い方 たち 現在 年... |

こちらをtf-idfにてベクトル化します。今回はベクトルの次元数が増大してしまったので、特異値分解により次元圧縮をします。65008次元から4096次元まで圧縮しました(数値に明確な根拠はありませんが、全体を通して累積寄与率が8割くらいになるキリのいい数値としました)。訓練用データの2割を拝借し、検証用データとします。

```execute_model.py
x_train_text = df_train['speech_text_ma'].values.tolist()
x_test_text  = df_test['speech_text_ma'].values.tolist()

#委員会をコード化しておく
classify_dict = {'本会議':0, '厚生労働委員会':1, '国土交通委員会':2, '予算委員会':3}
y_train = [classify_dict[x] for x in df_train['committee'].values.tolist()]
y_test  = [classify_dict[x] for x in df_test['committee'].values.tolist()]

corpus = x_train_text + x_test_text
train_size= len(x_train_text)

cv = CountVectorizer()
wc = cv.fit_transform(corpus)
ttf = TfidfTransformer()
tfidf = ttf.fit_transform(wc)
print(tfidf.shape)

svd = TruncatedSVD(n_components=4096, n_iter=3)
tfidf_svd = svd.fit_transform(tfidf)

x_train = tfidf_svd[:train_size,:]
x_test = tfidf_svd[train_size:,:]
print(x_train.shape)
print(x_test.shape)
print(np.sum(svd.explained_variance_ratio_))

#ニューラルネット用にone-hotベクトルを作成する。
y_labels = y_train + y_test
y_labels_one_hot = to_categorical(y_labels)
y_train = y_labels_one_hot[:train_size]
y_test = y_labels_one_hot[train_size:]

#学習データの2割を検証用データに割り当てる
train_len = int(x_train.shape[0] * 0.8)
x_train_nn = x_train[:train_len]
y_train_nn = y_train[:train_len]
x_valid_nn = x_train[train_len:]
y_valid_nn = y_train[train_len:]
```

## 正解率
正解率が85.5 %となりました(5回計測の平均値)。適当な前処理の割に思ったより悪くないなぁというのが率直な感想なのですが、やはり90 %以上目指したいなぁと思いました。


# 前処理2回目
よくある工夫をしてみます。まず内容を列挙します。

1. 苗字と名前の間などに全角スペースが入っているケースがあるので全角スペースを取り除く  
2. 数字や人名を適当なものに置き換える。  
→数字は全て1に置き換える。人名については一律固定文言に置き換える。  
3. 日付については意味をなさないので全て適当なものに置き換える。  
→日付については「1年1月1日」に置き換える。  
4. stopwaordを設ける

まずデータを見て気になったのは、以下のように苗字の名前の間など無意味な全角スペースの多さが気になりました。名前は苗字、名前で極力一つで判定させたい上に、それ以外の箇所についても意味をなさないのでこちらは詰めたいと思います。

| houses  | committee  | speaker  | date  | speech_order  | speech_text |
|--:|:--|:--|:--|--:|--:|:--|
| 衆議院  | 本会議  | 大島理森  | 20180719  | 28  | 右の結果、議院運営委員長古屋圭司君解任決議案は否決されました。（拍手）辻元清美君外六名提出議院運営委員長古屋圭司君解任決議案を可とする議員の氏名阿久津　幸彦君　　　阿部　　知子君　　　青柳　陽一郎君　　　荒井　　　聰君池田　　真紀君　　　石川　　香織君　　　．．． |

また、数値についても1回、2回、1月、2月等、特に大きさに意味はありませんのですべて1に置換してしまいます。あまり政治には詳しくないのですが、所属する委員会が学習用の2016,2017年と予測する2018年で異なる可能性がありますから固定文言で置き換えてしまいます。以下のように実装しました。

```utils.py
pattern_tapple = ((r'[一二三四五六七八九十〇]+号', r'一号'), \
                  (r'平成[一二三四五六七八九十〇]+年', r'平成一年'), \
                  (r'[一二三四五六七八九十〇]+月', r'一月'), \
                  (r'(午[(前)|(後)])[一二三四五六七八九十〇]+時',r'午前一時'), \
                  (r'[一二三四五六七八九十〇]+月[一二三四五六七八九十〇]+日', r'一月一日'),\
                  (r'^[一二三四五六七八九十壱弐参拾百千万萬億兆〇]$', r'一'),\
                  (r'^[0-9]+$', r'1'), \
                  (r'^[0-9]+(|名|号|分|円|番|発|戦|年度|種|期|年|月|日|時|)$', r'1\1'), \
                  (r'^[一二三四五六七八九十〇]+(|名|号|分|円|番|発|戦|年度|種|期|年|月|日|時|)$', r'1\1') \


    )
def translate_word(ma_word, stopwords = []):
    word = ma_word[-3] if ma_word[1] in inflection_tapple else ma_word[0]
    word = mojimoji.zen_to_han(word, kana=False)
    #stopwordsが定義された場合の対応
    if stopwords and word in stopwords:
        word = ''
        return word

    if ma_word[2] == '固有名詞' and ma_word[3] == '人名':
        word = '佐村河内守'
        return word

    for patterns in pattern_tapple:
        word = re.sub(patterns[0], patterns[1], word)

    return word
```

```execute_model.py
df_train_ma = pd.DataFrame([' '.join([translate_word(y) for y in ma.Morphological_Analysis(re.sub(r'　','',x)) if y[1] in inflection_accept]) \
                            for x in  tqdm_notebook(train_text)],\
                            columns=['speech_text_ma'])
df_test_ma  = pd.DataFrame([' '.join([translate_word(y) for y in ma.Morphological_Analysis(re.sub(r'　','',x)) if y[1] in inflection_accept]) \
                            for x in  tqdm_notebook(test_text)],\
                           columns=['speech_text_ma'])
```
数値の置き換えについては実際のデータを見ながら慎重にやりました。これは「千葉」や「21世紀美術館」など数字の意味で使われていないものや固有名詞の一部などを、「一葉」、「1世紀美術館」などと置き換えないようにという対策のためです。ただ、実用上このような変換をしても問題ないため、今考えると、ここまでいろんなことを想定しなくても、実用上数字と漢数字をすべて1に置き換えるで問題ないと思います。人名は何でもいいのですが、一律、「佐村河内守」に置き換えてしまいました。こちらが前処理をやり直した結果です。人名の部分が「佐村河内守」に、二期の部分が「1期」になっているのがわかると思います。stopwordについてはtf-idfのところで考慮します。

| houses  | committee  | speaker  | date  | speech_order  | speech_text  | speech_text_ma |
|--:|:--|:--|:--|--:|--:|:--|:--|
| 衆議院  | 厚生労働委員会  | 高鳥修一  | 20180427  | 1  | これより会議を開きます。開会に先立ちまして、立憲民主党・市民クラブ、希望の党・無所属クラブ、...  | これ 会議 開く 開会 先立つ 立憲民主党 市民 クラブ 希望の党 無所属クラブ 無所属の会... |
| 衆議院  | 厚生労働委員会  | 高鳥修一  | 20180427  | 2  | 速記を起こしてください。理事をして再度御出席を要請させましたが、立憲民主党・市民クラブ、希望...  | 速記 起こす くださる 理事 する 出席 要請 する せる 立憲民主党 市民 クラブ 希望の... |
| 衆議院  | 厚生労働委員会  | 高鳥修一  | 20180427  | 3  | 御異議なしと認めます。よって、そのように決しました。  | 異議 ない 認める よう 決す |
| 衆議院  | 厚生労働委員会  | 高鳥修一  | 20180427  | 4  | 質疑の申出がありますので、順次これを許します。船橋利実君。  | 質疑 申出 ある これ 許す 佐村河内守 君 |
| 衆議院  | 厚生労働委員会  | 船橋利実  | 20180427  | 5  | おはようございます。自由民主党の船橋利実でございます。二期目復帰をさせていただきまして、初め...  | 自由民主党 佐村河内守 1期 目 復帰 する せる いただく 厚生労働委員会 質問 する せ... |

最後にstopwordについてです。stopwordとは学習用コーパスから削除する語彙のことで

1. 辞書を用いる方法
2. 頻度で絞る方法

の2パターンあります。今回は眺めてみると

- 極端に頻度が少ない単語が多い
- 頻度が多い単語は「する」など意味をなさないような単語が多い

などの関係で両方を削除することにします。これらの影響から「2.頻度で絞る方法」を採用します。今回tf-idfに用いたCountVectorizerでは頻度でハンドリングするパラメータがありますので、そちらで簡単に実装できます。以下のように実装しました。

```execute_model.py
cv = CountVectorizer(max_df=0.5, min_df=3)
wc = cv.fit_transform(corpus)
ttf = TfidfTransformer()
tfidf = ttf.fit_transform(wc)
```
max_dfは頻度が一定を超えた場合に捨てる、min_dfは頻度が一定以下の場合に捨てるパラメータとなります。int型で記載した場合は回数、float型で指定した場合は割合となります。
上記の指定だと

- 半分以上の文書に出現した語彙
- 3回未満の文書にしか出現しなかった語彙

を除くこととなります。ちなみにこちらですが、動きを見るに学習データ（1コーパス）あたり1回以上出現したものはすべて1回とカウントしているようです。

## 正解率
さて、これを行なった場合の肝心の正解率ですが、

＼(^o^)／＼(^o^)／＼(^o^)／＼(^o^)／＼(^o^)／＼(^o^)／＼(^o^)／＼(^o^)／
84.5 %
＼(^o^)／＼(^o^)／＼(^o^)／＼(^o^)／＼(^o^)／＼(^o^)／＼(^o^)／＼(^o^)／

　＿＿＿ 　　　　　　　♪　　∩∧__,∧ 
/　||￣￣||　　　　　　　　　_ ヽ（ ＾ω＾ ）7　　どうしてこうなった！ 
|.....||＿＿|| 　　　　　　　　/｀ヽJ　　　,‐┘　　　どうしてこうなった！
|￣￣＼三　 /￣￣￣/ 　´｀ヽ､_　 ノ　　
|　　　　|　( ./　　　　 / 　　　　　｀) ）　♪
前処理を工夫した結果1 %ほど正解率が下がるという結果になってしまいました。。。
良かれと思ってやったことが悪影響を及ぼす。。。
あるある過ぎて泣けてきます。。

# 前処理(3回目)
さて、ようやくここに来て一般的な工夫では無理だという事実と向き合います。そもそも政治のスピーチなので
「静粛に」
「〜君」
など一言だけの発言もあり、これだけで委員会を当てるのはかなり難しいという判断です。前後の文脈の助けを借りるために下記のような実装をしました。

```utils.py
def create_chunk_dataset(df, chunk_size=5):
    assert chunk_size % 2 == 1, 'chunk_size should be odd number.'
    labels = df['committee'].values.tolist()
    texts = df['speech_text_ma'].values.tolist()
    chunk_mean = chunk_size // 2
    df_len = len(df)
    #先頭のラベルをdfと合わせるために先頭にchunk_meanの数だけ末尾のindexの要素を加える。
    #末尾も同様に先頭のindexを加える
    texts = [texts[idx] for idx in range(chunk_mean*(-1), 0)] + \
            texts + \
            [texts[idx] for idx in range(0, chunk_mean+1)]
    result_texts = []
    #真ん中のword頻度を最大に端に行けば行くほど頻度を少なくする
    for idx in range(0, df_len):
        words = []
        for jdx in range(idx, idx+chunk_size):
            origin_point = jdx - idx
            word_freq = chunk_mean - abs(chunk_mean - origin_point)
            words.extend([texts[jdx] for _ in range(0, word_freq+1)])

        result_texts.append(' '.join(words))

    return result_texts, labels
```


```execute_model.py
chunk_size = 15
x_train_text, y_train = create_chunk_dataset(df_train, chunk_size=chunk_size)
x_test_text, y_test = create_chunk_dataset(df_test, chunk_size=chunk_size)


```
こちらですが、コーパスに前後の文脈の語彙も加えます。加える量をchunk_sizeで指定しますが、例えば、chunk_sizeを5で指定した場合は前後二つずつの文書と当てたい文書の語彙を加えます。
このchunk_size=5の場合、当てたい文書の語彙を3つ、前後一つ目の文書の語彙を2つ、前後二つ目の文書の語彙を1回ずつ入れます。以下のようなデータを考えてみます。chunk_sizeが1の場合(create_chunk_datasetを通さない場合と同義)は以下のようになります。

| houses  | committee  | speaker  | date  | speech_order  | speech_text  | speech_text_ma |
|--:|:--|:--|:--|--:|--:|:--|:--|
| 衆議院  | 厚生労働委員会  | 高鳥修一  | 20180427  | 1  | これより会議を開きます。開会に先立ちまして、立憲民主党・市民クラブ、希望の党・無所属クラブ、...  | これ 会議 開く 開会 先立つ 立憲民主党 市民 クラブ 希望の党 無所属クラブ 無所属の会... |
| 衆議院  | 厚生労働委員会  | 高鳥修一  | 20180427  | 2  | 速記を起こしてください。理事をして再度御出席を要請させましたが、立憲民主党・市民クラブ、希望...  | 速記 起こす くださる 理事 する 出席 要請 する せる 立憲民主党 市民 クラブ 希望の... |
| 衆議院  | 厚生労働委員会  | 高鳥修一  | 20180427  | 3  | 御異議なしと認めます。よって、そのように決しました。  | 異議 ない 認める よう 決す |
| 衆議院  | 厚生労働委員会  | 高鳥修一  | 20180427  | 4  | 質疑の申出がありますので、順次これを許します。船橋利実君。  | 質疑 申出 ある これ 許す 佐村河内守 君 |
| 衆議院  | 厚生労働委員会  | 船橋利実  | 20180427  | 5  | おはようございます。自由民主党の船橋利実でございます。二期目復帰をさせていただきまして、初め...  | 自由民主党 佐村河内守 1期 目 復帰 する せる いただく 厚生労働委員会 質問 する せ... |

3つ目の文書に着目してみます。結果は以下のようになります。

```python
#chunk_size=5の場合
print(x_train_text[2])

#結果(みやすくするために行ごとの改行と〜行目というコメントを挿入しています)。
'''
#1行目
これ 会議 開く 開会 先立つ 立憲民主党 市民 クラブ 希望の党 無所属クラブ 無所属の会 日本共産党 所属 委員 出席 要請 いたす 出席 得る られる 理事 する 出席 要請 する せる お待ち くださる 速記 とめる くださる 速記 中止
#2行目
速記 起こす くださる 理事 する 出席 要請 する せる 立憲民主党 市民 クラブ 希望の党 無所属クラブ 無所属の会 日本共産党 所属 委員 出席 得る られる 議事 進める 厚生 労働 関係 基本 施策 件 調査 進める 際 諮る いたす 本件 調査 ため 本日 参考人 日本年金機構 佐村河内守 佐村河内守 君 出席 求める 意見 聴取 する 政府参考人 厚生労働省大臣官房 生活 衛生 食品 安全 審議官 佐村河内守 佐村河内守 君 大臣官房 年金 管理 審議官 佐村河内守 佐村河内守 君 医 政局 長 佐村河内守 佐村河内守 君 健康局 長 佐村河内守 君 社会・援護局 障害 保健福祉部 長 佐村河内守 佐村河内守 君 老健局 長 佐村河内守 佐村河内守 君 保険 局長 佐村河内守 君 出席 求める 説明 聴取 いたす 存じる 異議 ある 異議 ない 呼ぶ 者 あり
#2行目
速記 起こす くださる 理事 する 出席 要請 する せる 立憲民主党 市民 クラブ 希望の党 無所属クラブ 無所属の会 日本共産党 所属 委員 出席 得る られる 議事 進める 厚生 労働 関係 基本 施策 件 調査 進める 際 諮る いたす 本件 調査 ため 本日 参考人 日本年金機構 佐村河内守 佐村河内守 君 出席 求める 意見 聴取 する 政府参考人 厚生労働省大臣官房 生活 衛生 食品 安全 審議官 佐村河内守 佐村河内守 君 大臣官房 年金 管理 審議官 佐村河内守 佐村河内守 君 医 政局 長 佐村河内守 佐村河内守 君 健康局 長 佐村河内守 君 社会・援護局 障害 保健福祉部 長 佐村河内守 佐村河内守 君 老健局 長 佐村河内守 佐村河内守 君 保険 局長 佐村河内守 君 出席 求める 説明 聴取 いたす 存じる 異議 ある 異議 ない 呼ぶ 者 あり 
#3行目
異議 ない 認める よう 決す
#3行目 
異議 ない 認める よう 決す 
#3行目
異議 ない 認める よう 決す
#4行目
質疑 申出 ある これ 許す 佐村河内守 君
#4行目
質疑 申出 ある これ 許す 佐村河内守 君
#5行目
自由民主党 佐村河内守 1期 目 復帰 する せる いただく 厚生労働委員会 質問 する せる いただく 機会 頂戴 いたす 委員長 始め 理事 皆様 委員 皆様 感謝 申し上げる 次第 佐村河内守 厚生労働大臣 始める 政務 1 役 厚生労働省 皆様 佐村河内守 日本年金機構 佐村河内守 お願い 申し上げる 次第 初め 薬剤耐性菌 伺い する まいる 先日 国産 輸入 鶏肉 半数 抗生物質 効く 薬剤耐性菌 検出 する れる する 調査結果 厚生労働省 研究 班 出す れる おる 専門家 不要 抗菌 薬 使用 控える 声 上がる おる 抗菌薬 効く 薬剤耐性菌 国 行動 計画 医療機関 家畜 ペット 環境 中 耐性菌 監視 抗菌 薬 適正 使用 促す 人 1 1 1 1 年 抗菌薬 使用 量 1 1 1 1 年 当時 1 分の 1 減らす ふう ある 着実 達成 向ける 取組 伺い いたす
'''

```
実際にこのような水増しをして問題ないのかと思われるかもしれませんがtf-idfの定義は以下のように語順に依存せず、かつ各文書に含まれる単語の頻度のみに依存するため問題ありません。

$$ \textrm{tfidf}\_{i,j} =  \textrm{tf}\_{i,j} \times \textrm{idf}\_{j} $$

$$ \textrm{tf}_{i,j} = \frac{\mbox{文書} d\_{i} \mbox{に含まれる} t\_{j} \mbox{の個数} }{\mbox{文書} d\_{i} \mbox{内のすべての単語の個数} } = \frac{ | t\_{j}  \in d\_{i} | }{ \sum\_{t\_{l} \in d\_{i}} | t\_{l} | } $$

$$ \textrm{idf}_{j} = \log \left( \frac{ \mbox{全文書数} +1 }{ \mbox{単語} t\_{j} \mbox{が含まれる文書数} +1 } \right) +1 = \log \left( \frac{|D| +1 }{ | \\{ d: t\_{j} \in d  \\} | +1} \right) +1  $$

[拙著](https://qiita.com/drafts/67e7f9415dbf5c8ff8ba)より引用。

## 正解率
色々試してみた結果chunk_size=15での成績が良かったのでそこの正解率を採用します（実際にはこの問題の場合はchunk_sizeを上げれば上げるほど正解率が上がったため、もう少し大きい方がいいかもしれません）。正解率は
90.3 %
となりました。このような工夫をすることで5 %ほど精度を向上させることができました。

# まとめ(自戒も含む)
このような工夫で正解率を高めることができる反面、境目の判定がどうしてもこれでは難しく、開始点と終了点がこれではずれてしまう問題が残ってしまいます。
最悪発言の真ん中の方を誤答してしまうのは、後処理の工夫次第で（前後n個ラベルと合わせるなどの工夫）なんとかなるかもしれませんが、精度を追いかけるなら横着をせずに別のアルゴリズムを用いるべきだと思います。
ただ、ざっくり取ればいい場合などには十分使用できるのはないかなぁと思います。あとはAutoMLを使用するケースなどモデルをがっつりいじれない場合などは、このような工夫で簡単な問題で落とすというのは一つの解決策になるのかなぁと思っていたりします。

# 参考
[自然言語処理における前処理の種類とその威力](https://qiita.com/Hironsan/items/2466fe0f344115aff177)

[【Python】自然言語処理で使われるTF-IDFと単純ベイズ分類器(Naive Bayes)について使いながら解説する](https://qiita.com/tomone_hata/items/67e7f9415dbf5c8ff8ba)

https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.CountVectorizer.html

https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfTransformer.html

http://kokkai.ndl.go.jp/api.html