第6章: 機械学習
ここでもGoogle Colaboratoryを使用します。
ただし、作成したデータを残しておくため、Googleドライブをマウントをマウントしておきます。
from google.colab import drive
drive.mount('/content/drive')
出力コンソールに表示されるURLにアクセスし、Googleアカウントでログインします。
そしてブラウザに表示される文字列を出力コンソールにコピーしてEnterキーを押します。
<出力>
Go to this URL in a browser: https://accounts.google.com/o/oauth2/auth?client_id=XXXXXXXXXXXXXXXXXXXXXXXX
Enter your authorization code:
··········
Mounted at /content/drive
50. データの入手・整形
News Aggregator Data Setをダウンロードし、以下の要領で学習データ(train.txt),検証データ(valid.txt),評価データ(test.txt)を作成せよ.
ダウンロードしたzipファイルを解凍し,readme.txtの説明を読む.
情報源(publisher)が”Reuters”, “Huffington Post”, “Businessweek”, “Contactmusic.com”, “Daily Mail”の事例(記事)のみを抽出する.
抽出された事例をランダムに並び替える.
抽出された事例の80%を学習データ,残りの10%ずつを検証データと評価データに分割し,それぞれtrain.txt,valid.txt,test.txtというファイル名で保存する.ファイルには,1行に1事例を書き出すこととし,カテゴリ名と記事見出しのタブ区切り形式とせよ(このファイルは後に問題70で再利用する).
学習データと評価データを作成したら,各カテゴリの事例数を確認せよ.
このデータはzipで圧縮されており、中に複数のファイルが存在するため、URL直接指定でpandasのDataFrameに取り込めません。
あきらめて、ダウンロードして解凍し、Googleドライブ上のフォルダに配置しました。
import pandas as pd
from sklearn.model_selection import train_test_split
ds = pd.read_csv("/content/drive/My Drive/NL100/data/newsCorpora_re.csv", sep='\t')
ds.columns = ["ID", "TITLE", "URL", "PUBLISHER", "CATEGORY", "STORY", "HOSTNAME", "TIMESTAMP"]
# 情報源(publisher)が”Reuters”, “Huffington Post”, “Businessweek”, “Contactmusic.com”, “Daily Mail”の記事のみ抽出
ds1 = ds.loc[(ds["PUBLISHER"]=="Reuters")|(ds["PUBLISHER"]=="Huffington Post")|(ds["PUBLISHER"]=="Businessweek")|(ds["PUBLISHER"]=="Contactmusic.com")|(ds["PUBLISHER"]=="Daily Mail")]
# CATEGORY, TITLEのみ抽出
ds1 = ds1.iloc[:,[4,1]]
print(ds1.head())
# 学習データと検証データ、評価データに分割
trainDS, testDS = train_test_split(ds1, test_size=0.2, shuffle=True, random_state=123, stratify=ds1['CATEGORY'])
validDS, testDS = train_test_split(testDS, test_size=0.5, shuffle=True, random_state=123, stratify=testDS['CATEGORY'])
# タブ区切りでcsvに出力
trainDS.to_csv("/content/drive/My Drive/NL100/data/train.csv", sep='\t',index=False)
validDS.to_csv("/content/drive/My Drive/NL100/data/valid.csv", sep='\t',index=False)
testDS.to_csv("/content/drive/My Drive/NL100/data/test.csv", sep='\t',index=False)
<出力>
CATEGORY TITLE
11 b Europe reaches crunch point on banking union
12 b ECB FOCUS-Stronger euro drowns out ECB's messa...
18 b Euro Anxieties Wane as Bunds Top Treasuries, S...
19 b Noyer Says Strong Euro Creates Unwarranted Eco...
28 b REFILE-Bad loan triggers key feature in ECB ba...
作成データの行数を確認
%cat "/content/drive/My Drive/NL100/data/train.csv" | wc -l
%cat "/content/drive/My Drive/NL100/data/valid.csv" | wc -l
%cat "/content/drive/My Drive/NL100/data/test.csv" | wc -l
<出力>
10685
1337
1337
51. 特徴量抽出
学習データ,検証データ,評価データから特徴量を抽出し,それぞれtrain.feature.txt,valid.feature.txt,test.feature.txtというファイル名で保存せよ. なお,カテゴリ分類に有用そうな特徴量は各自で自由に設計せよ.記事の見出しを単語列に変換したものが最低限のベースラインとなるであろう.
特徴量を作るのに
scikit-learnのTfidfVectorizerを使用しています。
TfidfVectorizerは、テキストデータを、TF-IDFのマトリックスに変換します。
TF-IDFは、文書中に含まれる単語の重要度を評価する手法の1つで、主に情報検索やトピック分析などの分野で用いられています。
TF-IDFは、
TF(英: Term Frequency、単語の出現頻度)と
IDF(英: Inverse Document Frequency、逆文書頻度)の二つの指標の積で表されます。
TF = \frac{文書内の指定単語の出現回数}{文書内の全単語の出現回数} \\
IDF = log \frac{総文書数}{指定単語を含む文書数} \\
TFIDF = TF × IDF
import string
import re
# テキストデータの前処理関数
def preprocessing(text):
# string.punctuationは英数字以外のascii文字のこと。
table = str.maketrans(string.punctuation, ' '*len(string.punctuation))
text = text.translate(table) # 記号をスペースに置換
text = text.lower() # 小文字化
text = re.sub('[0-9]+', '0', text) # 数字列を0に置換
return text
import pandas as pd
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
ds1 = pd.read_csv("/content/drive/My Drive/Pythonライブラリ基礎講座/data/train.csv", sep='\t')
ds2 = pd.read_csv("/content/drive/My Drive/Pythonライブラリ基礎講座/data/valid.csv", sep='\t')
ds3 = pd.read_csv("/content/drive/My Drive/Pythonライブラリ基礎講座/data/test.csv", sep='\t')
ds = pd.concat([ds1, ds2, ds3])
ds.index=[np.arange(0,len(ds))] # 後で列方向にDataFrame結合するときのキーエラーを防ぐため、インデックスを振りなおす
print(ds.info())
vec_tfidf = TfidfVectorizer(min_df=10, ngram_range=(1, 2)) # 最小出現頻度:10 ユニグラムとバイグラム
# ベクトル化
ds["TITLE"] = ds["TITLE"].map(lambda x: preprocessing(x))
X = vec_tfidf.fit_transform(ds["TITLE"])
print('Vocabulary size: {}'.format(len(vec_tfidf.vocabulary_)))
print('Vocabulary content: {}'.format(vec_tfidf.vocabulary_))
dfw = pd.DataFrame(X.toarray(), columns=vec_tfidf.get_feature_names())
dfw.index=[np.arange(0,len(dfw))]
data = pd.concat([ds, dfw], axis=1)
# 学習データと検証データ、評価データに分割
trainDS, testDS = train_test_split(data, test_size=0.2, shuffle=True, random_state=123, stratify=data['CATEGORY'])
validDS, testDS = train_test_split(testDS, test_size=0.5, shuffle=True, random_state=123, stratify=testDS['CATEGORY'])
# タブ区切りでcsvに出力
trainDS.to_csv("/content/drive/My Drive/NL100/data/train.feature.csv", sep='\t',index=False)
validDS.to_csv("/content/drive/My Drive/NL100/data/valid.feature.csv", sep='\t',index=False)
testDS.to_csv("/content/drive/My Drive/NL100/data/test.feature.csv", sep='\t',index=False)
%cat "/content/drive/My Drive/NL100/data/train.feature.csv" | wc -l
%cat "/content/drive/My Drive/NL100/data/valid.feature.csv" | wc -l
%cat "/content/drive/My Drive/NL100/data/test.feature.csv" | wc -l
<出力>
10685
1337
1337
52. 学習
51で構築した学習データを用いて,ロジスティック回帰モデルを学習せよ.
scikit-learnのロジスティック回帰モデルを使用します。
from sklearn.linear_model import LogisticRegression
import sklearn.preprocessing as sp
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
#データ準備
data1 = pd.read_csv("/content/drive/My Drive/NL100/data/train.feature.csv", sep='\t')
data2 = pd.read_csv("/content/drive/My Drive/NL100/data/valid.feature.csv", sep='\t')
data3 = pd.read_csv("/content/drive/My Drive/NL100/data/test.feature.csv", sep='\t')
y_train = data1["CATEGORY"]
X_train = data1.drop(["TITLE","CATEGORY"],axis=1)
y_valid = data2["CATEGORY"]
X_valid = data2.drop(["TITLE","CATEGORY"],axis=1)
y_test = data3["CATEGORY"]
X_test = data3.drop(["TITLE","CATEGORY"],axis=1)
# modelの定義(ロジスティック回帰)
model = LogisticRegression(random_state=123, max_iter=10000)
# 学習
model.fit(X_train, y_train)
53. 予測
52で学習したロジスティック回帰モデルを用い,与えられた記事見出しからカテゴリとその予測確率を計算するプログラムを実装せよ.
# 予測
pred=model.predict(X_test)
for i in np.arange(0,len(y_test)-1):
print("予測:{0} 正解:{1} {2}".format(pred[i], y_test[i], pred[i]==y_test[i] ))
<出力>
予測:e 正解:e True
予測:e 正解:e True
予測:b 正解:b True
予測:b 正解:b True
予測:e 正解:e True
予測:e 正解:e True
予測:b 正解:b True
予測:b 正解:b True
予測:e 正解:e True
予測:b 正解:b True
予測:e 正解:e True
予測:e 正解:e True
予測:e 正解:e True
予測:t 正解:t True
予測:b 正解:b True
予測:e 正解:e True
予測:b 正解:m False
予測:b 正解:b True
予測:e 正解:e True
予測:b 正解:b True
予測:e 正解:e True
予測:b 正解:e False
予測:e 正解:e True
予測:e 正解:e True
予測:t 正解:t True
予測:e 正解:t False
・・・(省略)・・・
予測:e 正解:e True
54. 正解率の計測
52で学習したロジスティック回帰モデルの正解率を,学習データおよび評価データ上で計測せよ.
# 正解率表示
print("正解率(学習データ):%f" % model.score(X_train, y_train))
print("正解率(検証データ):%f" % model.score(X_valid, y_valid))
print("正解率(評価データ):%f" % model.score(X_test, y_test))
<出力>
正解率(学習データ):0.928117
正解率(検証データ):0.878743
正解率(評価データ):0.882485
55. 混同行列の作成
52で学習したロジスティック回帰モデルの混同行列(confusion matrix)を,学習データおよび評価データ上で作成せよ.
混同行列とはクラス分類で、正解クラスに対する予測クラスを表にまとめたもので、
2値分類の場合はTP(真陽性),TN(真偽性),FP(偽陽性),FN(偽陰性)の算出に利用できます。
sklearn.metrix.confusion_matrix( 正解ラベル, 予測値 )
と実行した場合、
- | 予測値 |
---|---|
正解ラベル | 該当条件での合計値 |
で表示されます。
また、クラスはlabelsオプションで出力順を指定できますが、未指定の場合はソート順で出力されます。
from sklearn.metrics import confusion_matrix
# 学習データでの混同行列
y_true = list(y_train)
y_pred = model.predict(X_train)
cm_train = confusion_matrix(y_true, y_pred)
print(cm_train)
# 検証データでの混同行列
y_true = list(y_valid)
y_pred = model.predict(X_valid)
cm_valid = confusion_matrix(y_true, y_pred)
print(cm_valid)
# 評価データでの混同行列
y_true = list(y_test)
y_pred = model.predict(X_test)
cm_test = confusion_matrix(y_true, y_pred)
print(cm_test)
<出力>
[[4350 89 9 53]
[ 50 4172 2 11]
[ 93 123 507 5]
[ 185 141 7 887]]
[[524 29 1 9]
[ 13 512 1 3]
[ 14 28 46 3]
[ 41 19 1 92]]
[[524 22 3 14]
[ 14 515 1 0]
[ 20 18 50 3]
[ 37 24 1 90]]
56. 適合率,再現率,F1スコアの計測
52で学習したロジスティック回帰モデルの適合率,再現率,F1スコアを,評価データ上で計測せよ.カテゴリごとに適合率,再現率,F1スコアを求め,カテゴリごとの性能をマイクロ平均(micro-average)とマクロ平均(macro-average)で統合せよ.
適合率(precision):陽性と予測されたサンプルのうち正解したサンプルの割合。
$$適合率=\frac{TP}{TP+FP}$$
再現率(recall):実際に陽性のサンプルのうち正解したサンプルの割合。
$$再現率=\frac{TP}{TP+FN}$$
F1値(F1-measure):適合率と再現率の調和平均。
$$F1値=\frac{2∗precision∗recall}{precision+recall}=\frac{2∗TP}{2∗TP+FP+FN}$$
from sklearn.metrics import classification_report
# 学習データでの予測
y_true = list(y_train)
y_pred = model.predict(X_train)
print("---学習データ---")
print(classification_report(y_true, y_pred, target_names=['b', 'e', 'm', 't']))
# 検証データでの予測
y_true = list(y_valid)
y_pred = model.predict(X_valid)
print("---検証データ---")
print(classification_report(y_true, y_pred, target_names=['b', 'e', 'm', 't']))
# 評価データでの予測
y_true = list(y_test)
y_pred = model.predict(X_test)
print("---評価データ---")
print(classification_report(y_true, y_pred, target_names=['b', 'e', 'm', 't']))
<出力>
---学習データ---
precision recall f1-score support
b 0.93 0.97 0.95 4501
e 0.92 0.99 0.95 4235
m 0.97 0.70 0.81 728
t 0.93 0.73 0.82 1220
accuracy 0.93 10684
macro avg 0.94 0.84 0.88 10684
weighted avg 0.93 0.93 0.93 10684
---検証データ---
precision recall f1-score support
b 0.89 0.93 0.91 563
e 0.87 0.97 0.92 529
m 0.94 0.51 0.66 91
t 0.86 0.60 0.71 153
accuracy 0.88 1336
macro avg 0.89 0.75 0.80 1336
weighted avg 0.88 0.88 0.87 1336
---評価データ---
precision recall f1-score support
b 0.88 0.93 0.91 563
e 0.89 0.97 0.93 530
m 0.91 0.55 0.68 91
t 0.84 0.59 0.69 152
accuracy 0.88 1336
macro avg 0.88 0.76 0.80 1336
weighted avg 0.88 0.88 0.88 1336
上記リストでマイクロ平均が表示されないのは、
accuracyと同じだからとのことです。
scikit-learnドキュメント参照
57. 特徴量の重みの確認
52で学習したロジスティック回帰モデルの中で,重みの高い特徴量トップ10と,重みの低い特徴量トップ10を確認せよ.
ロジスティック回帰モデルは、以下の式で与えられる。
$$\hat{y} = sigmoid(Wx+b)\qquad sigmoid:シグモイド関数\quad sigmoid(x) = \frac{1}{1+e^{-ax}}$$
scikit-learnのロジスティック回帰モデルでは、
Wは、model.coef_
bは、model.intercept_
で取得できる。
ここでは、Wの値をxの各パラメータが示す単語と関連付け、各カテゴリに関連度の高い単語と関連度の低い単語を表示させます。
# 重み:model.coef_を単語と関連付けるため、DataFrameを作成
# indexに入力データのカラム名を指定
df = pd.DataFrame(model.coef_.T, index=X_test.columns, columns=['b','e','m','t'])
print("----- Category : b -----")
df = df.sort_values(by='b', ascending=False)
print("[Upper]")
print(df["b"].head(10))
df = df.sort_values(by='b', ascending=True)
print("[Lower]")
print(df["b"].head(10))
print("----- Category : e -----")
df = df.sort_values(by='e', ascending=False)
print("[Upper]")
print(df["e"].head(10))
df = df.sort_values(by='e', ascending=True)
print("[Lower]")
print(df["e"].head(10))
print("----- Category : m -----")
df = df.sort_values(by='m', ascending=False)
print("[Upper]")
print(df["m"].head(10))
df = df.sort_values(by='m', ascending=True)
print("[Lower]")
print(df["m"].head(10))
print("----- Category : t -----")
df = df.sort_values(by='t', ascending=False)
print("[Upper]")
print(df["t"].head(10))
df = df.sort_values(by='t', ascending=True)
print("[Lower]")
print(df["t"].head(10))
<出力>
----- Category : b -----
[Upper]
fed 3.358672
bank 3.229622
china 3.109381
ecb 2.943295
stocks 2.928296
ukraine 2.799545
euro 2.714916
oil 2.567146
obamacare 2.500662
dollar 2.454107
Name: b, dtype: float64
[Lower]
and -1.949340
ebola -1.895492
her -1.856912
video -1.853030
star -1.692275
she -1.685700
tv -1.653755
study -1.648119
apple -1.643195
drug -1.617061
Name: b, dtype: float64
----- Category : e -----
[Upper]
kardashian 2.731792
chris 2.550129
movie 2.296004
she 2.212502
star 2.202562
film 2.160577
kim 2.142372
her 2.085134
jay 2.038762
wedding 1.938343
Name: e, dtype: float64
[Lower]
update -3.355646
us -3.041079
google -2.790797
china -2.344791
gm -2.251161
says -2.051933
study -2.020950
billion -1.931296
apple -1.912641
ceo -1.863020
Name: e, dtype: float64
----- Category : m -----
[Upper]
ebola 4.537120
study 3.743906
fda 3.526522
cancer 3.455659
drug 3.264976
mers 3.215792
doctors 2.446932
health 2.406093
cigarettes 2.398914
cdc 2.314434
Name: m, dtype: float64
[Lower]
gm -1.170723
ceo -1.073153
facebook -1.029891
apple -0.996284
climate -0.917664
bank -0.894374
google -0.840370
twitter -0.785872
deal -0.777956
sales -0.777750
Name: m, dtype: float64
----- Category : t -----
[Upper]
google 5.137037
apple 4.552119
facebook 4.430197
microsoft 3.933355
climate 3.727243
gm 3.193906
tesla 3.015237
nasa 2.809753
heartbleed 2.590798
comcast 2.512452
Name: t, dtype: float64
[Lower]
stocks -1.244381
fed -1.179348
men -1.092977
her -1.087400
american -1.061092
drug -1.046460
cancer -1.034589
percent -1.025405
shares -1.015857
ukraine -0.973475
Name: t, dtype: float64
カテゴリ分類は、以下の通りです。
b:ビジネス
e:エンターテイメント
m:健康
t:テクノロジー
出力結果が妥当であることがわかりました。
58. 正則化パラメータの変更
ロジスティック回帰モデルを学習するとき,正則化パラメータを調整することで,学習時の過学習(overfitting)の度合いを制御できる.異なる正則化パラメータでロジスティック回帰モデルを学習し,学習データ,検証データ,および評価データ上の正解率を求めよ.実験の結果は,正則化パラメータを横軸,正解率を縦軸としたグラフにまとめよ.
scikit-learnのロジスティック回帰では、初期値としてL2正則化が有効になっています。
正則化パラメータをなくしたところ、以下の結果となりました。
データ | L2正則化あり時正解率 | 正則化なし時正解率 |
---|---|---|
正解率(学習データ) | 0.928117 | 0.998877 |
正解率(検証データ) | 0.878743 | 0.879491 |
正解率(評価データ) | 0.882485 | 0.861527 |
正則化項がない方が学習データでの正解率は高いですが、
評価データでは若干正解率が下がっています。
L2正則化によって過学習が抑制できているようです。
L2正則化のパラメータを変更したときの正解率をグラフ化しました。
scikit-learnのロジスティック回帰モデルでは、パラメータ:Cが正則化パラメータになります。
from sklearn.linear_model import LogisticRegression
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
#データ準備
data1 = pd.read_csv("/content/drive/My Drive/NL100/data/train.feature.csv", sep='\t')
data2 = pd.read_csv("/content/drive/My Drive/NL100/data/valid.feature.csv", sep='\t')
data3 = pd.read_csv("/content/drive/My Drive/NL100/data/test.feature.csv", sep='\t')
y_train = data1["CATEGORY"]
X_train = data1.drop(["TITLE","CATEGORY"],axis=1)
y_valid = data2["CATEGORY"]
X_valid = data2.drop(["TITLE","CATEGORY"],axis=1)
y_test = data3["CATEGORY"]
X_test = data3.drop(["TITLE","CATEGORY"],axis=1)
prmC = np.array([])
acc_train=np.array([])
acc_valid=np.array([])
acc_test=np.array([])
for i in np.arange(10):
# modelの定義(ロジスティック回帰)
model = LogisticRegression(C=(0.5+0.1*i),random_state=123, max_iter=10000)
#
model.fit(X_train, y_train)
#
prmC = np.append(prmC,(0.5+0.1*i))
#print(prmC)
acc_train = np.append(acc_train,model.score(X_train, y_train))
acc_valid = np.append(acc_valid,model.score(X_valid, y_valid))
acc_test = np.append(acc_test,model.score(X_test, y_test))
plt.bar(prmC, acc_train, color='g', label="train")
plt.bar(prmC, acc_valid, color='r', label="valid")
plt.bar(prmC, acc_test, color='b', label="test")
plt.legend(loc=2)
plt.ylim(0.8,1)
plt.ylabel("accuracy")
plt.xlabel("parameter C")
plt.show()
<出力>
Cのデフォルト値は1です。この演習では、Cが0~1に近づくにつれ正解率が上昇し、1以上では頭打ちになっています。
59. ハイパーパラメータの探索
学習アルゴリズムや学習パラメータを変えながら,カテゴリ分類モデルを学習せよ.検証データ上の正解率が最も高くなる学習アルゴリズム・パラメータを求めよ.また,その学習アルゴリズム・パラメータを用いたときの評価データ上の正解率を求めよ.
52,54のプログラムをパラメータを変更して実行しました。
modelの各パラメータが以下のときの結果です。
C=7, random_state=123, max_iter=10000, multi_class="multinomial"
正解率(学習データ):0.979315
正解率(検証データ):0.895210
正解率(評価データ):0.889222