python データベース(RDBMS)を作成しデータを格納。
実行結果
hw.sqlite3というデータベースファイルが生成される。
実際の機械学習のプログラムにおいてはデータベースでのデータを使用することがほとんどなのでデータベースを連携させ、機械学習を行えるようにデータベースの生成から学んでいく。
上記コードでデータベースファイルの作成。
今回では簡易に利用できるデータベース(RDBMS)として、SQLiteを利用。SQLiteであれば、pythonの標準ライブラリーなので手軽にSQLクエリーを利用してデータベースを操作できる。
今回作成しているデータベースは、身長と体重、体型の3つのフィールドを持つデータベースとする。
列 フィールド説明 DBフィールド名
0 顧客ID(自動で追加) id
1 身長(cm) height
2 体重(kg) weight
3 体型(0~5の値) typeNo
体型は6段階の値で表現
値 体型
0 低体重(痩せ型)
1 普通体重
2 肥満1
3 肥満2
4 肥満3
5 肥満4
データベースに接続して、SQL文の実行
sqlite3.connect()でデータベースに接続し、オブジェクト生成。SQL文を実行するため、execute()メソッドを使用。
SQL文ではデータベースを作成するために三連引用符を使用。
CREATE TABLE IF NOT EXISTS person(カラム名 データ型, カラム名 データ型...)
CREATE TABLE(テーブル作成) IF NOT EXISTS(オプション、データベースに同じ名前のテーブルがないときだけテーブルを作成する制約) person(テーブル名)。
続く()内ではカラム名とデータ型を指定している。
id(カラム名) INTEGER(データ型) PRIMARY KEY(制約、重複した値の保存を禁止する)
height(カラム名) NUMBER
weight(カラム名) NUMBER
typeNo(カラム名) INTEGER
INTEGERとNUMBERはどちらも整数型を表すそうだが明確な違いはわからなかった。
次に新規に身長と体重、体型を100件追加するプログラムを実行してデータベースにデータを格納していく。
実行結果
100件の身長、体重、体型のデータがデータベースに格納される。
データベースに接続し、関数呼び出し
sqlite3.connect()で引数にデータベースファイルのパスを指定しデータベースに接続。
for文で100回insert_db()関数を実行。insert_db()関数では身長、体重、体型のデータをデータベースに格納し、内容を出力する。
c = conn.execute('SELECT cout(*) FROM preson')ではpersonテーブルのレコードの総数を取得している。SELECT文はデータベースからデータを取り出す時に使う。count(*)で全てのレコードの総数を取得。FROM句でテーブル指定。返り値はsqlite3.Cursorオブジェクト。
c.fetchone()でcorsorオブジェクトの1行目をタプルで取得する。
insert_db()関数、データベースにデータを格納
random.randint(130, 180)で130~180までの整数をランダムに変数heightに格納。
weightも同様。
体型はbmiによって値を変更。BMIは体重(kg) / (身長(cm)/100)の2乗 。
**を使用することで身長/100を2乗している。
if文でBMIの値によってtype_no(体型)に格納する値を変更。
conn.executemany()で一度に複数のデータを保存する。タプルをリストのを扱うので第二引数で[]で囲っている。for文を回すように一つの指令(第一引数のSQL文)を複数のデータに実行する(第二引数)。
第一引数のSQL文のINSERTではデータベースにデータを保存したい時にはINSERT文を使う。
書式
INSERT INTO テーブル名(カラム名, カラム名..)
VALUES(値, 値,..)
VALUES(?,?)のようにすることでexecutemany()の第二引数で指定したリストが順番に?の部分に格納され、それぞれのカラムに保存される。
webサービスにおける学習データについて
webサービスにおける学習データについて
現在では多くのサービスで機械学習システムを導入しているが、そのシステムが活躍する上で一番難しいところが「一度作って終わりではない」という点にある。日々、新しい投稿が行われ、分類精度が低下してしまうからである。流行語や新製品など、正解データは常に変化していく。そのため定期的に教師データを新しくして、学習をやり直す必要がある。webサービスでは投稿された内容をデータベースに保存する。そのデータベースに蓄積されたデータを用いて、機械学習システムを構築する。それにより、機械学習システムがwebサービスでユーザーの投稿を支援する。そして、それにより、ユーザーがさらに多くの投稿をし、それらがデータベースに蓄積されていく。そうした、蓄積されたデータを機械学習に応用すると高い精度で投稿を支援できる。
多くのwebサービスでは、「webサービス」→「データベース」→「機械学習システム」→「webサービス」→「データベース」....このようなフローが自動的に行われるような構築するのが効果的。何かしらのきっかけで急に判定精度が悪くなる可能性もあるので、精度が落ちたら通知が来るようにするなど、自動化の落とし穴を避ける仕組みも組み込むと良い。
まとめ
・学習済みデータを保存しておいて、業務システムから読み込んで使うと効率がいい
APIを呼び出すwebアプリの作成
index.html
実行結果
今回のプログラムでは
hanamichi-sukusuku.hatenablog.com
上記で作成した機械学習の機能を持つwebサーバーを使用して、APIを呼び出すwebアプリを作成していく。
webサーバーでは既にURLで渡されたパラメーターを機械学習にかけるようになっている。
今回必要なのはhtml/css, javascriptを用いたindex.htmlファイルを作成する。
入力欄とボタンの作成
判定ボタンがクリックされたときの処理(JS)
then((data)=>...ではhtmlで結果を出力するdiv要素にラベルデータと確率のデータを文字列で追加している。
*1:res) => {
const qsで無名関数を定義。引数に指定したidの要素を取得する。
qButton.onclickで判定ボタンが押されたときの処理を記述。
result.innerHTMLでAPIサーバーから出力が返ってくるまで"..."と表示する。
const apiで変数apiにAPIサーバーに送信するURLを構築。encodeURIComponent()では引数に指定した文字列をURLで使用できる形式に変換(エンコード)するためのもの。URLでは日本語は使用できないのでパラメーターとして送る文字列をこれによって変換している。
fetch()メソッドでは非同期で引数に指定したURLにHTTPリクエストを送信している(デフォルトはGET)。responseオブジェクトをfetch(api)からthen((response
機械学習の機能を持つwebサーバーの作成
webサービスに機械学習のシステムを組み込むためにはwebサーバーと機械学習サーバーを異なる形で動かすことやwebサーバー内で機械学習のシステムを持たせる方法になどによってwebサービスに機械学習のシステムを組み込むことができる。
今回はまず、pythonで機械学習の機能を持つwebサーバーを作成していく。
実行結果
ソースコードでは見やすさの観点からjupyter notebookでのコードになっているが実行はコマンドラインから行う。
実行結果では「野球を見るのは........楽しみです。」という文章をプログラムに渡して、json形式でその結果が返されている。jsonなので日本語がエンコードされていまっているがデコードすると「スポーツ」となる。
hanamichi-sukusuku.hatenablog.com
ここで使用しているプログラムは同じディレクトリ内にある、上記で作成したファイル(my_text.py)のcheck_genre関数を使用し、URLで渡した文章を分類している。
モジュールインポート
jsonモジュールはpythonでjson形のデータを扱う時に利用する。
flaskはwebサーバーを手軽に作成できるフレームワーク。
from flask import requestではURLからパラメーターの取得ができたりするためにインポート。
import my_textは上記で記述した通り、事前に作成した機械学習のプログラム。
ポート番号、HTTPサーバーの起動
TM_PORT_NOはポート番号で8888を定義しているがもし、別のアプリで8888番を使っている場合には他の番号に変更する。
flask.Flask(__name__)でFlaskオブジェクトを生成する。
ターミナル上でプログラムが正しく実行されるかのテスト
この出力結果はターミナル上に出力される。
無事出力されたので次に進む。
ローカルサーバー起動
appにはFlaskオブジェクトが格納されているのでapp.run()でサーバーを起動。
引数のdebug=Trueではデバッグを出力するようにしている。
host='0.0.0.0.'を指定しないと外のネットワークから接続ができないらしい。パソコンでこのローカルサーバーを立ち上げ、別の端末からこのサーバーにアクセスするために必要。実際に自身のスマホから接続できた。
portはポート番号に指定。
ルート(/)にアクセスしたときの処理
@app.route()でルートパスにアクセスされた時の後述する関数を実行するようにしている。methods=['GET']でGETリクエストを指定。
index()関数では同階層のindex.htmlファイルを読み込み、表示している。
http://localhost:8888/このURLにアクセスした時の画像。
/apiにアクセスしたときの処理
@app.route()で/apiにアクセスした時、後述する関数を実行するようにしている。ここでもmethods=['GET']でGETリクエストを指定。
request.args.get()ではURLからパラメーターをしている。第一引数の'q'はキー。URLで'q'をキーとするパラメーターを取得し変数qに格納。
my_text.check_genre()では事前の作成したmy_text.pyからcheck_genre()関数を使用している。上記のリンクを見ればわかるが、この関数では渡した文章を機械学習で分類し、どのラベルを示すものか、確率、ラベルデータの番号をそれぞれ返す。
return json.dumps()で第一引数に辞書を渡すとjson文字列として出力されたものを返す。
一番上の実行結果には
http://localhost:8888/api?q=(テキスト)という形でアクセスしたときの実行結果を表示している。
文章を指定してTF-IDFに変換しディープラーニングで判定
実行結果
このプログラムではMLPを利用して文章を指定しどんなジャンルの文章なのか判定するプログラム。
インポートしているtfidfモジュールは下記のリンクで作成したものを使用する。
TF-IDFの手法でモジュール作成 - hanamichi_sukusukuのブログ
独自テキストの定義、TF-IDFの辞書を読み込む
""" """は中身の改行をそのままの状態で扱うことができる。改行文字\nで表記した場合と同じ。
tfidf.laod_dic()は作成したtfidfモジュールのload_dic()関数である。この関数では引数で渡したパスから保存した単語辞書や単語の出現頻度を格納してあるデータをtfidfモジュール内で読み込み、グローバル変数として使用できるようにするもの。これによりtfidfモジュールから単語辞書、全文章での単語の出現頻度、livedoorニュースコーパスの文章をIDで表現したデータの3つが使用できるようになる。
モデル定義
dt_countはMLPの入力レイヤーでの入力数を格納している。tfidf.dt_dicはlivedoorニュースコーパスを使用した4ジャンルの文章全体での単語の出現頻度が格納されているデータ。一つの文章はこのdt_dicにある単語数で表現されるのでこの要素数が入力数になる。具体的には文章中の単語を辞書のidで表現し、その出現頻度と希少性を掛け合わせたTF-IDFのデータを使い、モデルに学習させるので入力数としては辞書の単語数(要素数)を使用する。
model.load_weights()で重みデータを読み込んでいる。この重みデータはlivedoorニュースコーパスのテキストを事前にMLPで学習し、その時の重みデータを保存したもの。
関数の呼び出し
check_genre()関数
この関数では引数で受け取ったテキストをモデルに学習させ予測結果を出力するためのもの。
tfidf.calc_text()でtfidfモジュール内の単語辞書を更新せずにTF-IDFベクトルに変換する。(単語辞書の要素数で表現されたTF-IDFベクトルデータ)
model.predict()で結果を予測。
argmax()で予測結果から最も値が大きいインデックス番号を返す。
LABELSに定義している要素のインデックスとラベルデータのインデックスは対応しているので予測したラベルと確率を出力。
・このプログラムでは学習ずみの単語しかベクトル化できない。今回作成したモジュールでは、livedoorニュースコーパスに出てこない、未知語を見つけると単語をなかったことにする処理にしてあるため学習したことない単語が多く出てくるほど、判定結果が悪くなる。そこで、未知語が出てきたら覚えておいて、改めて学習をやり直すなど、工夫が必要になる。
NaiveBayesでTF-IDFで作成したデータベースを学習
実行結果
このプログラムではNaiveBayes(ナイーブベイズ)を利用してTF-IDFのデータベースを学習している。
TF-IDFのデータベースを読み込む
hanamichi-sukusuku.hatenablog.com
genre.pickleに関しては上記でlivedoorニュースコーパスのデータを利用してTF-IDFベクトルに変換したデータを作成している。
学習用とテスト用に分ける
ナイーブベイズで学習
評価して結果を出力
metrics.classification_report()では正解データと予測データを渡すことでprecision(適合率)やrecall(再現率)などのそれぞれのデータを比較したときの詳細をみることができる。
metrics.classification_report()見方
y_test = [0,0,0,1](正解)
y_pred = [0,0,1,1](予測)
例えばmetrics.classification_report(y_test, y_pred)このような場合
precision
・precision(適合率)は0と予測した2つはどちらも正解なので適合率1.00。
・1と予測した2つはそのうちの1つが正解なので適合率0.50。
recall
・recall(再現率)は正解が0だった3つのうち、正しく0だと予測されたものは2つなので再現率0.67。
・正解が1だった1つを予測結果で予測できているので再現率1.00。
f1-score
fi-scoreは調和平均。
support
正解の要素数。
文章をTF-IDFのデータベースに変換
このプログラムではlivedoorニュースコーパスを利用する。
スポーツ、IT、映画、ライフの4つに分けたデータベースを生成する。
実行結果
genre.pickleというファイルにTF-IDFに文章を変換したデータを格納し保存する。
tfidfモジュール内で生成した単語辞書、テキストをIDで表現したデータ、全文章での単語の出現頻度のデータも保存。
ファイル読み込み
read_files()関数に事前にlivedoorニュースコーパスからtextディレクトリに作成しておいたそれぞれのファイルのパスとラベルにする値を渡す。
glob.glob()でファイルの中身のテキストファイル名を全て取得。
os.path.basename()では引数に渡したパスからファイル名を取得してくれる。LICENSE.txtの場合はcontinue。
tfidf.add_file()は自作したモジュールから関数を使用している。
hanamichi-sukusuku.hatenablog.com
上記のリンクでモジュールを作成している。
tfidf.add_file()
引数で受け取ったパスのファイルを読み取り用で開き、read()で読み込む。
add_text()ではテキストをIDリストに変換してくれる。
tfidf.add_file()関数を実行すると単語辞書、テキストをIDで表記したデータを生成してくれる。
モジュールの詳細は上記リンク。
y.append()でラベルデータ作成。
TF-IDFベクトルデータをxに格納
tfidf.calc_files()ではTF-IDFの手法で文章中に出現した単語の出現頻度を重要も考慮した形でのデータに変換し呼び出し元に返す。同時に関数で全テキストデータでの単語の出現頻度のデータも生成している。
このxの中身を確認すると
TF-IDFでの単語の重要度を考慮した形でのデータになっている。
データの保存
pickle.dump()でラベルデータ、TF-IDFベクトルに変換したテキストデータをtextディレクトリにgenre.pickleという名前のファイルを作成し保存。
tfidf.save_dic()では引数に指定したパスに単語辞書、テキストをIDで表現したデータ、全文章での単語の出現頻度のデータ(一つのファイルで複数回同じ単語が出現しても足し合わせない。一度でも複数でもその単語をキーとする値は1)を保存する。