Python・Mecab・ディープラーニングを用いたテキストのジャンル分類

$ pip3 install numpy
$ pip3 install scikit-learn
$ pip3 install pandas

scikit-learnはpythonで使用できる機械学習ライブラリ
ニュースコーパスは livedoorニュースコーパス を使用します。
livedoor ニュースコーパス

$ ls
text
$ cd text
$ ls
CHANGES.txt dokujo-tsushin livedoor-homme smax
README.txt it-life-hack movie-enter sports-watch
app.py kaden-channel peachy topic-news

app.py

import os, os.path
import csv

f = open('corpus.csv', 'w')
csv_writer = csv.writer(f, quotechar="'")
files = os.listdir('./')

datas = []
for filename in files:
	if os.path.isfile(filename):
		continue

	category = filename
	for file in os.listdir('./'+filename):
		path = './'+filename+'/'+file
		r = open(path, 'r')
		line_a = r.readlines()

		text = ''
		for line in line_a[2:]:
			text += line.strip()
		r.close()

		datas.append()
		print(text)
csv_writer.writerows(datas)
f.close()

$python3 app.py

以下のようなCSVファイルが生成されます。

### NNの仕組み
– 全てのテキストを形態素に分解し、全ての単語を把握した後、全ての単語に対してテキストに出現するかと言うベクトルを作成する。
– その後、ニューラルネットワークに対し、このベクトルを入力とし、出力をカテゴリとして学習を行う

nlp_tasks.py

# -*- coding: utf-8 -*-
#! /usr/bin/python3
import MeCab
from sklearn.feature_extraction.text import CountVectorizer

def _split_to_words(text):
	tagger = MeCab.Tagger('-O wakati')
	try:
		res = tagger.parse(text.strip())
	except:
		return[]
	return res

def get_vector_by_text_list(_items):
	count_vect = CountVectorizer(analyzer=_split_to_words)
	box = count_vect.fit_transform(_items)
	X = box.todense()
	return [X,count_vect]

models というフォルダを作成する

main.py

# -*- coding: utf-8 -*-
#! /usr/bin/python3
import pandas as pd
import numpy as np
from sklearn.preprocessing import LabelEncoder
from sklearn.externals import joblib
import os.path
import nlp_tasks

from sklearn.neural_network import MLPClassifier # アルゴリズムのmlp

def train():
	classifier = MyMLPClassifier()
classifier.train('corpus.csv')

def predict():
	classifier = MyMLPClassifier()
	classifier.load_model()
	result = classifier.predict(u"競馬の3歳馬日本一を決めるG1レース、「第88回日本ダービー」が行われ、4番人気のシャフリヤールが優勝しました。")
	print(result)

class MyMLPClassifier():
	model = None
	model_name = "mlp"

	def load_model(self):
		if os.path.exists(self.get_model_path())==False:
			raise Exception('no model file found!')
		self.model = joblib.load(self.get_model_path())
		self.classes = joblib.load(self.get_model_path('class')).tolist()
		self.vectorizer = joblib.load(self.get_model_path('vect'))
		self.le = joblib.load(self.get_model_path('le'))

	def get_model_path(self, type='model'):
		return 'models/'+self.model_name+"_"+type+".pkl"

	def get_vector(self,text)
		return self.vectorizer.transform()


	def train(self, csvfile):
		df = pd.read_csv(cavfile,names=('text','category'))
		X, vectorizer = nlp_tasks.get_vector_by_text_list(df["text"])

		le = LabelEncoder()
		le.fit(df['category'])
		Y = le.transform(df['category'])

		model = MLPClassifier(max_iter=300, hidden_layer_sizes=(100,),verbose=10,)
		model.fit(X, Y)

		# save models
		joblib.dump(model, self.get_model_path())
		joblib.dump(le.classes_, self.get_model_path("class"))
		joblib.dump(vectorizer, self.get_model_path("vect"))
		joblib.dump(le, self.get_model_path("le"))

		self.model = model
		self.classes = le.classes_.tolist()
		self.vectorizer = vectorizer

	def predict(self, query):
		X = self.vectorizer.transform([query])
		key = self.model.predict(X)
		return self.classes[key[0]]

if __name__ == '__main__':
	train()
	#predict()

$ ls
corpus.csv main.py models nlp_tasks.py text

$ python3 main.py
Iteration 1, loss = 4.24610757
Iteration 2, loss = 2.08110509
Iteration 3, loss = 1.70032200
Iteration 4, loss = 1.49572560
// 省略
Iteration 136, loss = 0.00257828
Training loss did not improve more than tol=0.000100 for 10 consecutive epochs. Stopping.

if __name__ == '__main__':
    # train()
    predict()

$ python3 main.py
sports-watch

まじかーーーーーーーーーーーーーーーーーー
Sugeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee

単純パーセプトロン

線形分類器の出力を-100, -200などの数値ではなく、確率で出力する
W^TX

0~1の間の値を取るようにシグモイド関数(sigmoid function)を使う
a = 1 / 1+exp(-a)
a = w1x1 + w2x2 + w3x3 + w4x4

このような分類器を単純パーセプトロンと呼び、もっとも簡単なニューラルネットワークである
どのような確率で出力されるかは特徴ベクトルxと重みベクトルwによって決まる

クロスエントロピー: 2つの確率分布の間に定義される尺度

なるほど^^

[OpenCV4.5.0] 画像をランダムフォレストで学習して判定したい3 ~ 判定

学習データを coffee.pkl に保存します。

clf = RandomForestClassifier()
clf.fit(x_train, y_train)

joblib.dump(clf, 'coffee.pkl')

学習データの画像とは別に判定したいターゲット画像を用意します。

fire_11.jpg

black_11.jpg

georgia_11.jpg

学習データを作ったように同じようリサイズ、リシェイプして判定(predict)します。

import cv2
import joblib

def predict_coffee(filename):
	clf = joblib.load("coffee.pkl")

	path = "img/target/"
	img = cv2.imread(path + filename)
	img = cv2.resize(img, (48, 64))
	img = img.reshape((-1, 9216))
	res = clf.predict(img)

	if res[0] == 0:
		print(filename + " = BLACK無糖")
	elif res[0] == 1:
		print(filename + " = FIRE微糖")
	else:
		print(filename + " = Georgia blend")
	

predict_coffee("black_11.jpg")
predict_coffee("fire_11.jpg")
predict_coffee("georgia_11.jpg")

$ python3 app.py
black_11.jpg = BLACK無糖
fire_11.jpg = FIRE微糖
georgia_11.jpg = Georgia blend

やば。。。。
blackに関しては、”Black”の文字ないのに判定できちゃうんだ。。。

ちょっと人生頑張ってみるわ。。

[OpenCV4.5.0] 画像をランダムフォレストで学習して判定したい2 ~学習

画像を用意済み。

画像サイズは864x1152pxなので、(48, 64)にします。
データラベルは、FIRE微糖=0、Georgia blend=1、BLACK無糖=2 とします。
リサイズして、一次元に展開して配列に入れる。

import cv2
import os, glob
import joblib

from sklearn.model_selection import train_test_split
from sklearn import datasets, metrics
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score


image_size = (48, 64) # 864:1152(3:4)の画像の為
path_black = "img/black"
path_fire = "img/fire"
path_georgia = "img/georgia"

x = [] # 画像
y = [] # ラベル

def read_dir(path, label):
	files = glob.glob(path + "/*.jpg")
	for f in files:
		img = cv2.imread(f)
		img = cv2.resize(img, image_size)
		img_data = img.reshape(-1, )
		x.append(img_data)
		y.append(label)

read_dir(path_black, 0)
read_dir(path_fire, 1)
read_dir(path_georgia, 2)

x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)

clf = RandomForestClassifier()
clf.fit(x_train, y_train)

y_pred = clf.predict(x_test)
print(accuracy_score(y_test, y_pred))

6回テストしてaccuracy_scoreはこんな感じ。平均して91%くらい。
$ python3 app.py
0.8333333333333334
$ python3 app.py
1.0
$ python3 app.py
1.0
$ python3 app.py
1.0
$ python3 app.py
0.6666666666666666
$ python3 app.py
1.0

91%の精度ってどうなんだろう。画像を取る角度を一定にすれば、もっと精度は上がりそうな気がする。

[OpenCV4.5.0] 画像をランダムフォレストで学習して判定したい1 ~写真の用意

まず画像の用意から。
缶コーヒーで(1)FIRE微糖、(2)Georgia blend、(3)BLACK無糖を用意する。

続いて、各缶コーヒーの写真を取る。取り敢えず、10枚ずつ位。
(1)FIRE微糖

(2)Georgia blend

(3)BLACK無糖

どの写真がどの缶コーヒーかラベリングをしたいので、画像の名前を変更する。
fireは fire_${i}.jpg、 Georgia blendは georgia_${i}.jpg、BLACK無糖は black_${i}.jpgにする。

centos8
imgフォルダの中にblack, fire, georgiaフォルダを作成し、その中にそれぞれの画像を入れる。

続いて、これらの画像を学習していきます。

[sklearn] 手書き数字のデータセットから学習

まずmatplotlibとsklearnを入れます。

$ sudo pip3 install matplotlib
$ sudo pip3 install sklearn

scikit-learnに付属しているHandwritten Digits Data Setを使います。

import matplotlib.pyplot as plt

from sklearn import datasets
digits = datasets.load_digits()

d0 = digits.images[0]
print(d0)

$ python3 app.py
[[ 0. 0. 5. 13. 9. 1. 0. 0.]
[ 0. 0. 13. 15. 10. 15. 5. 0.]
[ 0. 3. 15. 2. 0. 11. 8. 0.]
[ 0. 4. 12. 0. 0. 8. 8. 0.]
[ 0. 5. 8. 0. 0. 9. 8. 0.]
[ 0. 4. 11. 0. 1. 12. 7. 0.]
[ 0. 2. 14. 5. 10. 12. 0. 0.]
[ 0. 0. 6. 13. 10. 0. 0. 0.]]

### 画像の学習

from sklearn.model_selection import train_test_split # 学習用データとテスト用データに分割
from sklearn import datasets, svm, metrics # データセット、SupportVectorMachine, metrics
from sklearn.metrics import accuracy_score

digits = datasets.load_digits()
x = digits.images # 画像
y = digits.target # 数字
x = x.reshape((-1, 64)) # 画像の二次元配列を一次元配列に変換

x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)

# データ学習
clf = svm.LinearSVC()
clf.fit(x_train, y_train)

y_pred = clf.predict(x_test) # 学習データを元にテスト画像から数字を予測
print(accuracy_score(y_test, y_pred)) # 答えと予想の精度を確認

92%の精度
$ python3 app.py
/usr/local/lib64/python3.6/site-packages/sklearn/svm/_base.py:977: ConvergenceWarning: Liblinear failed to converge, increase the number of iterations.
“the number of iterations.”, ConvergenceWarning)
0.9277777777777778

手順としては、
(1)画像をピクセルの二次元配列データにして、データセットとラベルをセットに保存
(2)上記データセットを集める
(3)ピクセルの二次元配列を一次元配列に変換し、学習用データとテスト用データに分割する
(4)SVMで学習用データを学習
(5)テストデータから正解を予測
(6)制度をチェックする

### 学習済みデータの保存/呼び出し
※from sklearn.externals import joblibだとバインドされないので注意

import joblib
# 学習用データの保存
joblib.dump(clf, 'digits.pkl')

呼び出し

import joblib
clf = joblib.load("digits.pkl")

$ python3 app.py
0.9777777777777777

なるほど、OpenCVで画像を二次元配列にするわけだな
フローはほぼ理解した

MSEとR^2

MSEとR^2 って、六本木のバーのような名前ですが、
Mean Squared Error: MSE
→残差の2乗平均を取ったもの
1/n*nΣi=1(y実測値i – y予想値i)^2
あれ、MSEって、テストデータと検証データのnが一緒でないといけない??

決定係数: R^2
R^2 = 1 – MSE – / 1/n*nΣi=1(y実測値i – y予想値i)^2 (0≦R^2≦1)

MSE

from sklearn.metrics import mean_squared_error
y_true = [4, -0.2, 3, 6]
y_pred = [3.5, 0.0, 2, 8]

print(mean_squared_error(y_true, y_pred))

[vagrant@localhost python]$ python app.py
1.3225

R^2

from sklearn.metrics import r2_score
y_true = [4, -0.2, 3, 6]
y_pred = [3.5, 0.0, 2, 8]

print(r2_score(y_true, y_pred))

[vagrant@localhost python]$ python app.py
0.7365537848605578

交差検証法

統計学において標本データを分割し、その一部を解析して、残る部分で解析のテストを行い、解析の妥当性の検証・確認に当てる
→ これ以上標本を集めるのが困難な場合に有効

主な種類
– ホールドアウト検証
初期標本群から事例を無作為に抽出
– K-分割交差検証
標本群をK個に分割し、その一つをテスト事例、残りを訓練事例
– Leave-one-out cross validation
標本群から一つだけ抜き出し、残りを訓練。これを全事例が一回ずつテスト事例になるよう繰り返す

色々手法があるが、やはり信ぴょう性にはデータ量が物を言うような印象

正則化(Reguralization)

正則化とは、モデルが複雑になると、ペナルティが課されるような項を追加することで過学習を防ぐ
一般的には、L1正則化、L2正則化を用いる
正則化項 λE(w) = λLΣK=1|Wk|
重みWのノムルが大きくなりすぎない(複雑になりすぎない)ようにL2ノムルを追加し、Dを最小化する。
ここでλは、定数値として定め、λが大きいと、ノムルの値が小さくなる。
λの値を調整することで、正則化の度合いを調整する。

えーと、確かL1ノムルは絶対値の値、L2ノムルはユークリッド距離でしたね。
あれ、ってことは、L1ノムル(絶対値の和)で正則化することもあれば、L2ノムル(ユークリッド距離)で正則化することもあるってことね。
関数Dの最小化だから、微分するってことなんだろうけど、例えば重回帰分析とかなら、正則化の項は付いてなかったように記憶しているが、、、

ここは理解が乏しいな。。

結合確率

複数の事象が同時に起きる確率
simultaneous probability もしくは joint probability という。
P(X, Y)と表記

P(A∩B) = P(A,B) = P(A)P(B)

事象Bが起きた時に事象Aが発生する条件付き確率は
P(A|B) = P(A∩B)/P(B)

機械学習モデルの正確性を表現するには、適合率(Precision), 再現率(Recall)、F値などの指標が使われる

scikit learn
Model evaluation: quantifying the quality of predictions

混合行列(confusion matrix)とは
クラス分類問題の結果を「実際のクラス」と「予想したクラス」を軸にまとめたもの
– TP(Treu Positive)
– TN(True Negative)
– FP(False Positive)
– FN(False Negative)
陽性(Positive)と陰性(Negative)は自分で決められる

from sklearn.metrics import confusion_matrix

y_true = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
y_pred = [1, 0, 1, 1, 1, 0, 0, 0, 1, 1]

cm = confusion_matrix(y_true, y_pred)
print(cm)

[vagrant@localhost python]$ python app.py
[[1 4]
[3 2]]

なるほど、陽性、陰性は0,1の二進数で表すのね。
TN, FP, FN, TPはそのまま、flatten()で取り出せば良い。

で、正解率(accuracy)は、全てのサンプルのうち、正解したサンプル
$$
\text{accuracy} = \frac{TP + TN}{TP + TN + FP + FN}
$$

from sklearn.metrics import accuracy_score

y_true = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
y_pred = [1, 0, 1, 1, 1, 0, 0, 0, 1, 1]

print(accuracy_score(y_true, y_pred))

[vagrant@localhost python]$ python app.py
0.3

陽性と予想したうち、正解したのを適合率
pricision_scoreで示す
TP / TP+FP
再現率(recall)は実際に陽性のサンプルのうち正解したサンプルの割合
TP / TP+FN

F1値は、適合率と再現率の平均調和

つまり、予想に対して正解が高ければ、F1値は1に近づくし、モデルとして優れているということか。。