北野坂備忘録

主にインストールやプログラミングのメモを載せています。

言語処理100本ノック 2015年版 (78,79)

78. 5分割交差検定

76-77の実験では,学習に用いた事例を評価にも用いたため,正当な評価とは言えない.すなわち,分類器が訓練事例を丸暗記する際の性能を評価しており,モデルの汎化性能を測定していない.そこで,5分割交差検定により,極性分類の正解率,適合率,再現率,F1スコアを求めよ.

5分割交差検定とは、学習データを5つに分割し、1つをテスト例、残り4つを学習データとして5回分類器を作成してはテストする方法です。最終的に平均を取ります。 
見出し語化したデータは10695行存在するので、これを5で割ると2139行となります。

#!/usr/bin/env python

import codecs
import re
import collections
import math

eta0 = 0.66
etan = 0.9999
guard = 0.0002

def sigmoid(x):
  return 1.0 / (1.0 + math.exp(-x))

def update(W, features, label, eta):
  a = sum([W[x] for x in features])
  init_feature = 1 
  predict = sigmoid(a)
  label = ( label + 1) / 2

  for x in features:
    dif = eta * ( predict -label ) * init_feature
    if (W[x] - dif) > guard or ( W[x] - dif) < (guard * -1):
      W[x] = W[x] - dif

def cross_validation(datalist,testlist):
  t = 0
  W = collections.defaultdict(float)

  fsen = codecs.open('72.txt', 'r', 'latin_1')
  n = 1
  for line in fsen:
    if n in datalist:
      features = line[:-1].split(" ")
      update(W, features[1:], float(features[0]), eta0 * ( etan **t))
      t += 1
    n += 1

  n = 1
  countp = 0
  countn = 0
  good   = 0
  bad    = 0
  exapos = 0
  trupos = 0
  fsen = codecs.open('72.txt', 'r', 'latin_1')
  for line in fsen:
    if n in testlist:
      features = line[:-1].split(" ")
      a = sum([W[x] for x in features[1:]])
      predict = sigmoid(a)
      predict = (predict * 2) - 1
      predictlabel = "+1" if predict > 0 else "-1"  
#      print(features[0],"\t",predictlabel,"\t",predict)
# ポジネガ生成数
      if predictlabel == "+1":
        countp += 1
      else:
        countn += 1
# 正答率
      if features[0] == predictlabel :
        good += 1
      else :
        bad += 1
# 正例に関する適合率
      if features[0] == "+1" and predictlabel == "+1" :
        exapos += 1
# 正例の数
      if features[0] == "+1":
        trupos += 1
    n += 1

  accuracy_rate = good / ( good + bad)
  precision = exapos / countp
  recall = exapos / trupos
  f1 = ( 2 * precision * recall ) / ( precision + recall ) 

  return [ accuracy_rate , precision , recall , f1 ]

if __name__ == "__main__":
  linetotal = 10695
  totallist = range(1,linetotal)
  resultlist = []
  
  for x in range(5):
    xstart = x * ( linetotal / 5) + 1
    xend = (x + 1) * ( linetotal / 5 )
    datalist = range( int(xstart)  , int(xend) )
    testlist = list(set(totallist)-set(datalist))
    resultlist.append(cross_validation(datalist,testlist))

  accuracy_rate = 0
  precision = 0
  recall = 0
  f1 = 0
  for result in resultlist:
    accuracy_rate += result[0]
    precision += result[1]
    recall += result[2]
    f1 += result[3]
 
  print("accuracy_rate:", accuracy_rate / 5 )
  print("precision:", precision / 5 )
  print("recall:", recall / 5 )
  print("f1:", f1 / 5 )
結果
accuracy_rate: 0.6725806451612903
precision: 0.672900104233187
recall: 0.6719448077506203
f1: 0.6721992196647257

79. 適合率-再現率グラフの描画

ロジスティック回帰モデルの分類の閾値を変化させることで,適合率-再現率グラフを描画せよ.

普通の「適合率-再現率グラフ」といえば検索結果の順位付けによる適合率と再現率をそれぞれ軸としたプロットですが、「閾値の変化」だとちょっと違います。少しずつ閾値を変化させていくことで適合率と再現率の変動を見ていくグラフのことで、「適合率-再現率グラフ」よりも「適合率&再現率グラフ」のほうがわかりやすいでしょう。
今回の場合であれば予想値が0付近のものは正例か負例か怪しいということで、こちらを削除すれば当たる率は上昇していきます。
ベースとして77を使うか78を使うか。78のほうが正当性は高いが5倍の時間がかかります。とりあえず今回は77をベースに作成してみます。

#!/usr/bin/env python

import codecs
import re
import collections
import math
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm

eta0 = 0.66
etan = 0.9999
guard = 0.0002

def sigmoid(x):
  return 1.0 / (1.0 + math.exp(-x))

def update(W, features, label, eta):
  a = sum([W[x] for x in features])
  init_feature = 1 
  predict = sigmoid(a)
  label = ( label + 1) / 2

  for x in features:
    dif = eta * ( predict -label ) * init_feature
    if (W[x] - dif) > guard or ( W[x] - dif) < (guard * -1):
      W[x] = W[x] - dif

def logistic_regression(threshold):
  t = 0
  W = collections.defaultdict(float)

  fsen = codecs.open('72.txt', 'r', 'latin_1')
  n = 0
  for line in fsen:
    
    features = line[:-1].split(" ")
    update(W, features[1:], float(features[0]), eta0 * ( etan **t))
    t += 1
    n += 1

  n = 0
  countp = 0
  countn = 0
  zero   = 0
  good   = 0
  bad    = 0
  exapos = 0
  trupos = 0
  ts     = threshold
  fsen = codecs.open('72.txt', 'r', 'latin_1')
  for line in fsen:
    features = line[:-1].split(" ")
    a = sum([W[x] for x in features[1:]])
    predict = sigmoid(a)
    predict = (predict * 2) - 1
    predictlabel = "+1" if predict > 0 else "-1"
    if predict > ts:
      predictlabel = "+1"
    elif predict < ts * -1:
      predictlabel = "-1"
    else:
      predictlabel = "0"  
    print(features[0],"\t",predictlabel,"\t",predict)
# ポジネガ生成数
    if predictlabel == "+1":
      countp += 1
    elif predictlabel == "-1":
      countn += 1
    else:
      zero += 1
# 正答率
    if features[0] == predictlabel :
      good += 1
    elif predictlabel == "0":
      pass
    else :
      bad += 1
# 正例に関する適合率
    if features[0] == "+1" and predictlabel == "+1" :
      exapos += 1
# 正例の数
    if features[0] == "+1":
      trupos += 1

    n += 1

  accuracy_rate = good / ( good + bad)
  precision = exapos / countp
  recall = exapos / trupos
  f1 = ( 2 * precision * recall ) / ( precision + recall ) 
  print("p:",countp," n:",countn)
  print("good:",good," bad:",bad)
  print("accuracy_rate:", accuracy_rate )
  print("precision:", precision )
  print("recall:", recall )
  print("f1:", f1)
  return(ts,precision,recall,exapos,trupos)

if __name__ == "__main__":
  
  resultlist = [logistic_regression(float(threshold)) for threshold in np.arange(0,1,0.1)]
  print(resultlist)

  threshold = [round(x[0],1) for x in resultlist ]
  precision = [x[1] for x in resultlist ]
  recall = [x[2]for x in resultlist ]

  plt.plot(threshold, precision, label = "precision", color = "red")
  plt.plot(threshold, recall, label = "recall", color = "blue")

  plt.xlabel("threshold")
  plt.ylabel("rate")
  plt.xlim(-0.05 , 0.95)
  plt.ylim(0,1)
  plt.title("logistic_regresssion")
  plt.legend(loc = 3)

  plt.show()
結果のグラフ

f:id:kenichia:20160223115221p:plain
正例に関する適合率: 正例を正例と予測できた数 / 正例と予測した数
再現率: 正例を正例と予測できた数 /  実際の正例の数
 閾値をあげると、正例を正例と予測できた数は減っていきます。ただし、正例と予測した数も減っていきます。そして、予測を外す可能性は減っていくので、適合率は上がっていきます。
 しかし、再現率はどんどん下がってしまいます。