言語処理100本ノック 2015年版 (74~77)
74. 予測
73で学習したロジスティック回帰モデルを用い,与えられた文の極性ラベル(正例なら"+1",負例なら"-1")と,その予測確率を計算するプログラムを実装せよ.
今度は重みベクトルを使って文の極性ラベルを計算していきます。
与えられた文は74test.txtに書き込んでおきます。
#!/usr/bin/env python import codecs import re import collections import math from nltk import stem fsen = codecs.open('72.txt', 'r', 'latin_1') stopwords = [] eta0 = 0.66 etan = 0.9999 guard = 0.0002 def checkstopwords(word,stopwords): return True if word in stopwords else False 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 if __name__ == "__main__": #重みベクトル作成 t = 0 W = collections.defaultdict(float) for line in fsen: features = line[:-1].split(" ") update(W, features[1:], float(features[0]), eta0 * ( etan **t)) t += 1 features = [] #ストップワード読み込み fstop = codecs.open('stoplist.txt', 'r', 'latin_1') stopwords = [ line[:-1] for line in fstop] #比較文読み込み fin = codecs.open('74test.txt', 'r', 'latin_1') lemmatizer = stem.WordNetLemmatizer() for line in fin: string = re.compile(r'[,.:;\s]').split(line) for word in string: if not checkstopwords(word,stopwords): feature = lemmatizer.lemmatize(word) features.append(feature) print(features[1:]) a = sum([W[x] for x in features[1:]]) predict = sigmoid(a) predict = (predict * 2) - 1 if predict > 0: predictlabel = "+1" elif predict < 0: predictlabel = "-1" else: predictlabel = "0" print("label",predictlabel,"\tpredict:",predict)
結果
['campy', 'result', 'mel', "brooks'", 'borscht', 'belt', 'schtick', 'look', 'sophisticated'] label -1 predict: -0.6651139676000253
75. 素性の重み
73で学習したロジスティック回帰モデルの中で,重みの高い素性トップ10と,重みの低い素性トップ10を確認せよ.
unixコマンドを使います。
cut -f1,2 73.txt | sort -k2 -n -r > 73sort.txt
重みの高い素性トップ10
enjoyable 2.6169537969272794
powerful 2.3787951567849857
wonderful 2.252616193768806
help 2.2161462435618797
entertaining 2.1012476394052886
unexpected 2.06721146103483
capture 2.0487298445584528
provides 2.007309645804019
engrossing 1.9273056982929437
our 1.8582726739138051
重みの低い素性トップ10
fails -2.8814245455525906
dull -2.832095888121216
worst -2.356167001806389
boring -2.3536406580509825
none -2.033909838886688
flat -2.013097610029052
video -1.8959064537156256
badly -1.8830296620760196
lack -1.8803267135777417
単語を見る限りいい感じで重みが付いているようです。
それにしては videoが低いな……
ラベル数を見ると、pos:21 vs neg:72 なので評価的には正しいですね。このコーパスではvideoはネガティブな評価と結びついていることが多いということです。
76. ラベル付け
学習データに対してロジスティック回帰モデルを適用し,正解のラベル,予測されたラベル,予測確率をタブ区切り形式で出力せよ.
#!/usr/bin/env python import codecs import re import collections import math fsen = codecs.open('72.txt', 'r', 'latin_1') 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 if __name__ == "__main__": t = 0 W = collections.defaultdict(float) n = 0 for line in fsen: features = line[:-1].split(" ") update(W, features[1:], float(features[0]), eta0 * ( etan **t)) t += 1 n += 1 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" print(features[0],"\t",predictlabel,"\t",predict)
結果
+1 -1 -0.45434815960064157 +1 +1 0.9839176217919916 -1 -1 -0.0724876620476631 +1 +1 0.9204549983820602 +1 +1 0.9008372084478018 +1 +1 0.9298453364568531 -1 -1 -0.7628898147320603 +1 +1 0.8194284936043321 (略)
しょっぱなから予測を間違ってるのが泣ける。
77. 正解率の計測
76の出力を受け取り,予測の正解率,正例に関する適合率,再現率,F1スコアを求めるプログラムを作成せよ.
ややこしいですが、
正例に関する適合率: 正例を正例と予測できた数 / 正例と予測した数
再現率: 正例を正例と予測できた数 / 実際の正例の数
F1スコア 適合率と再現率の調和平均
( 2 * 適合率 * 再現率 ) / ( 適合率 + 再現率 )
となります。
#!/usr/bin/env python import codecs import re import collections import math fsen = codecs.open('72.txt', 'r', 'latin_1') 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 if __name__ == "__main__": t = 0 W = collections.defaultdict(float) for line in fsen: features = line[:-1].split(" ") update(W, features[1:], float(features[0]), eta0 * ( etan **t)) t += 1 countp = 0 countn = 0 zero = 0 good = 0 bad = 0 exapos = 0 trupos = 0 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" 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 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)
結果
p: 5425 n: 5270 good: 9640 bad: 1055 accuracy_rate: 0.9013557737260403 precision: 0.895483870967742 recall: 0.9087167976056865 f1: 0.9020518057747655
一旦ここで区切ります。