アレでアレをアレしてみた（自然言語処理で固有名詞をマスキングしてみた）

# 概要

やりたいことの例

```
input : 弊社が来年リリースする新製品のmasahiko-012の発表会を東京qiita会館で行いたい
↓
output: 弊社が来年リリースする新製品のアレの発表会をあそこで行いたい
```

文章内の固有名詞をマスキングする。（別の単語に置換）


固有名詞→「アレ」
人物名→「あの人」
場所→「あそこ」

にする。

<br>

「社外秘の文章をスライドで公開したい」というときなど、個人情報や開発中の製品名をマスキングするのに便利だと思います！


## 実行環境
- Windows10
- Pyhton 3.6.3

## 固有名詞の抽出
「アレ」などに置き換える単語を判別するために、[COTOHA API](https://api.ce-cotoha.com) の固有表現抽出APIを活用しました。
固有表現を抽出できる上、「人名」「地名」などのクラス名も判別してくれます。おあつらえ向きです！


※参考 
[(自然言語処理を簡単に扱えると噂のCOTOHA APIをPythonて゛使ってみた)](https://qiita.com/gossy5454/items/83072418fb0c5f3e269f)

今回、この固有表現抽出APIの解析結果をもとに、下表のように単語を置き換えました。

| 単語の予測クラス（解析結果） | 置き換える単語 | 
|:-----------|------------:|
| ART | アレ |
| ORG | アレ |
| PSN | あの人 |
| LOC | あそこ |


## 実装

<details><summary>クリックで展開する</summary><div>

```python:replace-ne.py
import os
import urllib.request
import json
import configparser
import codecs

# 解析対象文（input）
sentence = "弊社が来年リリースする新製品のmasahiko-012の発表会を東京qiita会館で行いたい"

DEVELOPER_API_BASE_URL = "https://api.ce-cotoha.com/api/dev/nlp/"
ACCESS_TOKEN_PUBLISH_URL = "https://api.ce-cotoha.com/v1/oauth/accesstokens"
CLIENT_ID = "XXXXXX"
CLIENT_SECRET = "XXXXXXX"

artifact = "アレ"
person = "あの人"
location = "あそこ"

# COTOHA API操作用クラス
class CotohaApi:
    # 初期化
    def __init__(self, client_id, client_secret, developer_api_base_url, access_token_publish_url):
        self.client_id = client_id
        self.client_secret = client_secret
        self.developer_api_base_url = developer_api_base_url
        self.access_token_publish_url = access_token_publish_url
        self.getAccessToken()

    # アクセストークン取得
    def getAccessToken(self):
        # アクセストークン取得URL指定
        url = self.access_token_publish_url

        # ヘッダ指定
        headers={
            "Content-Type": "application/json;charset=UTF-8"
        }

        # リクエストボディ指定
        data = {
            "grantType": "client_credentials",
            "clientId": self.client_id,
            "clientSecret": self.client_secret
        }
        # リクエストボディ指定をJSONにエンコード
        data = json.dumps(data).encode()

        # リクエスト生成
        req = urllib.request.Request(url, data, headers)

        # リクエストを送信し、レスポンスを受信
        res = urllib.request.urlopen(req)

        # レスポンスボディ取得
        res_body = res.read()

        # レスポンスボディをJSONからデコード
        res_body = json.loads(res_body)

        # レスポンスボディからアクセストークンを取得
        self.access_token = res_body["access_token"]


    # 固有表現抽出API
    def ne(self, sentence):
        # 固有表現抽出API URL指定
        url = self.developer_api_base_url + "v1/ne"
        # ヘッダ指定
        headers={
            "Authorization": "Bearer " + self.access_token,
            "Content-Type": "application/json;charset=UTF-8",
        }
        # リクエストボディ指定
        data = {
            "sentence": sentence
        }
        # リクエストボディ指定をJSONにエンコード
        data = json.dumps(data).encode()
        # リクエスト生成
        req = urllib.request.Request(url, data, headers)
        # リクエストを送信し、レスポンスを受信
        try:
            res = urllib.request.urlopen(req)
        # リクエストでエラーが発生した場合の処理
        except urllib.request.HTTPError as e:
            # ステータスコードが401 Unauthorizedならアクセストークンを取得し直して再リクエスト
            if e.code == 401:
                print ("get access token")
                self.access_token = getAccessToken(self.client_id, self.client_secret)
                headers["Authorization"] = "Bearer " + self.access_token
                req = urllib.request.Request(url, data, headers)
                res = urllib.request.urlopen(req)
            # 401以外のエラーなら原因を表示
            else:
                print ("<Error> " + e.reason)

        # レスポンスボディ取得
        res_body = res.read()
        # レスポンスボディをJSONからデコード
        res_body = json.loads(res_body)
        # レスポンスボディから解析結果を取得
        return res_body



if __name__ == '__main__':
    # COTOHA APIインスタンス生成
    cotoha_api = CotohaApi(CLIENT_ID, CLIENT_SECRET, DEVELOPER_API_BASE_URL, ACCESS_TOKEN_PUBLISH_URL)
    # 構文解析API実行
    result = cotoha_api.ne(sentence)

    # 出力結果を見やすく整形
    #result_formated = json.dumps(result, indent=4, separators=(',', ': '))
    #print (codecs.decode(result_formated, 'unicode-escape'))

    for i in result['result']:
        if i['class'] == "ART" or i['class'] == "ORG":
            sentence = sentence.replace(i['form'], artifact)
        if i['class'] == "LOC":
            sentence = sentence.replace(i['form'], location)
        if i['class'] == "PSN":
            sentence = sentence.replace(i['form'], person)
    print(sentence)
```

</details></div>

## 結果

```
input : 明日、x社の山田さんと大手町でミーティングなのでその前にqiitaの記事を書いておこう
↓
output: 明日、アレのあの人アレとあそこでミーティングなのでその前にアレの記事を書いておこう
```

```
input : 人民の人民による人民のための政治   -  エイブラハム・リンカーン  -

output: 人民の人民による人民のための政治   -  あの人  -
```

```
input : 日本語の高精度な自然言語解析を実現するapiサービス。nttグループの40年以上の研究成果を活かした自然言語解析技術をcotoha apiでお手軽にご利用いただけます。
↓
output: アレの高精度な自然言語解析を実現するあの人サービス。アレの40年以上の研究成果を活かした自然言語解析技術をアレ あの人でお手
軽にご利用いただけます。
```

```
input : 田中が出張のため、定例会議の場所を以下のように変更いたします。場所　第三会議室
↓
output: あの人が出張のため、アレの場所を以下のように変更いたします。場所　あそこ
```


だいたいうまくいきました


<br>
<br>
<br>

# 以下おまけ
可能な限りすべてをアレして遊んだ。

今度はCOTOHA構文解析APIを使った。
今回は下表のように単語を置き換えました。

| 単語の予測クラス（解析結果） | 置き換える単語 | 
|:-----------|------------:|
| 名詞（feature:地） | あそこ |
| 名詞（feature:姓） | あの人 |
| 名詞 （その他）| アレ |
| 動詞語幹 | アレ |
| 動詞活用語尾 | し |

## 実装２

<details><summary>クリックで展開する</summary><div>

```python:replace-all-noun.py
import os
import urllib.request
import json
import configparser
import codecs

# 解析対象文（input）
sentence = "弊社が来年リリースする新製品のmasahiko-012の発表会を東京qiita会館で行いたい"

DEVELOPER_API_BASE_URL = "https://api.ce-cotoha.com/api/dev/nlp/"
ACCESS_TOKEN_PUBLISH_URL = "https://api.ce-cotoha.com/v1/oauth/accesstokens"
CLIENT_ID = "XXXXXX"
CLIENT_SECRET = "XXXXXX"

artifact = "アレ"
person = "あの人"
location = "あそこ"

# COTOHA API操作用クラス
class CotohaApi:
    # 初期化
    def __init__(self, client_id, client_secret, developer_api_base_url, access_token_publish_url):
        self.client_id = client_id
        self.client_secret = client_secret
        self.developer_api_base_url = developer_api_base_url
        self.access_token_publish_url = access_token_publish_url
        self.getAccessToken()

    # アクセストークン取得
    def getAccessToken(self):
        # アクセストークン取得URL指定
        url = self.access_token_publish_url

        # ヘッダ指定
        headers={
            "Content-Type": "application/json;charset=UTF-8"
        }

        # リクエストボディ指定
        data = {
            "grantType": "client_credentials",
            "clientId": self.client_id,
            "clientSecret": self.client_secret
        }
        # リクエストボディ指定をJSONにエンコード
        data = json.dumps(data).encode()

        # リクエスト生成
        req = urllib.request.Request(url, data, headers)

        # リクエストを送信し、レスポンスを受信
        res = urllib.request.urlopen(req)

        # レスポンスボディ取得
        res_body = res.read()

        # レスポンスボディをJSONからデコード
        res_body = json.loads(res_body)

        # レスポンスボディからアクセストークンを取得
        self.access_token = res_body["access_token"]

    # 構文解析API
    def parse(self, sentence):
        # 構文解析API URL指定
        url = self.developer_api_base_url + "v1/parse"
        # ヘッダ指定
        headers={
            "Authorization": "Bearer " + self.access_token,
            "Content-Type": "application/json;charset=UTF-8",
        }
        # リクエストボディ指定
        data = {
            "sentence": sentence
        }
        # リクエストボディ指定をJSONにエンコード
        data = json.dumps(data).encode()
        # リクエスト生成
        req = urllib.request.Request(url, data, headers)
        # リクエストを送信し、レスポンスを受信
        try:
            res = urllib.request.urlopen(req)
        # リクエストでエラーが発生した場合の処理
        except urllib.request.HTTPError as e:
            # ステータスコードが401 Unauthorizedならアクセストークンを取得し直して再リクエスト
            if e.code == 401:
                print ("get access token")
                self.access_token = getAccessToken(self.client_id, self.client_secret)
                headers["Authorization"] = "Bearer " + self.access_token
                req = urllib.request.Request(url, data, headers)
                res = urllib.request.urlopen(req)
            # 401以外のエラーなら原因を表示
            else:
                print ("<Error> " + e.reason)
        
        # レスポンスボディ取得
        res_body = res.read()
        # レスポンスボディをJSONからデコード
        res_body = json.loads(res_body)
        # レスポンスボディから解析結果を取得
        return res_body

if __name__ == '__main__':
    # COTOHA APIインスタンス生成
    cotoha_api = CotohaApi(CLIENT_ID, CLIENT_SECRET, DEVELOPER_API_BASE_URL, ACCESS_TOKEN_PUBLISH_URL)
    # 構文解析API実行
    result = cotoha_api.parse(sentence)

    # 出力結果を見やすく整形
    #result_formated = json.dumps(result, indent=4, separators=(',', ': '))
    #print (codecs.decode(result_formated, 'unicode-escape'))

    forms = ""
    for chunks in result['result']:
        for token in chunks['tokens']:
            if token['pos'] == "名詞":
                if "地" in token['features'] :
                    forms = forms + location
                elif "姓" in token['features']:
                    forms = forms + person
                else:
                    forms = forms + artifact
            elif token['pos'] == "動詞語幹":
                forms = forms + artifact
            elif token['pos'] == "動詞活用語尾":
                forms = forms + "し"
            else:
                forms = forms + token['form']
    print(forms)

```
</details>
</div>

## 結果２

```
input : 弊社が来年リリースする新製品のmasahiko-012の発表会を東京qiita会館で行いたい
↓
output: アレがアレアレする新アレのアレのアレ会をあそこでアレしたい
```
```
input : 人民の人民による人民のための政治   -  エイブラハム・リンカーン  -

output: アレのアレによるアレのためのアレ   -  あそこ  -
```
```
input : 明日、x社の山田さんと大手町でミーティングなのでその前にqiitaの記事を書いておこう
↓
output: アレ、アレのあの人さんとあそこでアレなのでそのアレにアレのアレをアレしてアレしう
```

```
input : 日本語の高精度な自然言語解析を実現するapiサービス。nttグループの40年以上の研究成果を活かした自然言語解析技術をcotoha apiでお手軽にご利用いただけます。
↓
output: アレのアレなアレアレをアレするアレアレ。アレのアレ以上のアレをアレしたアレアレアレをアレ アレでおアレにごアレアレます。
```

```
input : 田中が出張のため、定例会議の場所を以下のように変更いたします。場所　第三会議室
↓
output: あの人がアレのため、アレ会議のアレをアレのようにアレいたします。アレ アレアレ室
```


動詞は「アレする」に変えているけど、活用のされ方によってはうまくいかないっぽいので、もう少しルールを追加していきたい。


<br>
<br>

## 備考
固有名詞をマスキングするほうはGoogle翻訳みたいなオンライン翻訳機使うとき、ダミーの単語に置き換えるのに便利だと思いました。

「I Love Translation」という翻訳サイトでページ内のあるチェックボックスにチェックを付けた状態で翻訳を行うと、その内容が誰でも閲覧できる状態になる、ということがあったそうです。
30件以上の漏えいが確認され、中には中央官庁や銀行の業務メールや、弁護士と依頼者とのメール、採用情報などもあったそうです・・・。

※参考 
[(BOXIL)](https://boxil.jp/mag/a321/)