<jupyter_start><jupyter_text>第5章 ロジスティック回帰とROC曲線：学習モデルの評価方法  「05-roc_curve」の解説 ITエンジニアための機械学習理論入門「第5章 ロジスティック回帰とROC曲線:学習モデルの評価方法」で使用しているサンプルコード「05-roc_curve.py」の解説です。※ 解説用にコードの内容は少し変更しています。 はじめに必要なモジュールをインポートしておきます。関数 multivariate_normal は、多次元の正規分布に従う乱数を生成するために利用します。<jupyter_code>import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from pandas import Series, DataFrame

from numpy.random import multivariate_normal

import matplotlib
matplotlib.rcParams['font.size'] = 12<jupyter_output><empty_output><jupyter_text>トレーニング用データを生成する関数を用意します。平面上の○☓の2種類のデータについて、それぞれの「個数、中心座標、分散」を引数で指定します。<jupyter_code>def prepare_dataset(n1, mu1, variance1, n2, mu2, variance2):
    df1 = DataFrame(multivariate_normal(mu1, np.eye(2)*variance1 ,n1),
                    columns=['x','y'])
    df1['type'] = 1
    df2 = DataFrame(multivariate_normal(mu2, np.eye(2)*variance2, n2),
                    columns=['x','y'])
    df2['type'] = -1
    df = pd.concat([df1,df2], ignore_index=True)
    df = df.reindex(np.random.permutation(df.index)).reset_index(drop=True)
    return df<jupyter_output><empty_output><jupyter_text>ロジスティック回帰で分割線を決定する関数を用意します。ここでは、得られた結果を用いて、トレーニングセットの各データに対して確率の値を付与したデータフレームも返却するようにしています。<jupyter_code># ロジスティック回帰
def run_logistic(train_set):
    pd.options.mode.chained_assignment = None
    w = np.array([[0], [0.1], [0.1]])
    phi = train_set[['x', 'y']]
    phi['bias'] = 1
    phi = phi[['bias', 'x', 'y']].values
    t = (train_set[['type']] + 1)*0.5 # type = 1, -1 を type = 1, 0 に変換
    t = t.values

    # 最大100回のIterationを実施
    for i in range(100):
        # IRLS法によるパラメータの修正
        y = np.array([])
        for line in phi:
            a = np.dot(line, w)
            y = np.append(y, [1.0/(1.0+np.exp(-a))])
        r = np.diag(y*(1-y)) 
        y = y[np.newaxis,:].T
        tmp1 = np.linalg.inv(np.dot(np.dot(phi.T, r),phi))
        tmp2 = np.dot(phi.T, (y-t))
        w_new = w - np.dot(tmp1, tmp2)
        # パラメータの変化が 0.1% 未満になったら終了
        if np.dot((w_new-w).T, (w_new-w)) < 0.001 * np.dot(w.T, w):
            w = w_new
            break
        w = w_new
        
    # 分類誤差の計算
    w0, w1, w2 = w[0], w[1], w[2]
    err = 0
    for index, line in train_set.iterrows():
        a = np.dot(np.array([1, line.x, line.y]), w)
        p = 1.0/(1.0+np.exp(-a))
        train_set.loc[index, 'probability'] = p
        if (p-0.5)*line.type < 0:
            err += 1
    err_rate = err * 100 / len(train_set)
    result = train_set.sort_values(by=['probability'], ascending=[False]).reset_index()
    
    return w0, w1, w2, err_rate, result<jupyter_output><empty_output><jupyter_text>結果をグラフ、および、ROC曲線として表示する関数を用意します。<jupyter_code># 結果の表示
def show_result(subplot, train_set, w0, w1, w2, err_rate):
    train_set1 = train_set[train_set['type']==1]
    train_set2 = train_set[train_set['type']==-1]
    ymin, ymax = train_set.y.min()-5, train_set.y.max()+10
    xmin, xmax = train_set.x.min()-5, train_set.x.max()+10

    subplot.set_ylim([ymin-1, ymax+1])
    subplot.set_xlim([xmin-1, xmax+1])
    subplot.scatter(train_set1.x, train_set1.y, marker='o', label=None)
    subplot.scatter(train_set2.x, train_set2.y, marker='x', label=None)

    linex = np.arange(xmin-5, xmax+5)
    liney = - linex * w1 / w2 - w0 / w2
    label = "ERR %.2f%%" % err_rate
    subplot.plot(linex, liney, label=label, color='red')
    subplot.legend(loc=1)
# ROC曲線の表示
def draw_roc(subplot, result):
    positives = len(result[result['type']==1])
    negatives = len(result[result['type']==-1])
    tp = [0.0] * len(result)
    fp = [0.0] * len(result)
    for index, line in result.iterrows():
        for c in np.arange(0, len(result)):
            if index < c:
                if line.type == 1:
                    tp[c] += 1
                else:
                    fp[c] += 1
    tp_rate = np.array(tp) / positives
    fp_rate = np.array(fp) / negatives

    subplot.set_ylim([0, 1])
    subplot.set_xlim([0, 1])
    subplot.set_xlabel("False positive rate")
    subplot.set_ylabel("True positive rate")
    subplot.plot(fp_rate, tp_rate)<jupyter_output><empty_output><jupyter_text>比較的分散が小さくて、分類が容易なトレーニングセットを用意します。<jupyter_code>train_set = prepare_dataset(80, [9, 9], 50, 200, [-3, -3], 50)<jupyter_output><empty_output><jupyter_text>ロジスティック回帰を適用した結果を表示します。<jupyter_code>w0, w1, w2, err_rate, result = run_logistic(train_set)
fig = plt.figure(figsize=(6, 12))
subplot = fig.add_subplot(2, 1, 1)
show_result(subplot, train_set, w0, w1, w2, err_rate)
subplot = fig.add_subplot(2, 1, 2)
draw_roc(subplot, result)<jupyter_output><empty_output><jupyter_text>分散が大きくて、分類が困難なトレーニングセットを用意します。<jupyter_code>train_set = prepare_dataset(80, [9, 9], 150, 200, [-3, -3], 150)<jupyter_output><empty_output><jupyter_text>ロジスティック回帰を適用した結果を表示します。<jupyter_code>w0, w1, w2, err_rate, result = run_logistic(train_set)
fig = plt.figure(figsize=(6, 12))
subplot = fig.add_subplot(2, 1, 1)
show_result(subplot, train_set, w0, w1, w2, err_rate)
subplot = fig.add_subplot(2, 1, 2)
draw_roc(subplot, result)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.15826052422557585, 'fraction_numerical': 0.03733121525019857, 'mean_word_length': 3.646678966789668, 'pattern_counts': {'":': 0, '<': 36, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '8', 'dir': 'jupyter-structured-clean-dedup', 'id': '58414', 'license': 'Apache-2.0', 'n_tokens_mistral': 2544, 'n_tokens_neox': 2405, 'n_words': 346, 'path': '05-roc_curve.ipynb', 'repo_name': 'enakai00/jupyter_ml4se', 'size': '5036'}