*本記事は旧TechblogからCOLORSに統合した記事です。
2018年8月24日(金)に1Dayインターンシップとして、『 ディープラーニングを使用したAIチャットボット体験! 』を開催しました。
5名の学生さんに参加してもらいました。ありがとうございました!
今回は、実施したインターンシップの内容を報告します。
目次
インターンシップ概要
「AI(人工知能)、ディープラーニング(深層学習)」 について、AIチャットボットを通して、仕組みの説明とハンズオンを体験してもらいました。
==体験の流れ==
- AI、ディープラーニングとは?
- 実際の処理に触れてみよう(ハンズオン)
- LINEのチャットボットで遊んでみよう
1. AI、ディープラーニングとは?
1.1. 事例紹介
最初にAI、ディープラーニングを適用した、サービスや商品事例を紹介しました。
1.2. ディープラーニング紹介
続いて、ディープラーニングの仕組みや、AIに言葉を覚えさせる(自然言語処理)までの工程を説明しました。
2. 実際の処理に触れてみよう(ハンズオン)
環境は、Googleが提供する機械学習の教育、研究を目的とした研究用ツール「Google Colaboratory」を使用しました。
ハンズオンは以下の内容で実施しました。
==ハンズオン実施内容==
- データ収集
- テキストマイニング
- ディープラーニング
2.1. データ収集
ディープラーニングで使用する「会話」データを、Twitterから収集しました。
学生さん達に、検索ワードを決めてもらい、そのワードに関連する「つぶやき」を、Twitter APIラッパーの「Tweepy」ライブラリを使用して、「ツイート」と「リプライ」を対にした「会話」データとして収集しました。
収集は、「リプライ」から、「ツイート」を逆引きし以下の処理フローで実施しました。
また、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]
コードは、以下を参照しました。
2.2. テキストマイニング
学生さん達が「Twitter」から収集した「ツイート」、「リプライ」の会話から、ディープラーニングで使用する「対訳コーパス1」を作成する為に、テキストマイニングを行いました。
1 対訳コーパスは、「自然言語処理」における機械翻訳の学習データとして利用する為等に構築された、「文と文が対訳の形で纏めた」ものを指します。
2.2.1. ワードクラウド
「wordcloud」ライブラリを使用し、収集した「会話文」の中から、出現頻度が高い「単語」を調べ、文字と画像埋め込みの可視化を行いました。
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.3.2. トレーニング状況確認
トレーニング状況を、対話形式(チャットボット)で確認してもらいました。
2.3.3. トレーニング状況確認(トレーニング済み)
次に、データ数、トレーニング時間による、「会話」精度の違いを確認してもらいました。
モデルは以下の設定で事前にトレーニングしました。
対訳コーパス数: 2,000,000
バッチサイズ: 64
ユニットサイズ: 1024
GRUモデル中間層数: 3
語彙数: 120,000
ステップ数: 300,000
3. LINEのチャットボットで遊んでみよう
ディープラーニングを使用したAIチャットボット、「えむしばくん1」をLINEの「Messaging API」を使用し、チャットボット公開しました。
学生さん達にアクセスして、「会話」を楽しんでもらいました。
1 弊社のマスコットキャラクターです。LINEスタンプ販売しています。
最後に
今回のインターンシップは、ES事業部初の試みとなり、準備から開催までの期間が短く、課題も多く見つかりました。
冬のインターンシップに向けて、来ていただく学生さん達に楽しんでもらい、弊社に興味を持っていただけるように、内容をブラッシュアップし臨みたいと思います!