LoginSignup
1
0

More than 3 years have passed since last update.

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

Posted at

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

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

はじめに

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

問題

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

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

原因

原因となる部分はこちら

const body = JSON.parse(event.body)
const events = body.events[0]
const message = events.message.text

const param = {
  TableName: 'ReplyMapping',
  Key: {
    type: message  ここ!!
  }
}
const result = await dynamodb.get(param).promise()
let msg, img
if (result.Item) {
  const getRandomList = (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'
const path = require('path')
const DIR = './node_modules/kuromoji/dict'
const kuromoji = require('kuromoji')

module.exports = class KuromojiWrapper {
  constructor() {
  }

  async init() {
    this.tokenizer = await new Promise((resolve, reject) => {
      kuromoji
        .builder({ dicPath: DIR })
        .build((err, tokenizer) => {
          if (err) reject(err)
          resolve(tokenizer)
        })
    })
  }

  exec(text) {
    return this.tokenizer.tokenize(text)
  }

  get(text) {
    const res = this.exec(text)
    // 第一優先: 感動詞
    const prop1 = res.find((o) => o.pos === '感動詞')
    if (prop1) return prop1.basic_form
    // 第二優先: 形容詞
    const prop2 = res.find((o) => o.pos === '形容詞')
    if (prop2) return prop2.basic_form
    return text
  }
}

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
const KuromojiWrapper = require('./KuromojiWrapper')

const main = async () => {
  // イニシャライズ
  const analysis = new KuromojiWrapper()
  // 外部init
  await analysis.init()
  const res = analysis.get('超!かわいいね!!')
  console.log(res)
}

main()

// 実行結果
// かわいい

既存の処理に組み込む

ソース全文

index.js
'use strict'

const line = require('@line/bot-sdk')
const client = new line.Client({ channelAccessToken: process.env.ACCESSTOKEN })
const crypto = require('crypto')
const AWS = require('aws-sdk')
const dynamodb = new AWS.DynamoDB.DocumentClient({
  region: 'ap-northeast-1'
})
const KuromojiWrapper = require('./KuromojiWrapper')

exports.handler = async (event) => {
  return new Promise(async (resolve, reject) => {
    const signature = crypto.createHmac('sha256', process.env.CHANNELSECRET).update(event.body).digest('base64')
    const checkHeader = (event.headers || {})['X-Line-Signature']
    const body = JSON.parse(event.body)

    if (signature !== checkHeader) {
      reject('Authentication error')
    }
    const events = body.events[0]
    const message = events.message.text

    // ここから 追加
    const analysis = new KuromojiWrapper()
    await analysis.init()
    const key = analysis.get(message)
    // ここまで 追加

    const param = {
      TableName: 'ReplyMapping',
      Key: {
        type: key // ここもいじった
      }
    }
    const result = await dynamodb.get(param).promise()
    let msg, img
    if (result.Item) {
      const getRandomList = (list) => list[Math.floor(Math.random() * list.length)]
      msg = getRandomList(result.Item.msg)
      img = getRandomList(result.Item.img)
    } else {
      msg = `${message}ってなんでちゅか?`
      img = ''
    }

    const replyText = {
      type: 'text',
      text: msg
    }
    const replyImage = {
      type: 'image',
      originalContentUrl: img,
      previewImageUrl: img
    }

    client.replyMessage(events.replyToken, replyText)
      .then((r) => {
        return client.pushMessage(events.source.userId, replyImage)
      })
      .then((r) => {
        resolve({
          statusCode: 200,
          headers: { 'X-Line-Status': 'OK' },
          body: '{"result":"completed"}'
        })
      }).catch(reject)
  })
}

結果

Aft.gif

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

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

まとめ

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

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

パフォーマンス劣化

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

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0