学習者言語の分析(基礎)2(第6回)
In [1]:
# 使用するパッケージのimport
import os
import numpy as np
from scipy.stats import mode
import pandas as pd
from nltk import word_tokenize,sent_tokenize
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
from sklearn.naive_bayes import GaussianNB
from sklearn.naive_bayes import MultinomialNB
from sklearn.neighbors import KNeighborsClassifier
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
import spacy
from spacy import displacy
nlp = spacy.load("en_core_web_sm")
#nlp = spacy.load("/opt/homebrew/lib/python3.9/site-packages/en_core_web_sm/en_core_web_sm-3.5.0/")
6.1 特徴量¶
- これまでの授業で扱った特徴量は以下です。
- 総文数
- 総語数
- 1文あたりの平均語数
- 単語の出現回数に基づいたベクトル表現
- 依存文法に基づいた文法項目に関する特徴量
- 品詞の頻度に基づいた特徴量
- 語彙の多様性に関する特徴量
- 語彙の洗練度に関する特徴量
- これまでの練習問題で使用したコーパスを用いて特徴量を抽出してみましょう。
In [2]:
# 対象とするテキストファイルの読み込み
fnames = os.listdir("../DATA02/NICE_NNS2/")
## ファイル名をソート ##
fnames.sort()
T = []
for i in fnames:
f = open("../DATA02/NICE_NNS2/"+i,"r")
text = f.read()
f.close()
T.append(text)
- 以下の関数を用いて1から4の特徴量を抽出します。
In [3]:
def feature_count(X):
s = len(sent_tokenize(X))
tokens = word_tokenize(X)
w = len(tokens)
types = len(list(set(tokens)))
ttr = types / w
wps = w / s
F = [s,w,ttr,wps]
return F
In [4]:
Features_1 = []
for i in T:
j = feature_count(i)
Features_1.append(j)
- 総文数、総語数、Type token ratio(異なり語の割合)、1文あたりの平均語数の順にリストになっています。
In [5]:
Features_1[0]
Out[5]:
[25, 392, 0.44387755102040816, 15.68]
- 「5. 単語の出現回数に基づいたベクトル表現」に関しては、あとで抽出します。
- の「1文あたりの節数」と「名詞句の平均語数」を抽出します。
In [6]:
CLAUSE = ["advcl","relcl","ccomp","csubj"]
CpS = []
for i in T:
c = 0
sents = sent_tokenize(i)
s = len(sents)
for j in sents:
doc = nlp(j)
for token in doc:
if token.dep_ in CLAUSE:
c +=1
CpS.append(c/s)
In [7]:
NL = []
for i in T:
tmp = []
sents = sent_tokenize(i)
for j in sents:
doc = nlp(j)
for chunk in doc.noun_chunks:
tmp.append(len(chunk.text.split()))
NL.append(sum(tmp)/len(tmp))
- 総文数、総語数、Type token ratio(異なり語の割合)、1文あたりの平均語数、1文あたりの節数、名詞句の平均語数、文法的誤りが含まれない文の割合をひとつのリストにまとめます。
In [8]:
X = []
for i,j,k in zip(Features_1,CpS,NL):
x = i + [j] + [k]
X.append(x)
- 総文数、総語数、Type token ratio(異なり語の割合)、1文あたりの平均語数、1文あたりの節数、名詞句の平均語数、文法的誤りが含まれない文の割合の順にリストに保存されています。
In [9]:
X[0]
Out[9]:
[25, 392, 0.44387755102040816, 15.68, 0.28, 1.8144329896907216]
6.2 特徴量選択¶
- これまでの授業では特徴量の抽出→自動採点の流れにおいて特徴量の選択に関して検討してきませんでした。
- どの特徴量を予測に使用するかを検討することで、自動採点の解釈可能性が上がる場合があります。
- 自動採点においてスコアを予測する特徴量を選択する方法はいくつかありますが、ここでは、フィルター法の一部を取り入れて特徴量を選択します。
- ここでは、以下を検討における指標とします。
- 変動係数
- スコアと特徴量の相関係数
- 特徴量同士の相関係数
6.2.1 変動係数¶
- 特徴量にある程度ばらつきがないとスコアの予測には使用できません。
- 極端な例として、すべてのデータが1であるものは予測に使えません。
- すべてのデータが同じ値でなくてもばらつきが小さいデータは予測に有用ではありません。
- 標準偏差というデータのばらつきを示す統計量がありますが、これは、データがとりうる値によって大きく変わります。
- そのため、標準偏差を平均値で割って平均値に対するデータとばらつきの関係を相対的に評価する統計量を用います。
- 変動係数を用いることでデータ同士のばらつきを比較することができます。
In [10]:
# スコアの読み込み
Eva = pd.read_csv("../DATA02/nice_evaluation.csv",index_col=0)
Y = list(Eva["score"])
In [11]:
# 上でリストに保存した特徴量をデータフレームに保存
data = pd.DataFrame(X,columns=["sents","words","TTR","WPS","CPS","NPL"])
data.head()
Out[11]:
sents | words | TTR | WPS | CPS | NPL | |
---|---|---|---|---|---|---|
0 | 25 | 392 | 0.443878 | 15.680000 | 0.280000 | 1.814433 |
1 | 18 | 288 | 0.350694 | 16.000000 | 0.611111 | 1.931507 |
2 | 26 | 548 | 0.452555 | 21.076923 | 0.884615 | 1.784722 |
3 | 21 | 332 | 0.400602 | 15.809524 | 0.809524 | 1.365385 |
4 | 18 | 391 | 0.493606 | 21.722222 | 1.166667 | 1.690000 |
- 変動係数(CV)は以下で求められます。
$$CV = \frac{標準偏差}{平均}$$
In [12]:
# それぞれの特徴量の変動係数を計算
CV = []
for i in data.columns:
cv = data[i].std()/data[i].mean()
CV.append(cv)
In [13]:
# 算出した変動係数をデータフレームに保存
data_cv = pd.DataFrame([CV],columns=data.columns,index=["CV"])
data_cv
Out[13]:
sents | words | TTR | WPS | CPS | NPL | |
---|---|---|---|---|---|---|
CV | 0.373414 | 0.37209 | 0.14136 | 0.258507 | 0.434414 | 0.101353 |
- 名詞句の平均語数(NPL)の変動係数が最も小さい。
6.2.2 スコアとの相関¶
- 特徴量とスコアとの相関係数を計算します。
- この相関係数が低い特徴量はスコアの予測には寄与しない可能性が高いです。
In [14]:
CC = []
for i in data.columns:
cc = np.corrcoef(Y,data[i].values)[0][1]
CC.append(cc)
In [15]:
data_cc = pd.DataFrame([CC],columns=data.columns,index=["CC"])
data_cc
Out[15]:
sents | words | TTR | WPS | CPS | NPL | |
---|---|---|---|---|---|---|
CC | 0.282074 | 0.697879 | -0.301265 | 0.570192 | 0.36007 | 0.289299 |
- スコアと総文数との相関係数が最も低い。
6.2.3 特徴量同士の相関係数¶
- 抽出した特徴量は「作文における英語の能力」の部分と仮定しているので、他の特徴量との相関係数が低い特徴量はこの「作文における英語の能力」とは関係ない能力に関する特徴量であるか、あるいは、抽出がうまくいってない可能性があります。
In [16]:
data.corr()
Out[16]:
sents | words | TTR | WPS | CPS | NPL | |
---|---|---|---|---|---|---|
sents | 1.000000 | 0.714656 | -0.566967 | -0.290585 | -0.278658 | -0.086715 |
words | 0.714656 | 1.000000 | -0.581803 | 0.417961 | 0.245815 | 0.237285 |
TTR | -0.566967 | -0.581803 | 1.000000 | -0.083262 | -0.185807 | -0.001388 |
WPS | -0.290585 | 0.417961 | -0.083262 | 1.000000 | 0.714217 | 0.445935 |
CPS | -0.278658 | 0.245815 | -0.185807 | 0.714217 | 1.000000 | 0.017773 |
NPL | -0.086715 | 0.237285 | -0.001388 | 0.445935 | 0.017773 | 1.000000 |
- 以下では、データフレームにスコアを追加し、スコアごとにプロットしています。
In [17]:
data_corr = data
data_corr["score"] = Y
data_corr.head()
Out[17]:
sents | words | TTR | WPS | CPS | NPL | score | |
---|---|---|---|---|---|---|---|
0 | 25 | 392 | 0.443878 | 15.680000 | 0.280000 | 1.814433 | 3 |
1 | 18 | 288 | 0.350694 | 16.000000 | 0.611111 | 1.931507 | 3 |
2 | 26 | 548 | 0.452555 | 21.076923 | 0.884615 | 1.784722 | 5 |
3 | 21 | 332 | 0.400602 | 15.809524 | 0.809524 | 1.365385 | 3 |
4 | 18 | 391 | 0.493606 | 21.722222 | 1.166667 | 1.690000 | 3 |
In [18]:
sns.pairplot(data_corr,hue="score",plot_kws=dict(alpha=0.4),palette="viridis")
Out[18]:
<seaborn.axisgrid.PairGrid at 0x30173f790>
6.3 機械学習による予測¶
- 授業で扱った機械学習の手法は以下の3種類です。
- knn法
- ナイーブベイズ分類器(MultinomialNB)
- ナイーブベイズ分類器(GaussianNB)
- これら3つの手法を用いて、スコアを予測する自動採点システムを構築します。
- 単語の頻度(Bag-of-words model)に基づいて作文を数値列(ベクトル)で表現し、 ナイーブベイズ分類器(MultinomialNB)を用いて自動採点システムを構築します。
In [46]:
# データの分割
X_text_train,X_text_test,y_train,y_test = train_test_split(T,Y,test_size=0.2)
In [47]:
# 作文のベクトル化
vectorizer = CountVectorizer(min_df=1,ngram_range=(2,2))
vectorizer.fit(X_text_train)
X_train_wordfreq = vectorizer.transform(X_text_train).toarray()
X_test_wordfreq = vectorizer.transform(X_text_test).toarray()
In [48]:
# ナイーブベイズ分類器のクラス
from sklearn.naive_bayes import MultinomialNB
# インスタンスの生成
nbc_m = MultinomialNB()
# トレーニングデータでモデル構築
nbc_m.fit(X_train_wordfreq,y_train)
# テストデータの評価を予測して、予測精度の出力
nbc_m.score(X_test_wordfreq,y_test)
/Users/yusukekondo/Library/Python/3.9/lib/python/site-packages/sklearn/utils/extmath.py:203: RuntimeWarning: divide by zero encountered in matmul ret = a @ b /Users/yusukekondo/Library/Python/3.9/lib/python/site-packages/sklearn/utils/extmath.py:203: RuntimeWarning: overflow encountered in matmul ret = a @ b /Users/yusukekondo/Library/Python/3.9/lib/python/site-packages/sklearn/utils/extmath.py:203: RuntimeWarning: invalid value encountered in matmul ret = a @ b
Out[48]:
0.44
- 上と同じ条件で文数などの特徴量を用いてknn法およびナイーブベイズ分類器(GaussianNB)を用いて自動採点システムを構築するため、特徴量を抽出しなおします。
In [22]:
X_train_count = []
for i in X_text_train:
j = feature_count(i)
X_train_count.append(j)
X_test_count = []
for i in X_text_test:
j = feature_count(i)
X_test_count.append(j)
In [23]:
CLAUSE = ["advcl","relcl","ccomp","csubj"]
X_train_CPS = []
for i in X_text_train:
c = 0
sents = sent_tokenize(i)
s = len(sents)
for j in sents:
doc = nlp(j)
for token in doc:
if token.dep_ in CLAUSE:
c +=1
X_train_CPS.append(c/s)
X_test_CPS = []
for i in X_text_test:
c = 0
sents = sent_tokenize(i)
s = len(sents)
for j in sents:
doc = nlp(j)
for token in doc:
if token.dep_ in CLAUSE:
c +=1
X_test_CPS.append(c/s)
In [24]:
X_train_NL = []
for i in X_text_train:
tmp = []
sents = sent_tokenize(i)
for j in sents:
doc = nlp(j)
for chunk in doc.noun_chunks:
tmp.append(len(chunk.text.split()))
X_train_NL.append(sum(tmp)/len(tmp))
X_test_NL = []
for i in X_text_test:
tmp = []
sents = sent_tokenize(i)
for j in sents:
doc = nlp(j)
for chunk in doc.noun_chunks:
tmp.append(len(chunk.text.split()))
X_test_NL.append(sum(tmp)/len(tmp))
In [25]:
# 特徴量をまとめる
X_train = []
for i,j,k in zip(X_train_count,X_train_CPS,X_train_NL):
x = i + [j] + [k]
X_train.append(x)
X_test = []
for i,j,k in zip(X_test_count,X_test_CPS,X_test_NL):
x = i + [j] + [k]
X_test.append(x)
In [26]:
# インスタンスの生成
knn = KNeighborsClassifier(n_neighbors=5)
# 学習
knn.fit(X_train,y_train)
# 精度
knn.score(X_test,y_test)
Out[26]:
0.6
In [27]:
# インスタンスの生成
nbc_g = GaussianNB()
# 学習
nbc_g.fit(X_train,y_train)
# 精度
nbc_g.score(X_test,y_test)
Out[27]:
0.4
- 「三人寄れば文殊の知恵」と言いますので、3つの手法で予測した結果を用いて多数決でスコアを算出してみましょう。
In [28]:
# それぞれの手法の予測スコアを保存
K = knn.predict(X_test)
G = nbc_g.predict(X_test)
M = nbc_m.predict(X_test_wordfreq)
/Users/yusukekondo/Library/Python/3.9/lib/python/site-packages/sklearn/utils/extmath.py:203: RuntimeWarning: divide by zero encountered in matmul ret = a @ b /Users/yusukekondo/Library/Python/3.9/lib/python/site-packages/sklearn/utils/extmath.py:203: RuntimeWarning: overflow encountered in matmul ret = a @ b /Users/yusukekondo/Library/Python/3.9/lib/python/site-packages/sklearn/utils/extmath.py:203: RuntimeWarning: invalid value encountered in matmul ret = a @ b
In [36]:
# from scipy.stats import modeを用いて最頻値を取得
P = []
for i,j,k in zip(G,K,M):
tmp = [i,j,k]
m,l = mode(tmp)
P.append(m)
In [37]:
# 一致度を算出
n = len(P)
c = 0
for i,j in zip(P,y_test):
if i == j:
c += 1
c/n
Out[37]:
0.52
In [38]:
cm = confusion_matrix(y_test,K)
sns.heatmap(cm,annot=True,cmap="Blues")
Out[38]:
<Axes: >
In [39]:
cm = confusion_matrix(y_test,P)
sns.heatmap(cm,annot=True,cmap="Blues")
Out[39]:
<Axes: >