Posted at

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


概要

やりたいことの例

input : 弊社が来年リリースする新製品のmasahiko-012の発表会を東京qiita会館で行いたい


output: 弊社が来年リリースする新製品のアレの発表会をあそこで行いたい

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

固有名詞→「アレ」

人物名→「あの人」

場所→「あそこ」

にする。


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


実行環境


  • Windows10

  • Pyhton 3.6.3


固有名詞の抽出

「アレ」などに置き換える単語を判別するために、COTOHA API の固有表現抽出APIを活用しました。

固有表現を抽出できる上、「人名」「地名」などのクラス名も判別してくれます。おあつらえ向きです!

※参考

(自然言語処理を簡単に扱えると噂のCOTOHA APIをPythonて゛使ってみた)

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

単語の予測クラス(解析結果)
置き換える単語

ART
アレ

ORG
アレ

PSN
あの人

LOC
あそこ


実装

クリックで展開する


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)




結果

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


output: 明日、アレのあの人アレとあそこでミーティングなのでその前にアレの記事を書いておこう

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

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

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


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

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


output: あの人が出張のため、アレの場所を以下のように変更いたします。場所 あそこ

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









以下おまけ

可能な限りすべてをアレして遊んだ。

今度はCOTOHA構文解析APIを使った。

今回は下表のように単語を置き換えました。

単語の予測クラス(解析結果)
置き換える単語

名詞(feature:地)
あそこ

名詞(feature:姓)
あの人

名詞 (その他)
アレ

動詞語幹
アレ

動詞活用語尾


実装2

クリックで展開する


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)






結果2

input : 弊社が来年リリースする新製品のmasahiko-012の発表会を東京qiita会館で行いたい


output: アレがアレアレする新アレのアレのアレ会をあそこでアレしたい

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

output: アレのアレによるアレのためのアレ - あそこ -

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


output: アレ、アレのあの人さんとあそこでアレなのでそのアレにアレのアレをアレしてアレしう

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


output: アレのアレなアレアレをアレするアレアレ。アレのアレ以上のアレをアレしたアレアレアレをアレ アレでおアレにごアレアレます。

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


output: あの人がアレのため、アレ会議のアレをアレのようにアレいたします。アレ アレアレ室

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






備考

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

「I Love Translation」という翻訳サイトでページ内のあるチェックボックスにチェックを付けた状態で翻訳を行うと、その内容が誰でも閲覧できる状態になる、ということがあったそうです。

30件以上の漏えいが確認され、中には中央官庁や銀行の業務メールや、弁護士と依頼者とのメール、採用情報などもあったそうです・・・。

※参考

(BOXIL)