Djangoでチャットボットアプリ作ってみた１

# ばらまく免罪符
- とりあえず動いたので書くだけ
- データサイエンティストではない(学習モデルの作り方がいい加減)
- 理系出身でもない(数式などの詳しい内容はあまり理解していない)
- エンジニアとしてもまだまだ初級者

# チャットボットを作ってみようと思った理由
- 社会人になって人から褒められる機会が減ったので、何をやっても褒めてくれるアプリがほしくなった
- 合コンさしすせそみたいに合コン50音を作って、50通りに返答してくれるチャットボットにしようと思った
    - 例１：「今日はDjangoが少し使えるようになったよ」→「すごーい(\*'ω'\*)」
    - 例２：「はい、プレゼント」→「こんなのはじめて(\*'ω'\*)」

# この記事を書こうと思った理由
- Django上でMeCabを使うときにはまったことがあり、Qiita上であまり記事を見かけなかった気がするので書いてみることにした
- 英語の記事を読むのが苦手なので、もう読まなくていいように日本語でメモっておきたい

# PythonとDjangoを選んだ理由

## pythonを選んだ理由
- 機械学習のライブラリが充実しているのがPythonだった。C++は使いたくなかった。

## Djangoを選んだ理由
- pythonのフレームワークについてはFlaskとDjangoしか知らなかった。
- Flaskは便利に使うまでにいろいろと準備が必要そう(sqlalchemyやWTFormsのインストールなど)だったので、管理者アプリも初期で用意されておりインストールコマンド一発で開発が始められそうなDjangoにしてみた。
- 今後、いろいろ機能追加したいなと思ったときにFlaskよりDjangoのほうがパッケージも含めて充実していそうだと思った。
- 結局、今回はModel関係クラスを一切使っていないので、Flaskでもよかったと思っている。

# 本編
## 概要
1. 学習データを用意する(今回)
1. データにラベル付けをする(今回)
1. Colaboratoryで学習させる
1. pipenvで仮想環境を作ってDjangoをインストール
1. MeCabをインストール
1. Djangoに予測の仕組みを組み込む
1. BotUIでUIを作成する

## 超ざっくりした機械学習の説明
### 機械学習でできること
機械学習ではいくつかのことができる。
分類・・・ラベル付けしたデータを学習させておいて、新しいデータがどれに該当するかを判定してくれる
回帰・・・今までのデータから関数を求めて、未知の値に対して予測をしてくれる
クラスタリング・・・いくつに分類するかを決めたら、その個数分にグループ化してくれる
次元削減・・・人間がわかりやすいように次元を削減して表してくれる
![slide.png](https://qiita-image-store.s3.amazonaws.com/0/227535/d4e86705-590b-21d0-19e1-2b8a2a40637e.png)

今回は文章が入ってきたら、50音のうち一番適切なデータが返ってくるようにしたい。
そのため、分類を使用する。

### 分類するために
分類をさせるためにはあらかじめ、データ(X)を集めてそれに対しての正解(Y)を与える。
このXとYの組み合わせをたくさん学習させることにより、関数が作られる。
Y1の領域、Y2の領域...みたいな感じで分類の境界が決まっていく。

下記のページの決定境界についての画像の色分けでいうと、このデータは赤、このデータは青、このデータは緑、と各データに色のラベルをつける。
この位置に入ってきたら赤、この位置に入ってきたら青、、、みたいに分類できるようになるイメージ。
画像：https://cdn-ak.f.st-hatena.com/images/fotolife/d/dskomei/20180228/20180228214011.png
引用元:http://www.dskomei.com/entry/2018/03/04/125249

というわけで、文章(X)と適切な返答(Y)をセットとした学習させる必要がある。
なので、文章を集めて、ラベルを貼っていく必要がある。
(本当はラベルに該当するデータを集めたほうがデータを作るのが簡単なのでおすすめ。)

## １、学習データを準備する
### 用意したいデータをアメブロから取得する
- 50音分の回答する予定のデータを作成(例:3→うんうん,13→すごーい！)
- 教師あり機械学習の場合、データに対して正解ラベルをつけなくてはいけない。  
  （例：データ「ごはん美味しいね」→正解「3(うんうん)」、データ「python完璧に理解した」→正解「13(すごーい)」）
- Twitterでデータを集めようと思った(例：リプライで「うんうん」や「すごーい」が含まれているデータを取得する)が、一般のAPIだとリプライに限定して収集するということができなさそうだったのとAPIの申請が通らなかったので断念。
- 芸能人のブログには「すごーい！」などの褒めるようなコメントがつくことが多いので、アメブロの芸能人ランキングからスクレイピングでデータを取得。
    - ChromeのWebdriverをインストール(BeautifulSoupだけで実現できて、いらなかった気もしている)
        - 参考：https://www.inet-solutions.jp/technology/python-selenium/
    - BeautifulSoup4を使って一行ずつ取得(アメブロは一行ずつskin-entryBodyクラスの本文が一行ずつdivで囲まれているように見えたので、divを１つずつ取得している) 

```scraping.py
# -*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import NoAlertPresentException
import unittest, time, re
from bs4 import BeautifulSoup
import sys, io
import requests

#芸能人のブログランキング
RANKING_URL = "https://official.ameba.jp/rankings/total"

#Webドライバーを使ってChromeを自動で動かす
driver = webdriver.Chrome()
driver.get(RANKING_URL)
html_ranking_page = driver.page_source
soup_ranking_page = BeautifulSoup(html_ranking_page, 'html.parser')
link = soup_ranking_page.find_all('a', class_="ranking-list-entry-title-link")
for i in range(len(link)):
    URL = link[i].attrs['href']
    driver.get(URL)
    resp = requests.get(URL)
    for i in range(100):
        #html_blog_page = driver.find_element_by_id("entryBody")
        #text = html_blog_page.text
        soup_blog_page = BeautifulSoup(resp.text)
        blog_content = soup_blog_page.find('div',class_="skin-entryBody")
        #print(blog_content)

        if(blog_content):
            blog_str = blog_content.strings
            f = open('data.txt','a')
            for s in blog_str:
                if s == '\xa0':
                    break;
                elif s.find('\xa0'):
                    s = s.replace(u'\xa9', u' ')
                f.write(s+"\n")
            f.close()
        if soup_blog_page.find('a', class_="skin-pagingNext"):
            blog_link = soup_blog_page.find('a', class_="skin-pagingNext")
            blog_link = blog_link.attrs['href']
            print(blog_link)
        elif soup_blog_page.find('a', class_="pagingNext"):
            blog_link = soup_blog_page.find('a', class_="pagingNext")
            blog_link = blog_link.attrs['href']
            print(blog_link)
        else:
            break;

        if blog_link.find('https://') > -1:
            BLOG_URL = blog_link
            print(BLOG_URL)
        elif blog_link.find('//ameblo.jp') > -1:
            BLOG_URL = "https:" + blog_link
        else:
            BLOG_URL = "https://ameblo.jp" + blog_link
            print(BLOG_URL)

        resp = requests.get(BLOG_URL)
```

- 絵文字が邪魔だったので、よく出てくる絵文字を消そうとしてみた

```remove_emoji.py
# -*- coding: utf-8 -*-
import sys
import re

# チェック元ファイル
f = open("testcopy.txt")
# 絵文字除去後のファイル
new_file = open('testcopy_rm_emoji.txt', 'a')

# 正規表現パターンを構築
emoji_pattern = re.compile("["
        u"\U0001F600-\U0001F64F"
        u"\U0001F300-\U0001F5FF"
        u"\U0001F680-\U0001F6FF"
        u"\U0001F1E0-\U0001F1FF"
                           "]+", flags=re.UNICODE)

#(https?|ftp)(:\/\/[-_\.!~*\'()a-zA-Z0-9;\/?:\@&=\+\$,%#]+)
#^[、|！|\s]*\n$
#^[0-9a-zA-Z|\s| -/:-@\[-~]+$

# １行ずつ処理
for line in f:
    print(line)
    new_file.write(emoji_pattern.sub(r'', line))

f.close();
new_file.close();
``` 

### データへのラベリングをしていく
- 上記で作成したデータはこんな感じになった(なお、海老蔵さんのブログ)

```blog.txt
えらい！
ダッシュで帰ってまーす
帰りまーす
間違いなく今日一日で太ります。
そして
ちょっとお楽しみに出かけてきます
進撃の巨人みてたら
かんげんも見だして笑怖いのでしょうね
目を覆いながら見てますパパ出かけるけど
みてる？
と聞くと
しばらく考えて
みるー
と力強く答えました笑笑お楽しみサクッとおえて
早めに帰ろうと思います
マンゴー
進撃の巨人。
今日は見続けてます笑笑
人類の先のお話の様です。
巨人と人類の戦い流行っていたの分かりますね！
見始めると止まらないっす。
たまらないっす
ブロリーと戦うの作ってるのー
そして彼好みのおやつはコレ！
シンプルなものが好きのようです
餌の時間
届くようになってきたね
ポップコーン
これも目的だったのかしらん、おかえり
起きてたべなさいふむお勉強お疲れ
暑いっすね今日も
少しフラッとして帰ろうね…
あはは
と言われまして
グダグダしてたら怒られた
連れ出されました
たしかにグダグダすることが
あまりないので
麗禾的にはダメらしいです笑笑
ありがとう
少し勉強しそれからはダメ男してます。
クッチャネしてます進撃の巨人はじめて見てます！
見続けてます。
楽しい…
そして
まんじゅう、ダメ男してます。
ありがとう。
麗禾とコッソリ買っちゃいました、イクラ丼ラブです
今日は、海鮮の日でございまする
幸せ、お買い物して帰ろうね
ちょいと行って来ます
いこうね
さ！
朝トレ！
おす！
食べよう
朝から気合い完璧注入させてもらいました
席着きましたー
さぁ！
食べちゃお
ついてから結構歩きます
```

- これをGoogleスプレッドシートに貼りつけて、正解のラベルをはっていく。  

```blog.csv
sentence,label
えらい！,1
ダッシュで帰ってまーす,2
帰りまーす,3
間違いなく今日一日で太ります。,2
そして,1
```

- これは手動でやった。途中でとりあえず動かすため適当にラベル付けしたので、学習済みモデルが残念な感じに。
- TwitterのAPIが簡単に使えるようになることを祈るばかり。


# すみません・・・
力尽きたので、次回続きを書きます。
全然Djangoに到達しない・・・