Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8837

息子の可愛さを普及するために、AWS + LINEでBotを作った話〜形態素解析導入編〜

$
0
0

我が家の息子が可愛すぎる。

可愛すぎるので、LINE Botを作成し布教したが、少し問題が・・・

はじめに

息子の可愛さを普及するために、AWS + LINEでBotを作った話の続編となります。元ソースや環境はこちらに記載してあります。

問題

こちらをご覧ください。
Bef.gif

「かわいい」と送信した際には、会話ができているが、「かわいいね」や「超!!かわいい!!!」など、「かわいい」という文字列にアドオンされた場合、該当するメッセージがないと判断され、「〜ってなんでちゅか?」と分からないフリをするかわいい。(親バカ)

原因

原因となる部分はこちら

constbody=JSON.parse(event.body)constevents=body.events[0]constmessage=events.message.textconstparam={TableName:'ReplyMapping',Key:{type:messageここ!!}}constresult=awaitdynamodb.get(param).promise()letmsg,imgif(result.Item){constgetRandomList=(list)=>list[Math.floor(Math.random()*list.length)]msg=getRandomList(result.Item.msg)img=getRandomList(result.Item.img)}else{msg=`${message}ってなんでちゅか?`img=''}

メッセージとtypeが完全一致した時に、DynamoDBからレコードを取得している。
今回の事象は、「かわいい」というレコードは登録されているが、「かわいいね」や「超!!かわいい!!!」といったレコードが登録されていないため発生している。

対策

じゃあ、メッセージを形態素解析して、どういった種類のメッセージか。解析するようにしたら良いのでは?

実装

kuromoji.jsを使って実現してみよう。
※ kuromoji.jsとは、javaのオープンソース日本語形態素解析エンジンkuromojiのjs移植版である。

ラッパークラスを作ってみた。

KuromojiWrapper.js
'use strict'constpath=require('path')constDIR='./node_modules/kuromoji/dict'constkuromoji=require('kuromoji')module.exports=classKuromojiWrapper{constructor(){}asyncinit(){this.tokenizer=awaitnewPromise((resolve,reject)=>{kuromoji.builder({dicPath:DIR}).build((err,tokenizer)=>{if(err)reject(err)resolve(tokenizer)})})}exec(text){returnthis.tokenizer.tokenize(text)}get(text){constres=this.exec(text)// 第一優先: 感動詞constprop1=res.find((o)=>o.pos==='感動詞')if(prop1)returnprop1.basic_form// 第二優先: 形容詞constprop2=res.find((o)=>o.pos==='形容詞')if(prop2)returnprop2.basic_formreturntext}}

this.tokenizer.tokenize(text)で形態素解析を行う。
例えば、超!かわいいね!という文字列の場合、kuromojiからのレスポンスは下記となる。

[{word_id:70940,word_type:'KNOWN',word_position:1,surface_form:'超',pos:'接頭詞',pos_detail_1:'名詞接続',pos_detail_2:'*',pos_detail_3:'*',conjugated_type:'*',conjugated_form:'*',basic_form:'超',reading:'チョウ',pronunciation:'チョー'},{word_id:91640,word_type:'KNOWN',word_position:2,surface_form:'!',pos:'記号',pos_detail_1:'一般',pos_detail_2:'*',pos_detail_3:'*',conjugated_type:'*',conjugated_form:'*',basic_form:'!',reading:'!',pronunciation:'!'},{word_id:1717990,word_type:'KNOWN',word_position:3,surface_form:'かわいい',pos:'形容詞',pos_detail_1:'自立',pos_detail_2:'*',pos_detail_3:'*',conjugated_type:'形容詞・イ段',conjugated_form:'基本形',basic_form:'かわいい',reading:'カワイイ',pronunciation:'カワイイ'},{word_id:92590,word_type:'KNOWN',word_position:7,surface_form:'ね',pos:'助詞',pos_detail_1:'終助詞',pos_detail_2:'*',pos_detail_3:'*',conjugated_type:'*',conjugated_form:'*',basic_form:'ね',reading:'ネ',pronunciation:'ネ'},{word_id:91640,word_type:'KNOWN',word_position:9,surface_form:'!',pos:'記号',pos_detail_1:'一般',pos_detail_2:'*',pos_detail_3:'*',conjugated_type:'*',conjugated_form:'*',basic_form:'!',reading:'!',pronunciation:'!'}]

この中から、posが感動詞となっているものを第一優先。形容詞となっているものを第二優先として、抜き出す。
※ ここは正直適当。優先順位を持たせることができたらとりあえずOK

呼び出し方としてはこう。
※ node.jsってconstructorでawaitできないのね・・・ちょっと強引に外部メソッドで実現。

sample.js
constKuromojiWrapper=require('./KuromojiWrapper')constmain=async()=>{// イニシャライズconstanalysis=newKuromojiWrapper()// 外部initawaitanalysis.init()constres=analysis.get('超!かわいいね!!')console.log(res)}main()// 実行結果// かわいい

既存の処理に組み込む

ソース全文

index.js
'use strict'constline=require('@line/bot-sdk')constclient=newline.Client({channelAccessToken:process.env.ACCESSTOKEN})constcrypto=require('crypto')constAWS=require('aws-sdk')constdynamodb=newAWS.DynamoDB.DocumentClient({region:'ap-northeast-1'})constKuromojiWrapper=require('./KuromojiWrapper')exports.handler=async(event)=>{returnnewPromise(async(resolve,reject)=>{constsignature=crypto.createHmac('sha256',process.env.CHANNELSECRET).update(event.body).digest('base64')constcheckHeader=(event.headers||{})['X-Line-Signature']constbody=JSON.parse(event.body)if(signature!==checkHeader){reject('Authentication error')}constevents=body.events[0]constmessage=events.message.text// ここから 追加constanalysis=newKuromojiWrapper()awaitanalysis.init()constkey=analysis.get(message)// ここまで 追加constparam={TableName:'ReplyMapping',Key:{type:key// ここもいじった}}constresult=awaitdynamodb.get(param).promise()letmsg,imgif(result.Item){constgetRandomList=(list)=>list[Math.floor(Math.random()*list.length)]msg=getRandomList(result.Item.msg)img=getRandomList(result.Item.img)}else{msg=`${message}ってなんでちゅか?`img=''}constreplyText={type:'text',text:msg}constreplyImage={type:'image',originalContentUrl:img,previewImageUrl:img}client.replyMessage(events.replyToken,replyText).then((r)=>{returnclient.pushMessage(events.source.userId,replyImage)}).then((r)=>{resolve({statusCode:200,headers:{'X-Line-Status':'OK'},body:'{"result":"completed"}'})}).catch(reject)})}

結果

Aft.gif

固定文言じゃなくても、単文レベルならそれっぽくできた!!!

ただ・・レスポンス遅くね?

まとめ

それっぽいロジックは組み込めたけど・・・

それっぽいロジックを組み込むことができたが、考えれば考えるほど様々な処理を入れたくなる。(例えば年齢ロジックとか、天気とか)
これらを詰め合わせると、とんでもないことになりそうな予感。

パフォーマンス劣化

パフォーマンスがかなり劣化。
ローカルで動かした時より圧倒的に劣化しているので、NW周りとか、Lambdaのメモリーとかが関係しているのかな。


Viewing all articles
Browse latest Browse all 8837

Trending Articles