【python】国会議員の会議出席可否を文章類似度を使って判断してみた


# 何を行うのか？
国会図書館から国会会議録検索システムの検索用APIというものが公表されており自由に使用することができるのでこれを使って国会議員の発言を抽出し、議員の各発言をベクトル化し比較する会議の議題と類似度の高い発言を抽出し類似度が高ければ出席可、類似度が低い発言しかなければ出席不可としました。要はこんな感じ。
![スライド1.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/650713/d4317c35-1091-2bc1-fdba-244b16974fb0.jpeg)
## なぜ行うのか？
国会では儀式的な会議が多く寝ていても問題ない無駄な会議が多いと思っていました。そもそも過去の発言からその人の得意分野や出席すべき会議が分かるので議題と関係ない会議には欠席してもらうことによりもっと他の専門的で生産的な議論したりしっかり布団で寝てほしいと思ったのでこのようなものを作成しました。
![スライド1.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/650713/f76d5d24-6417-2749-d8c6-5d1195221292.jpeg)
# モジュール全体
とりあえずモジュール全体。部分ごとに分けて簡単に説明します。

```python
import untangle
import urllib.parse
import re
import pandas as pd
import numpy as np
import MeCab
import scipy.spatial as sp
from sklearn.feature_extraction.text import TfidfVectorizer

def scrape_congress(filename, name):
    # 抽出部分
    start = '1'
    i = 0
    Reco = ""
    day = ""
    # 議長リスト
    chairs = ["大島理森","赤松広隆","伊達忠一","郡司彰","山崎正昭","輿石東","川端達夫","金子原二郎","向大野新治","冨岡勉","石田昌宏","郷原悟"] 
    while True:
        name = name
        startdate = '2020-10-01'
        enddate = '2021-09-30'
        meeting = '本会議'
        url = 'http://kokkai.ndl.go.jp/api/1.0/speech?'+urllib.parse.quote('startRecord=' + start
                                                                           + '&speaker=' + name
                                                                           + '&nameOfMeeting=' + meeting
                                                                           + '&from=' + startdate
                                                                           + '&until=' + enddate)
        obj = untangle.parse(url)
        art = obj.data.numberOfRecords.cdata
        for record in obj.data.records.record:
            name = record.recordData.speechRecord.speaker.cdata
            if name == '' or name in chairs:    #発言者なしor議長の場合はパス
                pass
            else:
                speechreco = record.recordData.speechRecord
                if not day == speechreco.date.cdata:
                    Reco += speechreco.date.cdata + "\n"            
                day = speechreco.date.cdata
                reco_line = speechreco.speech.cdata
                Reco += reco_line
        Reco += '\n'

        if not i%500:   #500件超えるならここでカキコ
            with open(filename, 'a') as f:
                f.write(Reco)
            Reco = ""
        try:
            start = obj.data.nextRecordPosition.cdata
        except AttributeError:
            print("おわり")
            break
        i += 100
        print("{0}件中{1}件目".format(art,i))
    return Reco

def read_congress_words(path):
    #パスを指定してテキストデータを読み込んでいます
    f = open(path)
    text = f.read()
    f.close()
    
    # stop_wordsを指定
    stop_words = ['　', r'\n', r'\u3000','（拍手）', '―――――――――――――', '――――◇―――――', '、', '─', '—', '（内閣提出）', '・', '「', '」', '（', '）', '\n', '\u3000']
    splited_path = path.split('/')
    text_filename = splited_path[-1]
    # 議員名をパスから取り出しています
    name = text_filename[6:-4]

    #stop_wordsをテキストから削除しています
    prime_minister = '○内閣総理大臣{}君'.format(name)
    minister = '○国務大臣{}君'.format(name)
    congressman = '○{}君'.format(name)
    vice_chair = '○副議長{}君'.format(name)
    stop_words.extend([prime_minister, minister, congressman, vice_chair])
    for word in stop_words:
        text = text.replace(word, '')

    # 正規表現に合致した文字を削除する
    # パターンとしては順に議事最後の登壇などの状況説明文書、大臣の発言の議事最初のラベル、通常議員の発言の議事最初のラベル
    stop_words = ['〔[一-龥ぁ-んァ-ン]+〕', '○([ぁ-んァ-ン一-龥]+)（([ぁ-んァ-ン一-龥]+)君）', '○([ぁ-んァ-ン一-龥]+)君']
    for word in stop_words:
        text = re.sub(word, '', text)
    return text, name

def get_cosine_similarity(x, y):
    # cosine類似度　= 1 - cosine距離
    # sp.distance.cosine(x, y)の返り値はcosine距離
    return 1- sp.distance.cosine(x, y)


def main(agenda, text):
    text_list = text.split('。')
    # 最後の発言は空白のなるため[-1]とスライスし空白を削除
    text_list = text_list[:-1]
    # 重複削除
    text_list = list(set(text_list))
    # target agendaをdataframeに追加しています
    text_list.append(agenda)

    # 形態素解析
    mecab = MeCab.Tagger("-O wakati")
    text_tokenized = []
    for text in text_list:
        text_tokenized.append(mecab.parse(text))
    df = pd.DataFrame(columns=['text', 'text_tokenized'])
    df['text'] = text_list
    df['text_tokenized'] = text_tokenized

    # 各発言にある改行文字を消去
    df['text_tokenized'] = df['text_tokenized'].str.replace(' \n', '')

    # TF-IDFで特徴量化
    vectorizer = TfidfVectorizer(token_pattern=r"(?u)\b\w+\b")
    text_transformed = vectorizer.fit_transform(df['text_tokenized'])
    text_transformed = text_transformed.toarray()
    words_similarity = []
    # 今回は文書ベクトルが数値であらわされているため、コサイン類似度
    target_agenda_idx = -1

    for i in range(len(text_transformed)):
        # cosine類似度　= 1 - cosine距離
        sim = get_cosine_similarity(text_transformed[target_agenda_idx], text_transformed[i])
        words_similarity.append(sim)
        
    words_similarity = np.array(words_similarity)
    words_similarity = pd.DataFrame(words_similarity, columns=['words_similarity'])
    df = pd.concat([df, words_similarity], axis=1)
    # 比較対象発言で類似度が高い発言を抽出
    # トップ20の類似度が高い発言を抽出しています
    topN = 20
    # 類似度でソート
    sorted_df = df.sort_values('words_similarity', ascending=False)
    # 必要のない列を削除
    sorted_df = sorted_df.drop('text_tokenized', axis=1)
    # トップ20を抽出
    similarited_text = sorted_df.iloc[1:topN+1, :]
    similarited_text = similarited_text.reset_index(drop=True)
    return similarited_text

if __name__ == '__main__':
    name = '麻生太郎'
    filename = f'{name}.txt'
    r = scrape_congress(filename, name)
    with open(filename, 'a') as f:
        f.write(r)
    text, name = read_congress_words(filename)
    agenda = '感染症への金融政策面での対応と金融情勢'
    agenda2 = '人間中心のＡＩ社会原則会議の設置について'
    translated_similarited_text = main(agenda, text)
    print(translated_similarited_text)
    translated_similarited_text = main(agenda2, text)
    print(translated_similarited_text)


```

# 国会議員の発言抽出
こちらの方が作成されたモジュールを参考にして作成しています。
[国会議事録検索システムのAPIを使って議事録を取得してみる(python)](https://qiita.com/Sak1361/items/ca8bdf5c699f6a8a7f92)
引数に議員名と発言が入るテキストファイルパスを指定すると発言を取得してくれます。

```python
import untangle
import urllib.parse
import os
import sys
import re

def scrape_congress(filename, name):
    # 抽出部分
    start = '1'
    i = 0
    Reco = ""
    day = ""
    # 議長リスト
    chairs = ["大島理森","赤松広隆","伊達忠一","郡司彰","山崎正昭","輿石東","川端達夫","金子原二郎","向大野新治","冨岡勉","石田昌宏","郷原悟"] 
    while True:
        name = name
        startdate = '2017-10-01'
        enddate = '2019-09-30'
        meeting = '本会議'
        url = 'http://kokkai.ndl.go.jp/api/1.0/speech?'+urllib.parse.quote('startRecord=' + start
                                                                           + '&speaker=' + name
                                                                           + '&nameOfMeeting=' + meeting
                                                                           + '&from=' + startdate
                                                                           + '&until=' + enddate)
        obj = untangle.parse(url)
        art = obj.data.numberOfRecords.cdata
        for record in obj.data.records.record:
            name = record.recordData.speechRecord.speaker.cdata
            if name == '' or name in chairs:    #発言者なしor議長の場合はパス
                pass
            else:
                speechreco = record.recordData.speechRecord
                if not day == speechreco.date.cdata:
                    Reco += speechreco.date.cdata + "\n"            
                day = speechreco.date.cdata
                reco_line = speechreco.speech.cdata
                Reco += reco_line
        Reco += '\n'

        if not i%500:   #500件超えるならここでカキコ
            with open(filename, 'a') as f:
                f.write(Reco)
            Reco = ""
        try:
            start = obj.data.nextRecordPosition.cdata
        except AttributeError:
            print("おわり")
            break
        i += 100
        print("{0}件中{1}件目".format(art,i))
    return Reco

if __name__ == '__main__':
    name = '麻生太郎'
    filename = f'{name}.txt'
    r = scrape_congress(filename, name)
    with open(filename, 'a') as f:
        f.write(r)
```

# 発言の前処理
抽出した発言データに対して前処理を行います。発言テキストのデータのファイルパスを指定すると最低限のストップワード処理などの前処理を行ってくれます。

```python
import re

def read_congress_words(path):
    #パスを指定してテキストデータを読み込んでいます
    f = open(path)
    text = f.read()
    f.close()
    
    # stop_wordsを指定
    stop_words = ['　', r'\n', r'\u3000','（拍手）', '―――――――――――――', '――――◇―――――', '、', '─', '—', '（内閣提出）', '・', '「', '」', '（', '）', '\n', '\u3000']
    splited_path = path.split('/')
    text_filename = splited_path[-1]
    # 議員名をパスから取り出しています
    name = text_filename[6:-4]

    #stop_wordsをテキストから削除しています
    prime_minister = '○内閣総理大臣{}君'.format(name)
    minister = '○国務大臣{}君'.format(name)
    congressman = '○{}君'.format(name)
    vice_chair = '○副議長{}君'.format(name)
    stop_words.extend([prime_minister, minister, congressman, vice_chair])
    for word in stop_words:
        text = text.replace(word, '')

    # 正規表現に合致した文字を削除する
    # パターンとしては順に議事最後の登壇などの状況説明文書、大臣の発言の議事最初のラベル、通常議員の発言の議事最初のラベル
    stop_words = ['〔[一-龥ぁ-んァ-ン]+〕', '○([ぁ-んァ-ン一-龥]+)（([ぁ-んァ-ン一-龥]+)君）', '○([ぁ-んァ-ン一-龥]+)君']
    for word in stop_words:
        text = re.sub(word, '', text)
    return text, name
```


# コサイン類似度比較
メインモジュールの後半で使用するコサイン類似度の計算部分です。scipyを使用してコサイン類似度を計算します。今回は議員の発言と出席可否を検討したい議題の発言との類似度を計算します。

```python
import scipy.spatial as sp

def get_cosine_similarity(x, y):
    # cosine類似度　= 1 - cosine距離
    # sp.distance.cosine(x, y)の返り値はcosine距離
    return 1- sp.distance.cosine(x, y)
```

# メイン処理
メイン処理は以下の順で実行します。
- 発言を形態素解析
- 発言をTF-IDFで特徴量化
- 議題と発言のコサイン類似度計算

形態素解析はMeCabを使用しています。文章の特徴量化は実はBERTなどいろいろ試したのですが結局TF-IDFにしています。そこらへんも今後時間があるときに書ければ。

```python
import pandas as pd
import numpy as np
import MeCab
import scipy.spatial as sp
from sklearn.feature_extraction.text import TfidfVectorizer

def main(agenda, text):
    text_list = text.split('。')
    # 最後の発言は空白のなるため[-1]とスライスし空白を削除
    text_list = text_list[:-1]
    # 重複削除
    text_list = list(set(text_list))
    # target agendaをdataframeに追加しています
    text_list.append(agenda)

    # 形態素解析
    mecab = MeCab.Tagger("-O wakati")
    text_tokenized = []
    for text in text_list:
        text_tokenized.append(mecab.parse(text))
    df = pd.DataFrame(columns=['text', 'text_tokenized'])
    df['text'] = text_list
    df['text_tokenized'] = text_tokenized

    # 各発言にある改行文字を消去
    df['text_tokenized'] = df['text_tokenized'].str.replace(' \n', '')

    # TF-IDFで特徴量化
    vectorizer = TfidfVectorizer(token_pattern=r"(?u)\b\w+\b")
    text_transformed = vectorizer.fit_transform(df['text_tokenized'])
    text_transformed = text_transformed.toarray()
    words_similarity = []
    # 今回は文書ベクトルが数値であらわされているため、コサイン類似度
    target_agenda_idx = -1

    for i in range(len(text_transformed)):
        # cosine類似度　= 1 - cosine距離
        sim = get_cosine_similarity(text_transformed[target_agenda_idx], text_transformed[i])
        words_similarity.append(sim)
        
    words_similarity = np.array(words_similarity)
    words_similarity = pd.DataFrame(words_similarity, columns=['words_similarity'])
    df = pd.concat([df, words_similarity], axis=1)
    # 比較対象発言で類似度が高い発言を抽出
    # トップ20の類似度が高い発言を抽出しています
    topN = 20
    # 類似度でソート
    sorted_df = df.sort_values('words_similarity', ascending=False)
    # 必要のない列を削除
    sorted_df = sorted_df.drop('text_tokenized', axis=1)
    # トップ20を抽出
    similarited_text = sorted_df.iloc[1:topN+1, :]
    similarited_text = similarited_text.reset_index(drop=True)
    return similarited_text
```

# モジュール実行
試しに前財務大臣である麻生太郎氏の発言と簡単な議題を比較します。議題は関係のありそうな日銀総裁講演の議題とあまり関係のなさそうな「人間中心のＡＩ社会原則会議」という会議の議題です。

```python
if __name__ == '__main__':
    name = '麻生太郎'
    filename = f'{name}.txt'
    r = scrape_congress(filename, name)
    with open(filename, 'a') as f:
        f.write(r)
    text, name = read_congress_words(filename)
    agenda = '感染症への金融政策面での対応と金融情勢'
    agenda2 = '人間中心のＡＩ社会原則会議の設置について'
    translated_similarited_text = main(agenda, text)
    print(translated_similarited_text)
    translated_similarited_text = main(agenda2, text)
    print(translated_similarited_text)
```

# 結果
発言と類似度の結果はこのとおり。やはり日銀の議題は類似度が高い発言が多く、AIの議題は類似度が低い発言となっています。

- 議題「感染症への金融政策面での対応と金融情勢」との比較

| ランキング       |       発言 |    類似度    |
|:-----------------|------------------:|:------------------:|
| 1             |次に金融政策と財政政策の組合せについてお尋ねがありました | 0.301|
| 2           |具体的には感染症危機管理体制や保健所体制の整備等によって感染拡大防止に万全を期すとともに予期せぬ状況変化への備えとして五兆円の新型コロナウイルス感染症対策予備費を措置することとしております| 0.288 |
| 3             |具体的には感染症危機管理体制や保健所体制の整備等によって感染拡大防止に万全を期すとともに予期せぬ状況変化への備えとして五兆円の新型コロナウイルス感染症対策予備費を措置することといたしております|0.288|
| 4               |日本経済につきましては新型コロナウイルス感染症の影響により依然として厳しい状況にあります|0.217|
| 5             |他方現在では金融機関が万一破綻をした場合等に備える観点から預金の定額保護に関する業務等は預金保険機構の一般勘定でまた金融機関への対応として行う金融機関の資本増強や一時国有化に関する業務などは預金保険機構のいわゆる危機対応勘定でそれぞれ経理をするということとされております| 0.215|


- 議題「人間中心のＡＩ社会原則会議の設置について」との比較

| ランキング       |       発言 |    類似度    |
|:-----------------|------------------:|:------------------:|
| 1             |まず中小企業のＭアンドＡ促進税制につきましてのお尋ねであります|0.163|
| 2           |最後になりましたが新型コロナ対策の予算に関する特別会計の設置についてのお尋ねがあっております| 0.150|
| 3             |またデジタル社会グリーン社会の実現や全世代型社会保障の構築など中長期的な課題にもしっかり対応するものといたしております|0.134|
| 4               |大門議員から中小企業のＭアンドＡ促進税制中小企業が抱える債務について二問お尋ねがあっています|0.117|
| 5             |このため現在でも関係省庁において検疫体制の計画整備のための人員確保や結核などの入国前スクリーニングの実施の準備出入国審査におけるＩＴ機器の活用などを取り組んでいるものと承知をいたしております| 0.116|

# 終わりに
しきい値を設定して会議の出席を完全に判断することはできませんでしたが、過去の発言と比較してほとんど関係ない会議には出席しないようにすることができるのが分かったと思います。時間があればしきい値の設定も書こうと思います。