自然言語処理の力で世の中から暗いサイトを駆逐するChrome拡張を作った

#動機
コロナウィルス大流行で最近は暗いニュースばかり…:pensive:
→明るいニュースしか目に入らなければポジティブな気持ちになれるはず！

#作ったもの
Googleの検索結果を内容が暗いほど見えづらく表示するChrome拡張機能。
Chromeストア：[Opty](https://chrome.google.com/webstore/detail/opty-optimistic-search-fi/bgjckeoghfogflpgbmgikhdmaidnkamd/related?hl=ja)
[github](https://github.com/tomoino/opty-pn-classifier)

###実際の画面
|暗いサイト|明るいサイト|
|---|---|
|<img width="350" alt="スクリーンショット 2020-04-22 11.01.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/623530/8851fba1-ea6c-2b40-64b8-7fb198ed7c6b.png">|<img width="350" alt="スクリーンショット 2020-04-22 11.03.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/623530/22fde8fa-1942-491f-7f76-ba5fb9710850.png">|

###使い方
[ここ](https://chrome.google.com/webstore/detail/opty-optimistic-search-fi/bgjckeoghfogflpgbmgikhdmaidnkamd/related?hl=ja)からChrome拡張機能をインストールして、検索するだけ！
参考：[拡張機能のインストール方法](https://support.google.com/chrome_webstore/answer/2664769?hl=ja)

#環境
人員：大学生2名([井上智裕](https://twitter.com/tomoino925/)、[渡辺豪志](https://twitter.com/dubianhaozhi))
制作期間：1日
Cloud Functions
Python 3.7
JavaScript
MeCab

##システム構成
![図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/623530/351d902e-d552-35fb-2b01-1c3b2930ab98.png)

#明るさ判定
文章を単語に分解して、要素ごとに明るいか暗いかの判定をします。

文章を単語に分解する処理には、MeCabを利用しています。
たとえば、

```morph.py
import MeCab

tagger = MeCab.Tagger()
result = tagger.parse('新型コロナウィルスが世界的に大流行しています。')
print(result)
```

という処理を実行すると、

```解析結果
新型    名詞,一般,*,*,*,*,新型,シンガタ,シンガタ
コロナ  名詞,一般,*,*,*,*,コロナ,コロナ,コロナ
ウィルス        名詞,一般,*,*,*,*,ウィルス,ウィルス,ウィルス
が      助詞,格助詞,一般,*,*,*,が,ガ,ガ
世界    名詞,一般,*,*,*,*,世界,セカイ,セカイ
的      名詞,接尾,形容動詞語幹,*,*,*,的,テキ,テキ
に      助詞,副詞化,*,*,*,*,に,ニ,ニ
大      接頭詞,名詞接続,*,*,*,*,大,ダイ,ダイ
流行    名詞,サ変接続,*,*,*,*,流行,リュウコウ,リューコー
し      動詞,自立,*,*,サ変・スル,連用形,する,シ,シ
て      助詞,接続助詞,*,*,*,*,て,テ,テ
い      動詞,非自立,*,*,一段,連用形,いる,イ,イ
ます    助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
。      記号,句点,*,*,*,*,。,。,。
EOS
```

のように分解されます。
分解された結果からそれぞれの単語の活用されてない形を取り出して利用します。

要素の明るさ判定には、東北大学の乾・鈴木研究室が公開している[日本語評価極性辞書](http://www.cl.ecei.tohoku.ac.jp/index.php?Open%20Resources%2FJapanese%20Sentiment%20Polarity%20Dictionary)を使わせていただきました。

こちらの辞書では、日本語の用言をポジティブ（明るい）またはネガティブ（暗い）に、名詞をp（ポジティブ）、n（ネガティブ）、e（どちらでもない）の3段階に分類しています。

```wago.121808.pn
ネガ（経験）	あがく
ネガ（経験）	あきらめる
ネガ（経験）	あきる
ネガ（経験）	あきれる
ネガ（経験）	あきれる た
```

```pn.csv.m3.120408.trim
ありがたさ	p	〜がある・高まる（存在・性質）
ありがたみ	p	〜がある・高まる（存在・性質）
ありがた迷惑	n	〜である・になる（評価・感情）主観
ありのまま	e	〜である・になる（評価・感情）主観
あるがまま	e	〜である・になる（評価・感情）主観
```
文章の構成要素のそれぞれをポジティブなら1、ネガティブなら-1、それ以外なら0と置き換えて、その平均を文章の明るさとしています。

以下、開発中につまずいたポイントです。

##つまずきポイント１：極性辞書 ≠ 単語ごとの明るさ
文章を単語に切り分けて、単語をkeyに極性辞書を検索すればいいかと安易に考えていたのですが、そんな単純ではなかったです。極性辞書には"良い"のような単語単体だけでなく、"良くない"というように2つ以上の語（この場合は"良い"+"ない"）からなる要素も登録されています。
そのため、複数の単語からなる要素で検索できるように辞書を再構成し、pickleにしてから利用しています。

```python:main.py
    # 辞書に格納
    for line in pn_noun_file:
        line = line.replace('\n', '').split('\t')
        if line[1] == 'e': # ポジティブでもネガティブでもない行は無視
            continue
        # 極性辞書に登録されている単語を基本形に変換したリスト
        basic_form = convert_to_basic_form(line[0]) 
        # 基本形が取得できない行と基本形が1文字になる行については無視
        if not basic_form:
            continue
        elif len(basic_form) == 1 and len(basic_form[0]) == 1:
            continue
        key = basic_form[0]
        if key not in pn_dict:
            pn_dict[key] = {}
        # 基本形をつなぎ合わせたものと明るさを組みにして格納
        pn_dict[key][(',').join(basic_form)] = 1 if line[1] == 'p' else - 1
```

##つまずきポイント2：楽しくない = ポジティブ？
"良くない"がネガティブな要素として極性辞書に登録されている一方で、"楽しくない"は登録されていないため、"楽しい"という部分にだけ反応してポジティブな単語だと判定されてしまいました。そこで、～ないという形になった場合は、それまでの部分の明るさの値が反転するようにしました。

```python:main.py
# PN判定。リクエスト中の要素のPN値の平均を返す。
def calc_pn(basic_form):
    pn_dict = pickle.load(open('pn.pkl', 'rb'))
    pn_values = [] # 文章内の各要素のPN判定の数値を格納

    while basic_form:
        pn_value = 0
        del_num = 1  # リストから削除する件数
        beginning = basic_form[0] # 先頭の単語をkeyにする

        if beginning in pn_dict:
            for index, word in enumerate(basic_form):
                if word == "。" or word == "、":  #　文の句切れが来たら、中断する
                    break
                if index == 0:
                    joined_basic_forms = beginning
                else:
                    joined_basic_forms += ',' + word

                if word == "ない" and del_num == index: # ポジネガ反転が必要
                    print('reverse')
                    pn_value *= -1
                    del_num = index + 1

                if joined_basic_forms in pn_dict[beginning]:
                    pn_value = pn_dict[beginning][joined_basic_forms]
                    del_num = index + 1

        pn_values.append(pn_value)
        del basic_form[0:del_num]

    return sum(pn_values) / len(pn_values)
```
#改善ポイント
- 速度が遅い：現状、検索結果表示からスタイル反映まで3秒くらいかかる。
- 明るいページの装飾：明るいほど目立つようにしたい。
 
#最後に
コロナ自粛で時間を持て余していたので、春休み中に勉強を兼ねて作ってみました。
他にも開発中です！
よければ、LGTMとツイッターのフォローお願いします！

拡張機能：[Opty](https://chrome.google.com/webstore/detail/opty-optimistic-search-fi/bgjckeoghfogflpgbmgikhdmaidnkamd/related?hl=ja)
Twitter：[井上智裕](https://twitter.com/tomoino925/)、[渡辺豪志](https://twitter.com/dubianhaozhi)

#参考文献
1. [ネガポジ判定を行うGem作ってみた](https://qiita.com/hatt0519/items/e6352d31311418f38227
)
- [[Python] 時系列CSVの読み込みを爆速化する](https://qiita.com/Yuhsak/items/7c8e2b131cc536ef9ba1
)
- [Cloud Functions](https://cloud.google.com/functions?hl=ja)
- [Chrome拡張の作り方 (超概要)](https://qiita.com/RyBB/items/32b2a7b879f21b3edefc)
- [Chrome 拡張機能のマニフェストファイルの書き方](https://qiita.com/mdstoy/items/9866544e37987337dc79)