koji/メガネ男の日誌

日々の学び、活動状況を記録します。仕事のことは少なめ。

GoとPythonを比較してみた

f:id:kj_man666:20200927111630j:plain

Goのハンズオンに参加してGoをざっと学びましたので、多少かじっているPythonと比較してみました。

techplay.jp

今回だけでは終わり切らないので、引き続き比較を続けようと思います。

なお、コードはWindows環境を前提としております。

今回のコードはこちら

プログラムの実行

Go

# コンパイル
go build プログラム名.go

# exeファイルの実行
.\プログラム名.exe

Python

python3 プログラム名.py

型の指定と出力、型確認

Go

import (
    "fmt"
    "reflect"
)

func main() {
    var num int = 42
    fmt.Println(num)
    fmt.Println(reflect.TypeOf(num))
}

【出力】
42
int

Python

num = 42
print(num)
print(type(num))

【出力】
42

Print関数

Go

package main

import (
    "fmt"
)

func main() {
// 改行あり
    fmt.Println(42)
    fmt.Println(42)

// 改行なし
    fmt.Print(42)
    fmt.Print(42)
}

【出力】
42
42
4242

Python

# 改行あり
print(42)
print(42)

# 改行なし
print(42, end="")
print(42, end="")

【出力】
42
42
4242

その他 GoのPrint関数の違い qiita.com

フォーマット済み文字列

Go

package main

import (
    "fmt"
)

func main() {
    var age int = 42
    fmt.Printf("父は%d歳である\n", age)
}

【出力】
父は42歳である

Python

print()
age = 42
print("父は{}歳である".format(age))
print(f"父は{age}歳である")

【出力】
父は42歳である
父は42歳である

キーボードからの入力を取得

Go

package main

import (
    "fmt"
)

func main() {
    var key int
    fmt.Scan(&key)
    fmt.Println(key)
}

【出力】
入力によって異なるため省略

Python

key = input()
print(key)

【出力】
入力によって異なるため省略

以上になります、最後までお読みいただきありがとうございました。

効果検証入門 1章をpythonでやってみた

f:id:kj_man666:20200920201108j:plain

効果検証入門〜正しい比較のための因果推論/計量経済学の基礎の学びを兼ねて、Rで書かれたコードをpythonに置き換えてみました。

コードはこちら

書籍と数値が異なっている点もございます点ご了承ください。

間違いのご指摘等いただけると幸いです。

1章 セレクションバイアスとRCT

データセットThe MineThatData E-Mail Analytics And Data Mining Challengeのメールマーケティングデータを使用

blog.minethatdata.com

RCT(ランダム化比較実験)データの作成

# データの読込
import pandas as pd
data = pd.read_csv("http://www.minethatdata.com/Kevin_Hillstrom_MineThatData_E-MailAnalytics_DataMiningChallenge_2008.03.20.csv")

segmentカラムのWomens E-Mailを除いて Mens E-Mail と No E-Mail のみにして、女性向けメールを除いたデータセットを作成

介入(メールの配信)の有無(Mens E-Mail なら 1、そうでなければ 0)を表す treatment 変数を追加

male_data = data[data["segment"] != "Womens E-Mail"]

male_data["treatment"] = 0
male_data["treatment"].mask(male_data["segment"] == "Mens E-Mail", 1, inplace=True)

メールが配信されたグループとされなかったグループでの購入の発生確率(conversion)と購入額(spend)の平均を計算

male_data.groupby("treatment").mean()[["conversion", "spend"]]

f:id:kj_man666:20200920203115p:plain

male_data["treatment"].value_counts(sort=False)

0: 21306
1: 21307
Name: treatment, dtype: int64

男性向けメールが配信されたグループの購買データと、メールが配信されなかったグループの購買データを有意差検定

# 男性向けメールが配信されたグループの購買データ
mens_mail = male_data[male_data["treatment"] == 1]["spend"]

# メールが配信されなかったグループの購買データ
no_mail = male_data[male_data["treatment"] == 0]["spend"]

# 2群間でのt検定
from scipy import stats
stats.ttest_ind(mens_mail, no_mail, equal_var=True)

Ttest_indResult(statistic=5.300090294465472, pvalue=1.163200872605869e-07)

p値は1.16e-07と非常に小さいので有意

バイアスのあるデータの作成

  1. 「去年の購入額であるhistoryが300より小さい場合」
  2. 「最後の購入であるrecencyが3より小さい場合」
  3. 接触チャネル channelが複数あることを表すMultichannelである場合」

つまり、1. 過去に多額の購入があり、2. 直近も購入しており、3. 流入元が多ければ、購入に意欲的なユーザー

メールが配信されていないグループでは、3つの条件のどれかに該当するデータをランダムに半分選んで削除
→ メールが配信されていないグループは購入に意欲的なユーザーが半分削除される
→ メールが配信されていないグループは購入に意欲的でないバイアスがかかる

メールが配信されているグループでは、3つの条件に該当しないデータをランダムに半分選んで削除
→ メールが配信されているグループは購入に意欲的でないユーザーが半分削除される
→ メールが配信されているグループは購入に意欲的なバイアスがかかる

メールが配信されていないグループのデータを作成

# データをコピー
biased_data = male_data.copy()

# メールが配信されていないグループ
fulfill_cond_data = male_data[((male_data["treatment"] == 0) & ((male_data["history"] > 300) | (male_data["recency"] < 6) | (biased_data["channel"] == "Multichannel")))]

# 条件を満たすデータから半分抽出する
fulfill_cond_data.sample(frac=0.5, random_state=42)

# 条件を満たすデータの半分を削除する
biased_data.drop(fulfill_cond_data.sample(frac=0.5, random_state=42).index, inplace=True)

メールが配信されているグループのデータを作成

# メールが配信されているグループ
non_fulfill_cond_data = male_data[((biased_data["treatment"] == 1) & (male_data["history"] <= 300) & (male_data["recency"] >= 6) & (male_data["channel"] != "Multichannel"))]

# 条件を満さないデータから半分抽出する
non_fulfill_cond_data.sample(frac=0.5, random_state=42)

# 条件を満たさないデータの半分を削除する
biased_data.drop(non_fulfill_cond_data.sample(frac=0.5, random_state=42).index, inplace=True)

バイアスありのデータのconversion、spendの平均値とカウント数、バイアスなしのデータのconversion、spendの平均値とカウント数を比較

# バイアスありのデータ
display(biased_data.groupby("treatment").mean()[["conversion", "spend"]])

print(biased_data["treatment"].value_counts(sort=False))

# バイアスなしのデータ
print("\n比較 バイアスなしデータ")
display(male_data.groupby("treatment").mean()[["conversion", "spend"]])
print(male_data["treatment"].value_counts(sort=False))

f:id:kj_man666:20200920205208p:plain

バイアスのあるデータで、男性向けメールが配信されたグループの購買データと、メールが配信されなかったグループの購買データを有意差検定

# 男性向けメールが配信されたグループの購買データ
bias_mens_mail = biased_data[biased_data["treatment"] == 1]["spend"]

# メールが配信されなかったグループの購買データ
bias_no_mail = biased_data[biased_data["treatment"] == 0]["spend"]

display(stats.ttest_ind(bias_mens_mail, bias_no_mail, equal_var=True))

Ttest_indResult(statistic=5.283787889513689, pvalue=1.273689559022462e-07)

バイアスありの場合の p値は 1.27e-07、バイアスなしの場合の p値は 1.16e-07

バイアスがあっても p値は非常に小さく、有意という結果となってしまう

# 2章のためにcsvの出力
male_data.to_csv('male_data.csv', index=False)

biased_data.to_csv('biased_data.csv', index=False)

以上になります、最後までお読みいただきありがとうございました。

因果推論の学びメモ

f:id:kj_man666:20200913213951j:plain

因果推論を学び始めたので、勉強のためにメモします。

間違いや拙い点がございましたら、ご指摘いただけると助かります。

因果推論とは

Wikipedia(英語)によると、因果推論は、因果関係、つまり原因と結果の関係を特定するプロセス、とのことです。

en.wikipedia.org

もう少しかみ砕くと、得られた結果 A が、本当に B という原因によるものなのか、ということを追求することです。

計量経済学や医療において、経済施策が本当に効果があるのか、医療行為が本当に効果があるのかの検証に使われるようです。

なぜ因果推論が必要なのか

例えば実験で新薬を投与した場合、病気が治ったとしましょう。

その効果が新薬を飲んだことによるものなのか、その人が持つ自然治癒力によるものなのか、はたまた治療中の食事によるものなのか、厳密にはわかりません。

厳密にはわからないまま、実験でこの新薬を飲んだ人は病気が治ったことを受け、患者全員に投与したとしましょう。

万が一効果がなかったら、下手すれば重大な副作用が生じたら、大変なことになります。

その効果の原因が本当にその新薬の効果によるものなのか、検証する必要があるのです。

世の中にあふれる自己啓発は因果推論による検証は行っていないと思われます。著者が語る方法論や考え方に本当に効果があったのか、著者に才能があっただけなのか、手助けしてくれる人に恵まれたのか、運が良かったのかは何とも言えませんよね。

どうやって因果推論をするのか
  • (非現実的だが)施策や医療行為を同じ人・グループに実施した場合と、実施しなかった場合を比較する。

もし可能であれば一番良い比較なのですが、パラレルワールドを行ったり来たりでもしない限り無理です。

現実的ではありません。

  • RCT(ランダム化比較試験)により、ランダムに抽出したサンプルの片方に施策や医療行為を実施し、もう片方のサンプルに施策や医療行為を実施せず、両者を比較する。

ランダムに抽出したサンプルを比較することで、例えば年齢や家庭環境といったバイアスを除外して比較することが可能です。

ただし、これも片方の病気の患者のグループを治療しなかったり、片方の市民を経済的に有利な施策を行わない、ということは人道的に許されないため、限界があります。

  • 傾向スコアマッチング

機械学習のクラス分類で出した確率が近いものをグルーピングすることで、近い共変量同士を比較することが可能と考えられます。

これにより、共変量のバイアスの影響を除外する手法を傾向スコアマッチングというそうです。

目的変数の予測結果が近い=共変量を含む説明変数が近い と考えるのではないかと思われます。

なお、共変量と同じような用語として、交絡因子というものがあります。

共変量と交絡因子の違いがよくわからなかったのですが、「原因と結果」の経済学によると以下のように説明されています。

共変量 原因と結果でない残りすべての変数のこと

行楽因子 その共変量の中で「原因と結果の両方に影響を与えるもの」

つまり共変量の中には交絡因子であるものも、そうでないものも含まれる。

なお、傾向スコアの算出には、ロジスティック回帰が用いられることが多いそうです。

kaggleでよく使われるLightGBMや、ランダムフォレスト、サポートベクトルマシンといった手法を使うとまずいわけではないようです。

思うに、精度の高さが要求される通常の機械学習と違い、因果推論では予測結果の関係を説明しやすさを優先するためではないでしょうか。

シンプルかつランダム性を除外した学習モデルを使うことで説明変数と目的変数の予測の関係を説明しやすくなるのでしょう。

なお、参考サイトにも紹介されていますが、傾向スコアマッチングを安易に使うべきではないという論文もあるようです。

以上になります、最後までお読みいただきありがとうございました。

参考書籍

因果推論とはどういう考え方のものなのか、わかりやすく説明されています。

「原因と結果」の経済学

岩波データサイエンス Vol.3

因果推論の内容と、Rを使った因果推論の過程が説明されています。

効果検証入門〜正しい比較のための因果推論/計量経済学の基礎

因果推論の内容と、Pythonを使った因果推論の過程が説明されています。

個人的な感想ですが、効果検証入門よりもくだけて解説されています。

つくりながら学ぶ! Pythonによる因果分析 ~因果推論・因果探索の実践入門

参考サイト

因果推論の理論が非常に詳細に説明されています。

pira-nino.hatenablog.com

私は傾向スコアマッチングがどういうもので、何のためにするのかなかなかピンと来なかったのですが、このサイトは非常に細かく説明されていてわかりやすかったです。

best-biostatistics.com

公開されているデータセットをもとにした傾向スコアの算定過程が詳細に説明されています。

www.medi-08-data-06.work

傾向スコアマッチングを安易に使ってはならない、という論文が紹介されています。

analyticalsociology.hatenadiary.com

Effective Python(第2版) 2章~3章の気になった箇所をまとめてみた

f:id:kj_man666:20200906100859j:plain

引き続き、Challeng-Every-Monthというコミュニティで、みんなで Effective Python を読み合うイベントに参加しています。

Effective Pythonの公式の .py コードがGitHubに公開されています。

しばらくバタバタしていて、2章~3章はすべてのコードを詳細に見れていません。

勉強のため、気になったコード、個人的に使えそうだなあと思ったコードを啄んでいます。

手前ミソですが、第1章の記事はこちらです。

2章 項目13 アンパック

変数に * (アスタリスク)を付けてアンパックすることで、複数の変数にリストの値をまとめて代入するとき、リスト内の要素数が変数の数を上回っても代入ことができる。

# 経過年数を表すリスト
age_list = [0, 9, 4, 8, 7, 20, 19, 1, 6, 15]

# 並び替え
sorted_list = sorted(age_list, reverse=True)
sorted_list

そのまま複数の変数にまとめて値を代入すると、変数が2つしかないのにリスト内には3つ以上の数値があるため、エラーが発生する。

リストから1番目、2番目に大きい数値を抽出したい

bigest, second_bigest = sorted_list

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-14-6f07495bfa6c> in <module>
      1 # 1番目、2番目に大きいものを抽出
      2 # エラーになる
----> 3 bigest, second_bigest = sorted_list

ValueError: too many values to unpack (expected 2)

スライスで抽出することも可能だが、コードがゴチャついてしまう

bigest = sorted_list[0]

second_bigest = sorted_list[1]

print(f"bigest: {bigest}, second_bigest: {second_bigest}")

[出力] bigest: 20, second_bigest: 19

アンパックを使うと、スライスを使うよりも行数を少なく処理できる。

bigest, second_bigest, *others = sorted_list

print(f"bigest: {bigest}, second_bigest: {second_bigest}, others: {others}")

なお、アンパックのみでデータを受け取ることはできない。

*others = sorted_list

  File "<ipython-input-104-83e6b9fe5a33>", line 5
SyntaxError: starred assignment target must be in a list or tuple

辞書型のデータもアンパック可能

# 用意した辞書型データ
info = {
    'age':(20, 19, 15, 9, 8, 7, 6, 4, 1, 0),
    'type':("a", "b", "c", "d", "e", "f", "g", "h", "i", "j")
}

# アンパック処理
((key1, (value1, *value_others1)),
 (key2, (value2, *value_others2))) = info.items()

# 結果を出力
print(f"No.1 key {key1}: values {value1}, values_others {value_others1}")
print(f"No.2 key {key2}: values {value2}, values_others {value_others2}")

[出力]
key age: values 20, values_others [19, 15, 9, 8, 7, 6, 4, 1, 0]
key type: values a, values_others ['b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

イテレータをアンパックすることも可能

def generate():
    yield ("a", "b", "c")
    yield ("d", "e", "f")
    yield ("g", "h", "i")
    yield ("j", "k", "l")
it = generate()

# イテレータのアンパック
first, *others = it
print(f"first: {first}, others: {others}")

[出力] first: ('a', 'b', 'c'), others: [('d', 'e', 'f'), ('g', 'h', 'i'), ('j', 'k', 'l')]

ちなみに、スライスを使って処理することも可能

# listに変換する必要がある
it_list = list(generate())

print(f"first: {it_list[0]}, others: {it_list[1:]}")

[出力] first: ('a', 'b', 'c'), others: [('d', 'e', 'f'), ('g', 'h', 'i'), ('j', 'k', 'l')]

3章 項目22 可変長位置引数、スター引数

アンパックを使うと、代入するものが何もなくても、空のリストを自動で代入してくれる

# 1つしか要素のないリストを用意する
num_list2 = [5]

# othersに代入するリストはないので、自動的に空っぽのリストが入る
first, *others = num_list2
print(f"first: {first}, others: {others}")

[出力] first: 5, others: []

スライスを使う場合は、空のリストを指定する必要が生じる

first, others = num_list[0], []

空のリストを指定しないとエラーが生じる

first, others = num_list[0]

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-112-99477d836b96> in <module>
----> 1 first, others = num_list[0]

TypeError: cannot unpack non-iterable int object

引数であるvalue が空っぽの場合はメッセージのみを返す log()という関数を前提とし、value が空の時、どのような処理になるか確かめる

def log(message, value):

    if not value:
        print(message)
    else:
        print(message, value)

引数であるvalue に値を代入する場合

log('Number is', [1, 2])

[出力] Number is [1, 2]

引数であるvalue に値を代入しない場合、エラーが生じる

log('Number is Nothing')

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-64-3e547894f1e6> in <module>
----> 1 log('Number is Nothing')

TypeError: log() missing 1 required positional argument: 'value'

引数である value に値を代入しない場合は valueに空のリストを指定する必要がある

log('Number is Nothing', [])

[出力] Number is Nothing

引数にスター(*)を付けることで引数に代入する値がなくても自動でからのリストが代入される

def log(message, *value):
    # values が空っぽの場合はメッセージのみを返す
    if not value:
        print(message)
    else:
        print(message, value)
log('Number is Nothing')

[出力] Number is Nothing

ただし、引数にスター(*) を付けてしまうと、本来意図しない引数として認識されてしまうこともある

先ほどのlog関数に、sequenceという引数を1番目に追加し、出力にも追加する

なお、sequenceは数値データを想定している

def log2(sequence, message, *value):
    # values が空っぽの場合はメッセージのみを返す
    if not value:
        print(sequence, message)
    else:
        print(sequence,"-", value, message, )

7以降の値は引数valueにアンパックされる

log2(1, 'Number is', 7, 13)

[出力] Number is 1 - (7, 13)

引数であるsequenseになにも設定しなかった場合、本来意図しない引数に代入されてしまう

log2('Number is', 7, 13)

[出力] 7 Number is - (13,)

'Number is' が sequenseに、7 がmessageに、13 が value に格納されてしまい、出力がおかしなことになる

なぜこのような処理になるのか原因が突き止めにくいという欠点もある

上記のコードはこちらのPart 2フォルダの中、Chapter 02_03 Select.ipynbになります。

以上になります、最後までお読みいただきありがとうございました。

精度評価指標について調べてみた(後半)

f:id:kj_man666:20200816155723j:plain

先日記載したとおり、精度評価指標の後半部分としてコードで各精度評価指標を実装したまとめです。

長いので全文は記載しません、全文を確認される場合はこちらをご覧ください。

前半の理論部分は下記リンクからご覧ください。

megane-man666.hatenablog.com

概要:
データセットHousePriceコンペ
学習モデル:LightGBM
評価指標:MSE、MAE、決定係数、RSME

以降の精度結果は、train データをホールドアウト法で分割した30%部分の検証用データの予測値と実績値を比較しています。

1.欠損値を含まない数値データを特徴量とする

単純に欠損値を含まず、カテゴリデータを除く数値データのみを特徴量とします。

LightGBMは欠損値を含んでいても回せるのですが、他の欠損値処理が必要なモデルで回す場合も考慮して対象外としています。

結果は以下のとおりでした。

MSE :718051766.5529627
MAE :16540.0487430681
決定係数 :0.8970991518382367
RMSE :26796.48795183732

2.1 に加え、欠損値処理を行った数値データを特徴量とする

欠損値の割合が2割超の項目は特徴量から除外します。

欠損値の割合が2割以下の項目については、trainデータについては行を削除し、testデータについては中央値に置換します。

f:id:kj_man666:20200830103146p:plain

train_miss_ratio ...トレインデータの欠損値の割合
test_miss_ratio ... テストデータの欠損値の割合
NaN ... トレインデータ、テストデータいずれかに欠損値があり、一方には欠損値がない

# 欠損値の割合
train_miss_ratio = pd.DataFrame(train.isnull().sum()[train.isnull().sum() != 0]/train.isnull().count()[train.isnull().sum() != 0]*100, columns=["train_miss_ratio"])

test_miss_ratio = pd.DataFrame(test.isnull().sum()[test.isnull().sum() != 0]/test.isnull().count()[test.isnull().sum() != 0]*100, columns=["test_miss_ratio"])

# trainデータとtestデータで比較
pd.concat([train_miss_ratio, test_miss_ratio], axis=1)
MSE :1050826489.8539871
MAE :19378.48196261921
決定係数 :0.8685064360941711
RMSE :32416.45399876407

特徴量を増やしすぎたこと、数値データの欠損値を中央値で置き換えたことが悪影響したのか、精度が悪化してしまいました。

なお、前述のとおりLightGBMは欠損値を含んでいても回せるのですが、他の欠損値処理が必要なモデルで回す場合も考慮して欠損値処理しています。

3.2 に加え、ラベルエンコーディングしてカテゴリデータも特徴量とする

ラベルエンコーディングすることでカテゴリデータも特徴量に追加しています。

# ラベルエンコーディング
from sklearn import preprocessing
encorder = preprocessing.LabelEncoder()

# object型のカテゴリデータのリストを作成
cat_list = list(train.select_dtypes(include=object).isnull().sum()[train.select_dtypes(include=object).isnull().sum() > 0].index)

# ラベルエンコーディングは欠損値があるとできないので missing に置き換える
for i in cat_list:
    train3[i].fillna("missing", inplace=True)

for i in cat_list:
    test3[i].fillna("missing", inplace=True)

# ラベルエンコーディングを実施
for i in cat_list:
    train3[i] = encorder.fit_transform(train3[i])
    test3[i] = encorder.fit_transform(test3[i])

2 と同様に、欠損値が2割超の項目は特徴量から除外しています。

MSE :716059827.242882
MAE :16895.93255402717
決定係数 :0.8973846079209897
RMSE :26759.29422168832

若干ではありますが、精度は1と比べてもおおむね改善ました。

4.3 に加え、外れ値を処理する

目的変数である"SalePrice"と各項目の相関関係を散布図で確認し、外れ値を削除しました。

f:id:kj_man666:20200830105223p:plain

LotAreaの処理のみ抜粋

# LotArea が 30,000未満、かつ、SalePriceが600,000超の項目を除外
train = train.drop(train[(train['LotArea']<30000) & (train['SalePrice']>600000)].index)

# LotArea が 100,000超の項目を除外
train = train.drop(train[train['LotArea']>100000].index)

外れ値除外前

f:id:kj_man666:20200830105823p:plain

外れ値除外後

f:id:kj_man666:20200830105833p:plain

MSE :444408261.28365076
MAE :14892.964374929155
決定係数 :0.9052214999301952
RMSE :21080.992891314458

外れ値を除外することで、精度を向上させることができました。

まとめ

特徴量選択 1 欠損値を含まない数値データを特徴量とする

MSE :718051766.5529627
MAE :16540.0487430681
決定係数 :0.8970991518382367
RMSE :26796.48795183732

特徴量選択 2 欠損値を削除した数値データを特徴量とする

MSE :1050826489.8539871
MAE :19378.48196261921
決定係数 :0.8685064360941711
RMSE :32416.45399876407

特徴量選択3 ラベルエンコーディングしてカテゴリデータも対象にした場合

MSE :716059827.242882
MAE :16895.93255402717
決定係数 :0.8973846079209897
RMSE :26759.29422168832

特徴量選択4 外れ値を除外した場合

MSE :444408261.28365076
MAE :14892.964374929155
決定係数 :0.9052214999301952
RMSE :21080.992891314458

外れ値を除外することで、精度が向上しました!

以上になります、最後までお読みいただきありがとうございました。

参考サイト

medium-s.jp

精度評価指標について調べてみた(前半)

f:id:kj_man666:20200816155723j:plain kaggle等でモデルの精度を評価する際、いつもググって出て来た評価指標を適当に使っていて指標の意味が分かっていなかったので、学びのためにまとめてみました。

なおこの前半部分は理論のまとめとし、後日、後半部分としてコードで各精度評価指標を実装する予定です。

分類モデルの評価指標

混同行列(Confusion Matrix)

混同行列は分類モデルの評価を考える際の基本となる行列で、モデルの予測値と観測値の関係を表したもの。

陽性と予測 陰性と予測
実際は陽性 TP(True Positive)真陽性 FN(False Negative)偽陰性
実際は陰性 FP(False Positive)疑陽性 TN(True Negative)真陰性
from sklearn.metrics import confusion_matrix

m = confusion_matrix(y_test, y_pred)
正解率(Accuracy)
\displaystyle{
\frac{陽性と予測して陽性だった確率+陰性と予測して陰性だった確率(=予測が当たった確率)}{すべての確率}
}

正解率は、全体に対して予測が当たった確率。

from sklearn.metrics import accuracy_score

accuracy_score(y_test, y_pred)
適合率(Precision)
\displaystyle{
\frac{陽性と予測して陽性だった確率}{陽性と予測して陽性だった確率+陽性と予測して陰性だった確率}
}

適合率は、陽性と予測したものが当たった(実際に陽性だった)確率。

たくさん陽性を出す検査(保守的な検査)は再現率は高くなるが、適合率は低くなる。

from sklearn.metrics import precision_score

precision_score(y_test, y_pred)
再現率(Recall)
\displaystyle{
\frac{陽性と予測して陽性だった確率}{陽性と予測して陽性だった確率+陰性と予測して陽性だった確率}
}

再現率は、陽性のものを、陽性と予測できた確率。

たくさん陰性を出す検査は適合率は高くなるが、再現率は低くなる。

from sklearn.metrics import recall_score

recall_score(y_test, y_pred)
F1スコア
\displaystyle{
\frac{2*(適合率*再現率)}{適合率+再現率}
}

F1スコアは適合率と再現率の調和平均

適合率を優先すべきか、再現率を優先すべきかが決まっていない時点で、モデルを総合的に評価する場合などに使われる。

from sklearn.metrics import f1_score

f1_score(y_test, y_pred)

なお、Fの意味は下記サイトをご参照ください。

F値のFはFunction(関数)の意味だったようです。

qiita.com

ROC曲線、AUC

medium.com

  • ROC(Receiver Operating Characteristic)曲線

ROC曲線は、縦軸に真陽性率、横軸に偽陽性率の値をプロットした曲線。

真陽性率(TPR: True Positive Rate)=再現率 陽性のものを、陽性と予測できた確率。

偽陽性率(FPR: False Positive Rate)陽性のものを陰性と予測してしまった確率。

from sklearn.metrics import roc_curve

# 偽陽性率と真陽性率の算出
fpr, tpr, thresholds = roc_curve(y_test, y_pred)

# ROC曲線の描画
plt.plot(fpr, tpr, color='red', label='ROC curve (area = %.3f)' % auc)
plt.plot([0, 1], [0, 1], color='black', linestyle='--')
  • AUC(Area Under the Curve)

AUCとは、2クラス分類の評価指標で、ROC曲線の右下部分の面積を差す。

from sklearn.metrics import auc

# AUCの算出
auc = auc(fpr, tpr)

回帰モデルの評価指標

MSE(Mean Squared Error) 平均二乗誤差
\displaystyle{
MSE=\frac{1}N \sum_{i=1}^n (y_{test, i}- y_{pred, i})^ 2
}

予測値と正解の差(残差)の二乗をサンプルごとに足し上げたものをサンプル数で割ったもの。

MSE が 0 に近いほど見積もられる予測誤差が小さい、すなわち予測精度が高い。

from sklearn.metrics import mean_squared_error

mse = mean_squared_error(y_test, y_pred)
MAE(Mean Absolute Error) 平均絶対誤差
\displaystyle{
MAE=\frac{\sum_{i}|y_{test,i} - y_{pred,i}|}n
}

残差の絶対値をサンプルごとに足し上げ、最後にサンプル数で割ったもの。

MSEと比べ残差が二乗されていない分、(予測の)外れ値の影響を受けにくい。

MAE も 0 に近いほど予測精度が高いことを表す。

from sklearn.metrics import mean_absolute_error

mae = mean_absolute_error(y_test, y_pred)
R2(Multiple R-Squared) 決定係数
\displaystyle{
R^2=1-\frac{\sum{(y_{test,i}-y_{pred,i})^2}}{\sum{(y_{test,i}-\overline{y_{pred,i}})^2}}
}

検証データの平均値で予測をした場合の残差平方和 SST(Sum of Squared Total)と、モデルの残差平方和 SSE(Sum of Squared Errors)の比率で、 R2=1−SSE/SST と定義される。

平均値予測という最もナイーブな予測に対して二乗誤差をどれだけ削れたかを示す指標で、誤差をすべてなくせば1.0となり、平均値予測と同じになれば0.0になる。

R2 の範囲は、通常0〜1の値を取りますが、負になる可能性がある。

from sklearn.metrics import r2_score

r2 = r2_score(y_test, y_pred)
RMSE(Root Mean Squared Error) 平均平方二乗誤差
\displaystyle{
RMSE=\sqrt{\frac{\sum{(y^{test,i}-y ^{pred,i})^2}}{n}}
}

実績と予測が近似する(誤差が小さい)ほど、RMSE は小さくなる。

逆に、実績と予測が乖離する(誤差が大きい)と、RMSE が大きくなる。

したがって、外れ値があると、RMSE が著しく大きくなる。

RMSE は外れ値の影響を受けやすい。

import numpy as np
from sklearn.metrics import mean_squared_error

rmse = np.sqrt(mean_squared_error(y_test, y_pred))

最後までお読みいただきありがとうございました。

参照サイト

funatsu-lab.github.io

mathwords.net

mathwords.net

qiita.com

weblab.t.u-tokyo.ac.jp

note.nkmk.me

stats.biopapyrus.jp