システムデザイン

サマーインターンシップ2018実施報告

*本記事は旧TechblogからCOLORSに統合した記事です。


2018年8月24日(金)に1Dayインターンシップとして、『 ディープラーニングを使用したAIチャットボット体験! 』を開催しました。

5名の学生さんに参加してもらいました。ありがとうございました!

今回は、実施したインターンシップの内容を報告します。

目次

インターンシップ概要

「AI(人工知能)、ディープラーニング(深層学習)」 について、AIチャットボットを通して、仕組みの説明とハンズオンを体験してもらいました。
==体験の流れ==

  1. AI、ディープラーニングとは?
  2. 実際の処理に触れてみよう(ハンズオン)
  3. LINEのチャットボットで遊んでみよう

1. AI、ディープラーニングとは?

1.1. 事例紹介

最初にAI、ディープラーニングを適用した、サービスや商品事例を紹介しました。
AI、ディープラーニングとは?

   
   

1.2. ディープラーニング紹介

続いて、ディープラーニングの仕組みや、AIに言葉を覚えさせる(自然言語処理)までの工程を説明しました。
ディープラーニング紹介

   

 

2. 実際の処理に触れてみよう(ハンズオン)

環境は、Googleが提供する機械学習の教育、研究を目的とした研究用ツールGoogle Colaboratoryを使用しました。
実際の処理に触れてみよう(ハンズオン)

ハンズオンは以下の内容で実施しました。
==ハンズオン実施内容==

  1. データ収集
  2. テキストマイニング
  3. ディープラーニング

2.1. データ収集

ディープラーニングで使用する「会話」データを、Twitterから収集しました。

学生さん達に、検索ワードを決めてもらい、そのワードに関連する「つぶやき」を、Twitter APIラッパーのTweepyライブラリを使用して、「ツイート」と「リプライ」を対にした「会話」データとして収集しました。
tweets

収集は、「リプライ」から、「ツイート」を逆引きし以下の処理フローで実施しました。
twitter_collection

また、Twitterから取得したメッセージは140文字制限判定を行い、最大280文字の取得を実施しました。
コードを以下に記します。

[code lang="python"]
import os, sys, re, json
import tweepy
import time
import socket
import http.client
import argparse
from datetime import datetime
from tweepy import OAuthHandler, Stream
from tweepy.streaming import StreamListener
from urllib3.exceptions import ProtocolError

# Number of seconds to wait after an exception before restarting the stream.
tcpip_delay = 0.25
MAX_TCPIP_TIMEOUT = 16

# tweepy
consumer_key = os.getenv('CONSUMER_KEY', None)
consumer_secret = os.getenv('CONSUMER_SECRET', None)
access_token = os.getenv('ACCESS_TOKEN', None)
access_token_secret = os.getenv('ACCESS_TOKEN_SECRET', None)

if consumer_key is None:
print('Specify CONSUMER_KEY as environment variable.')

sys.exit()

if consumer_secret is None:
print('Specify CONSUMER_SECRET as environment variable.')

sys.exit()

if access_token is None:
print('Specify ACCESS_TOKEN as environment variable.')

sys.exit()

if access_token_secret is None:
print('Specify ACCESS_TOKEN_SECRET as environment variable.')

sys.exit()

class QueueListener(StreamListener):
def __init__(self, total):
"""Creates a new stream listener with an internal queue for tweets."""
super(QueueListener, self).__init__()
self.queue = [] #Queue.Queue()
self.lang = 'ja'
self.auth = OAuthHandler(consumer_key, consumer_secret)
self.auth.set_access_token(access_token, access_token_secret)
self.api = tweepy.API(self.auth)
self.start = 0
self.count = 0
self.total = total

# corpus file
self.dumpfile = "tweets.txt"

# データ受信(ストリーム)
def on_data(self, data):
"""Routes the raw stream data to the appropriate method."""
raw = json.loads(data)

# リプライメッセージ(リプライ元ID【ツイートID】設定有り)意外の場合次のメッセージを取得する
if 'in_reply_to_status_id' in raw:
if self.on_status(raw) is False:
return False

# メッセージ取得数限界の場合
elif 'limit' in raw:
if self.on_limit(raw['limit']['track']) is False:
return False

return True

# Twitterメッセージ受信(ストリーム)
def on_status(self, raw):
# リプライ元ID取得判定
if isinstance(raw.get('in_reply_to_status_id'), int):
# Twitter拡張メッセージ(140文字超過)取得判定
if 'extended_tweet' in raw:
# 拡張メッセージ取得
replyMessage = raw['extended_tweet']['full_text']

else:
# 通常メッセージ取得
replyMessage = raw.get('text')

self.dump(raw.get('in_reply_to_status_id'), replyMessage)

return True

def on_error(self, status):
#        print('ON ERROR:', status)

return True

def on_limit(self, track):
#       print('ON LIMIT:', track)

return True

def on_exception(self, exception):
print('ON EXCEPTION:', exception)

return True

# メッセージ出力
def dump(self, id, replyMessage):
with open(self.dumpfile, 'a') as fdump:
try:
# リプライ元IDメッセージ(ツイートメッセージ)取得
tweet = self.api.get_status(id, tweet_mode='extended')

# Twitter拡張メッセージ(140文字超過)が取得出来た場合
try:
# 拡張メッセージ取得
tweetMessage = tweet.full_text

# Twitter拡張メッセージ(140文字超過)が取得出来ない場合
except AttributeError:
# 通常メッセージ取得
tweetMessage = tweet.text

print("tweet[%d]:(%s)%s" % (self.count, id, tweetMessage))
print("reply[%d]:(%s)%s" % (self.count, id, replyMessage))

fdump.write("%s\n%s\n" % (self.preprocess(tweetMessage), self.preprocess(replyMessage)))

# 時間調整
time.sleep(1)

except tweepy.error.RateLimitError as e:
elapsed_time = datetime.fromtimestamp(time.time() - self.start)

print(e)
print("tweets:" + str(self.count) + " elapsed_time: ", elapsed_time.strftime("%H:%M:%S.%f"))

sys.exit()

except Exception:
return

self.count += 1

# 指定回数取得判定
if self.total > 0 and self.count >= self.total:
elapsed_time = datetime.fromtimestamp(time.time() - self.start)

print("tweets:" + str(self.count) + " elapsed_time: ", elapsed_time.strftime("%H:%M:%S.%f"))

sys.exit()

def preprocess(self, line):
line = re.sub("\s+", ' ', line).strip().lower()

return line

# 指定キーワード収集
def search(self, keyword):
maxid = 0

self.start = time.time()

try:

# 指定キーワードに関連する最新ツイートから、100件Twitter拡張メッセージ(140文字超過)を取得する。
search_result = self.api.search(q=keyword, count=100, result_type='recent', tweet_mode='extended')

while True:
for result in search_result:
if maxid > result.id or maxid == 0:
maxid = result.id

# リプライ元ID取得判定
if isinstance(result.in_reply_to_status_id, int):
# Twitter拡張メッセージ(140文字超過)が取得出来た場合
try:
# 拡張メッセージ取得
replyMessage = result.full_text

# Twitter拡張メッセージ(140文字超過)が取得出来ない場合
except AttributeError:
# 通常メッセージ取得
replyMessage = result.text

# ツイート、リプライメッセージ出力
self.dump(result.in_reply_to_status_id, replyMessage)

if len(search_result) == 0:
break

# 検索で取得した最も古いメッセージIDから指定キーワードに関連する過去ツイート100件Twitter拡張メッセージ(140文字超過)を取得する。
search_result = self.api.search(q=keyword, count=100, result_type='recent', tweet_mode='extended', max_id=maxid-1)

except tweepy.error.RateLimitError as e:
elapsed_time = datetime.fromtimestamp(time.time() - self.start)

print(e)
print("tweets:" + str(self.count) + " elapsed_time: ", elapsed_time.strftime("%H:%M:%S.%f"))

sys.exit()

except Exception as e:
print(e)

# ストリーム収集
def filter(self, keyword):
global stream
stream = Stream(self.auth, self)

if not keyword:
track = ['I', 'you', 'http', 'www', 'co', '@', '#', '。', ',', '!', '.', '!', ',', ':', ':', '』', ')', '...']

else:
track = [keyword]

self.start = time.time()

try:
while True:
try:
# [stream filter]
stream.filter(languages=["ja"], track=track, stall_warnings=True)

except KeyboardInterrupt:
print('KEYBOARD INTERRUPT')

return

except (socket.error, http.client.HTTPException):
global tcpip_delay

print('TCP/IP Error: Restarting after %.2f seconds.' % tcpip_delay)

time.sleep(min(tcpip_delay, MAX_TCPIP_TIMEOUT))
tcpip_delay += 0.25

except ProtocolError as e:
print(e)

finally:
stream.disconnect()
print('Exit successful, corpus dumped in %s' % (self.dumpfile))

def main():
parser = argparse.ArgumentParser()
parser.add_argument("-s", "--search", action='store_true')
parser.add_argument("-k", "--keyword", nargs="?", type=str, default="")
parser.add_argument("-c", "--count", nargs="?", type=int, default=-1)

args = parser.parse_args()

# open stream
global listener
listener = QueueListener(args.count)

if args.search:
if not args.keyword:
parser.print_help()

else:
listener.search(args.keyword)

else:
listener.filter(args.keyword)

if __name__ == '__main__':
sys.exit(main())
[/code]

コードは、以下を参照しました。

Marsan-Ma/twitter_scraper

2.2. テキストマイニング

学生さん達が「Twitter」から収集した「ツイート」、「リプライ」の会話から、ディープラーニングで使用する「対訳コーパス1を作成する為に、テキストマイニングを行いました。

1 対訳コーパスは、「自然言語処理」における機械翻訳の学習データとして利用する為等に構築された、「文と文が対訳の形で纏めた」ものを指します。

2.2.1. ワードクラウド

wordcloudライブラリを使用し、収集した「会話文」の中から、出現頻度が高い「単語」を調べ、文字と画像埋め込みの可視化を行いました。

   
ワードクラウド ワードクラウド2

2.2.2. 形態素解析

形態素解析ライブラリMeCab、辞書mecab-ipadic-NEologdを使用して、「対訳コーパス」を作成しました。

また形態素解析を行う際、データの「正規化」、「スクレイピング」、「ストップワード除去処理1」を実施しました。

コードを以下に記します。

[code lang="python"]
import MeCab
import unicodedata
import emoji
import re

# ストップワード取得
with open("Japanese.txt", "r", encoding="utf-8") as f :
global stopwords
stopwords  = [stopword.strip() for stopword in f.readlines()]

# クリーニング処理
def creaning(sentence, stopwordflg = False):
# 絵文字除去
sentence = "".join(c for c in sentence if c not in emoji.UNICODE_EMOJI)
# 文字の正規化
sentence = unicodedata.normalize("NFKC", sentence)
# 改行、タブ、スペース除去
sentence = sentence.replace("\n", "").replace("\r", "").replace("\t","").replace(" ", "")
# ユーザ名除去
sentence = re.sub(r"@([A-Za-z0-9_]+)", "", sentence)
# URL除去
sentence = re.sub(r"https?:\/\/.*", "", sentence)
# ハッシュタグ除去
sentence = re.sub(r"[#]([ー゛゜々ヾヽぁ-ヶ一-龠a-zA-Z0-9_]*[ー゛゜々ヾヽぁ-ヶ一-龠a-zA-Z]+[ー゛゜々ヾヽぁ-ヶ一-龠a-zA-Z0-9_]*)", "", sentence)

# 入力データの場合
if stopwordflg:
# 句読点除去
sentence = re.sub(r"[。、]+", "", sentence)
# ストップワード除去
return remove_stopwords(MeCab.Tagger(r"-Owakati -d ../install/mecab-ipadic-neologd").parse(sentence).strip().split())

else:
return MeCab.Tagger(r"-Owakati -d ../install/mecab-ipadic-neologd").parse(sentence).strip()

# ストップワード除去
def remove_stopwords(words):
sentence = ""

for word in words:
if not word in stopwords:
sentence += word + " "

return sentence.strip()
[/code]

1 SlothLibで定義されたデータをストップワードとしています。

2.3. ディープラーニング

TensorFlowライブラリを使用して、GRUモデルでディープラーニングを実施しました。
ディープラーニング

2.3.1. トレーニング

トレーニングを実施し、「対訳コーパス」から「特徴量」を算出しました。

   
トレーニング トレーニング2

2.3.2. トレーニング状況確認

トレーニング状況を、対話形式(チャットボット)で確認してもらいました。

※データ数が少ないので、あまり「会話」が成立しません。
トレーニング状況

2.3.3. トレーニング状況確認(トレーニング済み)

次に、データ数、トレーニング時間による、「会話」精度の違いを確認してもらいました。

モデルは以下の設定で事前にトレーニングしました。

対訳コーパス数: 2,000,000

バッチサイズ: 64

ユニットサイズ: 1024

GRUモデル中間層数: 3

語彙数: 120,000

ステップ数: 300,000

トレーニング状況

 

3. LINEのチャットボットで遊んでみよう

ディープラーニングを使用したAIチャットボット、「えむしばくん1をLINEのMessaging APIを使用し、チャットボット公開しました。

学生さん達にアクセスして、「会話」を楽しんでもらいました。

   
えむしばくん LINE Bot
えむしばくんBOT

1 弊社のマスコットキャラクターです。LINEスタンプ販売しています。

最後に

今回のインターンシップは、ES事業部初の試みとなり、準備から開催までの期間が短く、課題も多く見つかりました。

冬のインターンシップに向けて、来ていただく学生さん達に楽しんでもらい、弊社に興味を持っていただけるように、内容をブラッシュアップし臨みたいと思います!