はじめに
筆者の本職は薬剤師ですがご縁があってCOTOHAの中の人をやっております。薬局薬剤師歴5年目(やっと中堅に差し掛かったくらいかな?)pythonを学び始めて約3カ月の初心者です。
薬局薬剤師という仕事柄、患者さんからの相談を受けることがありますが皆様もプライベート、業務に関係なく相談を受けたときこんなことを思ったことはありませんか?
それは・・・
「真面目にかえすか、それともスルーするか!」(●´・ω・)a ェット…
薬局ではせっかく相談してくれたことを蔑ろにはできません、が、あまり長く話し込んでしまいますと業務が滞って他の患者さんに迷惑をかけてしまいます。
(待ち時間に対してのお叱りは本当に多いです。大変申し訳なく思っておりますm(__)m)
それでも薬剤師なら時間がない中でもこんなことを思うはず。
・なるべく多くの相談にのってあげたい!!
・落ちた気持ちを少しでも前向きにさせてあげたい!!
・病気がよくなった。気持ちが楽になったと言ってほしい!!
そんな薬剤師のため患者さんの心のケアを助けるツールとしてCOTOHAの音声認識※と感情分析を組み合わせてみました。
※音声認識APIはFor Enterprise版の提供のみとなっています,3ヶ月のお試し期間があるためそちらを使ってお試しいただければと思います!!詳細は下記ポータルを参照ください
COTOHA API ポータル
実行環境
Windows10
Python 3.7.5
Visual Studio Code
COTOHA API
実装
APIアカウント認証情報
Source Code | クリックしてください
{
"client_id":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"client_secret":"yyyyyyyyyyyyy",
"domain_id":"zzzzzzzz"
}
上記に認証情報を記載したjsonファイルを任意の名前で作成してください。今回は[your.json]と名付けます。
音声認識クリーン_レスポンス
Source Code | クリックしてください
def clean_response(text): # 全角を半角に変換、スペースを除去、句点を付与
OFFSET = ord('0')-ord('0') # 全角コードと半角コードの差分を取得
dic = {chr(ord('0')+i): chr(ord('0')+i-OFFSET) for i in range(10)} # 数字
dic.update({chr(ord('A')+i): chr(ord('A')+i-OFFSET) for i in range(26)}) # 英字大文字
dic.update({chr(ord('a')+i): chr(ord('a')+i-OFFSET) for i in range(26)}) # 英字小文字
text = text.translate(str.maketrans(dic))
text = text.replace(" ", "")
text += "。"
return text
返ってきた音声認識の文を見やすくキレイに整えてくれるものです。[clean_response.py]として保存してください
メインコード
Source Code | クリックしてください
import json
import sys
import time
import pyaudio
import requests
import termcolor
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
from pylab import *
from clean_response import clean_response
args = sys.argv
json_name = args[1]
oauth_url = 'https://api.ce-cotoha.com/v1/oauth/accesstokens'
model_id = 'ja-gen_tf-16'
hostname = 'https://api.ce-cotoha.com/api/'
url = hostname + 'asr/v1/speech_recognition/' + model_id
with open(json_name) as f:
credential = json.load(f)
client_id = credential['client_id']
client_secret = credential['client_secret']
domain_id = credential['domain_id']
#円グラフの作成
def pie_chart(x,y):
plt.close()
matplotlib.use("wxagg")
plt.figure(figsize=(5,3))
colors = ["blue", "red"]
label = ["Positive", "Negative"]
plt.title('Total')
plt.pie(np.array([x,y]),labels=label, counterclock=False, startangle=90,autopct="%1.1f%%")
thismanager = get_current_fig_manager()
thismanager.window.SetPosition((850, 300))#画面右下にグラフを表示
plt.pause(1)
class StreamingRequester:
def __init__(self):
self.channels = 1
self.format = pyaudio.paInt16
self.rate = 44100
self.Interval = 0.24
self.nframes = int(self.rate * self.Interval)
self.url = url
self.client_id = client_id
self.client_secret = client_secret
self.param_json = {"param": {
"baseParam.samplingRate": self.rate,
"recognizeParameter.domainId": domain_id,
"recognizeParameter.enableContinuous": 'true'
}}
self.x = 0
self.y = 0
#アクセストークンの取得
def get_token(self):
headers = {"Content-Type": "application/json;charset=UTF-8"}
obj = {'grantType': 'client_credentials', 'clientId': self.client_id,
'clientSecret': self.client_secret}
data_json = json.dumps(obj).encode("utf-8")
response = requests.post(url=oauth_url, data=data_json,
headers=headers)
self.access_token = response.json()['access_token']
# エラー発生時の処理
def check_response(self):
if self.response.status_code not in [200, 204]:
print("STATUS_CODE:", self.response.status_code)
print(self.response.text)
exit()
# 感情分析
def sentiment(self):
base_url = hostname + "nlp/"
headers = {
"Content-Type": "application/json",
"charset": "UTF-8",
"Authorization": "Bearer "+ self.access_token
}
data = {
"sentence": self.sentence,
"type": "default"
}
r = requests.post(base_url + "v1/sentiment",
headers=headers,
data=json.dumps(data))
return r.json()
# レスポンスをパースし認識結果を標準出力
def print_result(self):
# statusが200のときのみresponseにjsonが含まれる
if self.response.status_code == 200:
for res in self.response.json():
if res['msg']['msgname'] == 'recognized':
# type=2ではsentenceの中身が空の配列の場合がある
if res['result']['sentence'] != []:
self.sentence = clean_response(res['result']['sentence'][0]['surface'])
#感情分析を開始
e_phrase = self.sentiment()['result']['emotional_phrase']
for e_select in e_phrase[0:]:
if e_select['emotion'] == "P"
or e_select['emotion'] == "好ましい"
or e_select['emotion'] == "喜ぶ"
or e_select['emotion'] == "安心":
e_color = termcolor.colored(e_select['form'],'blue')
self.sentence = self.sentence.replace(e_select['form'],e_color)
self.x =self.x + 1
elif e_select['emotion'] == "N"
or e_select['emotion'] == "怒る"
or e_select['emotion'] == "不安"
or e_select['emotion'] == "恥ずかしい"
or e_select['emotion'] == "嫌":
e_color = termcolor.colored(e_select['form'],'red')
self.sentence = self.sentence.replace(e_select['form'],e_color)
self.y =self.y + 1
print(self.sentence)
""" print(self.sentiment()) """#ONにすると感情分析の解析結果を参照できる
pie_chart(self.x,self.y)
# 開始要求
def start(self):
obj = self.param_json
obj['msg'] = {'msgname': 'start'}
data_json = json.dumps(obj).encode("utf-8")
headers = {"Content-Type": "application/json;charset=UTF-8",
"Authorization": "Bearer " + self.access_token}
self.requests = requests.Session()
self.response = self.requests.post(url=self.url, data=data_json,
headers=headers)
self.check_response()
# uniqueIdはデータ転送、停止要求に必要
self.unique_id = self.response.json()[0]['msg']['uniqueId']
# 停止要求
def stop(self):
headers = {"Unique-ID": self.unique_id,
"Content-Type": "application/json;charset=UTF-8 ",
"Authorization": "Bearer " + self.access_token}
obj = {"msg": {"msgname": "stop"}}
data_json = json.dumps(obj).encode("utf-8")
self.response = self.requests.post(url=self.url, data=data_json,
headers=headers)
self.check_response()
self.print_result()
def _post(self):
self.headers = {"Content-Type": "application/octet-stream",
"Unique-ID": self.unique_id,
"Authorization": "Bearer " + self.access_token}
self.response = self.requests.post(url=self.url, data=self.data,
headers=self.headers)
self.check_response()
self.print_result()
def listen(self):
p = pyaudio.PyAudio()
stream = p.open(format=self.format,
channels=self.channels,
rate=self.rate,
input=True,
frames_per_buffer=self.nframes
)
print("Now Recording")
try:
while True:
self.data = stream.read(self.nframes, exception_on_overflow=False)
self._post()
# "Ctrl+C" でプログラム終了
except KeyboardInterrupt:
stream.close()
p.terminate()
sys.exit("Exit Voice Recognition")
if __name__ == '__main__':
requester = StreamingRequester()
requester.get_token()
requester.start()
requester.listen()
requester.stop()
音声認識と感情分析を行うコードです。今回は[Positive_Negative.py]で保存します
上記3種類のSource Codeを同じフォルダにいれて/srcディレクトリに移動しpython3 Positive_Negative.py your.json を実行してください。
検証
患者さんとのやり取り ①
私「今回は糖尿病のお薬が強くなっていますね、先生からお話は伺っていますか?」
患「はい、年末年始に忘年会や新年会に出席することが多くて暴飲暴食してしまいました。
血液検査の結果も悪くなっていて先生から怒られてしまいました。
気を付けてはいるんだけど営業職だから付き合いは断れなくて・・・」
私「なるほど、ですが今回は原因もはっきりしていますのでしっかりと改善していきましょう。
糖尿のお薬は強くなっているので低血糖のリスクが上がっています、しばらくは注意してくださいね。」
患「はい。あと、良くないと思っているのですが仕事で遅くなったときは疲れて食べてすぐに
寝るから薬を飲み忘れてしまいます。それで夕食後の薬だけ余って来てしまいました。」
私「もしかしたら飲み忘れも悪化の原因になっているかもしれません。
生活習慣も含めてDrとも用法について相談していった方が良いでしょう。
もしよろしければ私の方からお薬の日数の調節と用法についてDrと相談してみましょうか?」
患「やっぱりそうかな。飲み忘れのことは次回に自分から話してみます。
お薬の余りも10日分くらいだから予備として持っておくことにします。
ところでいつも食事に気をつけろって言われるけど何に気を付ければいいの?」
想定① 解析結果 | クリックしてください
患者さんとのやり取り ②
私「今回もいつもと同じお薬です、お変わりありませんね。」
患「最近近くにパン屋さんができたでしょ?そこで買った食パンがとってもおいしかったの。
それからお向かいのスーパーはお野菜が安いのよ。」
私「そうですか。」
患「先週は3歳になった孫が遊びにきてね、この前産まれたばかりだと思っていたのに、
すっかり大きくなっておしゃべりもできるようになったんですよ。」
私「なるほど」
患「今週の月9ドラマはみたかしら?続きがとても気になる終わり方でしたね。
薬剤師さんは今後の展開どうなると思います?」
想定② 解析結果 | クリックしてください
まとめ
いかがだったでしょうか?COTOHA APIで簡易的な感情分析ができました!
実際のお薬の受け渡しは対面で接していますので患者さんの声のトーンや表情、仕草から感情を推測し会話されていると思います。今回紹介したツールは投薬中の出番はないかもしれません。
ただこの解析結果を薬歴に残しておけば、次回別の薬剤師が対応したときも前回の記録をもとにより詳しく患者さんの状態を推測できます。
店舗に置いてやってみたいところですが、まわりが十分に静かな環境が必要であったり指向性の高いマイクを患者さんに向けなければなりませんのでまだ少しハードルが高いかなと思います。
もし今後、遠隔医療が進んでタブレット画面に向かいながら服薬指導なんてことも多くなれば、映像では受け取りにくい患者さんのリアルタイムな情報を補完してくれるツールになるかもしれません。
参考記事
COTOHA音声認識を中の人が仕組みからサンプルまで軽く解説
参考図書
- 株式会社ビープラウド・監修/リブロ・ワークス・著 「Python ふりがなプログラミング」、インプレス出版
- 斎藤 康毅・著 「ゼロから作る Deep Learning Pythonで学ぶディープラーニングの理論と実装」 発行:オライリー・ジャパン 発売:オーム社