《机器学习》 西瓜书的代码实现. 《机器学习》 西瓜书实例 第 2 章

我的博客: https://yunist.cn

《机器学习》西瓜书 第 $2$ 章 编程实例

这里我们拥有一个训练样本集, 是关于学生的分数与录取的情况的 ( 1 为被录取, 0 为被拒绝) .

准备及分类器训练

In [6]:
# 导入头文件
import numpy as np
np.set_printoptions(suppress=True)  # 禁用科学计数
import pandas as pd
import matplotlib.pyplot as plt
In [7]:
path = 'work/data.txt'
data = pd.read_csv(
    path,
    names=['Exam1', 'Exam2', 'Admitted'])
data.head() # 先来看一下长啥样
Out[7]:
Exam1 Exam2 Admitted
0 34.623660 78.024693 0
1 30.286711 43.894998 0
2 35.847409 72.902198 0
3 60.182599 86.308552 1
4 79.032736 75.344376 1

接下来, 我们进行可视化.

In [8]:
positive = data[data['Admitted'].isin([1])]
negitive = data[data['Admitted'].isin([0])]

fig, ax = plt.subplots(figsize=(12, 8))
ax.scatter(positive['Exam1'], positive['Exam2'], marker='o', c='b', label='Admitted')
ax.scatter(negitive['Exam1'], negitive['Exam2'], marker='x', c='r', label='Not Admitted')
ax.legend()
ax.set_xlabel('Exam1 Score')
ax.set_ylabel('Exam2 Score')
plt.show()

可以看到, 大概是有一个分类界限的. 这里使用逻辑回归进行分类 (至于逻辑回归是什么, 现在还不需要了解) .

In [9]:
X = data.iloc[:, :2]  # 切分
y = data.iloc[:, 2]
X.head()
Out[9]:
Exam1 Exam2
0 34.623660 78.024693
1 30.286711 43.894998
2 35.847409 72.902198
3 60.182599 86.308552
4 79.032736 75.344376
In [10]:
y.head()
Out[10]:
0    0
1    0
2    0
3    1
4    1
Name: Admitted, dtype: int64
In [11]:
from sklearn.linear_model import LogisticRegression
clf = LogisticRegression()
clf.fit(X, y)  # 训练
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/sklearn/linear_model/logistic.py:432: FutureWarning: Default solver will be changed to 'lbfgs' in 0.22. Specify a solver to silence this warning.
  FutureWarning)
Out[11]:
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
          intercept_scaling=1, max_iter=100, multi_class='warn',
          n_jobs=None, penalty='l2', random_state=None, solver='warn',
          tol=0.0001, verbose=0, warm_start=False)

我们将训练后的结果可视化

In [12]:
# 获得网格
x1_max = X['Exam1'].max()
x1_min = X['Exam1'].min()
x2_max = X['Exam2'].max()
x2_min = X['Exam2'].min()
x1, x2 = np.mgrid[x1_min:x1_max:500j, x2_min:x2_max:500j]  # np.mgrid的用法自行百度
x_pre = np.stack((x1.flat, x2.flat), axis=1)  # 合成坐标
x_pre
Out[12]:
array([[30.05882245, 30.60326323],
       [30.05882245, 30.74006919],
       [30.05882245, 30.87687515],
       ...,
       [99.8278578 , 98.59582383],
       [99.8278578 , 98.73262979],
       [99.8278578 , 98.86943574]])
In [13]:
import matplotlib as mpl
y_pre = clf.predict(x_pre)
y_pre = y_pre.reshape(x1.shape)
cm_light = mpl.colors.ListedColormap(['#77E0A0', '#FF8080', '#A0A0FF'])
fig, ax = plt.subplots(figsize=(12, 8))
plt.pcolormesh(x1, x2, y_pre, cmap=cm_light)  # 网格
ax.scatter(positive['Exam1'], positive['Exam2'], marker='o', c='b', label='Admitted')
ax.scatter(negitive['Exam1'], negitive['Exam2'], marker='x', c='r', label='Not Admitted')
ax.legend()
plt.xlim(x1_min, x1_max)
plt.ylim(x2_min, x2_max)
ax.set_xlabel('Exam1 Score')
ax.set_ylabel('Exam2 Score')
plt.show()

绘制曲线

$ROC$ 曲线

下面我们来试着绘制 $ROC$ 曲线

In [14]:
positive_num = positive.shape[0]
negitive_num = negitive.shape[0]
positive_num, negitive_num
Out[14]:
(60, 40)

先将每点预测的概率和 y 和在一起, 然后依据概率降序排序. 接着按书里的进行描点绘画, 最后连接.

In [15]:
res = np.array([[0, 0]])
pos = clf.predict_proba(X)[:, 1].reshape(X.shape[0], 1)
pos = np.insert(pos, 1, values=y, axis=1)
pos = pos[np.argsort(-pos[:, 0])]  # 降序排序 (至于为什么是这样排序可以自行百度)
x_temp = 0
y_temp = 0
k = 0
sign = False
for i, j in pos:
    k += 1
    if sign:
        sign = False
        continue
    if (k != pos.shape[0] and i == pos[k][0] and j != pos[k][1]):  # 特判 0 与 1 概率一样的情况, 此时曲线应当斜向上(加了好丑).
        y_temp += (1 / positive_num)  # (1 / m+)
        x_temp += (1 / negitive_num)  # (1 / m-)
        res = np.append(res, [[x_temp, y_temp]], axis=0)
        sign = True
    elif j == 1:
        y_temp += (1 / positive_num)  # (1 / m+)
        res = np.append(res, [[x_temp, y_temp]], axis=0)
    else:
        x_temp += (1 / negitive_num)  # (1 / m-)
        res = np.append(res, [[x_temp, y_temp]], axis=0)
In [16]:
fig, ax = plt.subplots(figsize=(12, 8))
ax.plot(res[:, 0], res[:, 1])
ax.set_xlabel("FPR")
ax.set_ylabel("TPR")
plt.title("ROC")
plt.show()

看起来不错, 不是吗.

代价曲线

接下来根据概率曲线绘制代价曲线.

In [17]:
cost_res = res.copy()
cost_res[:, 0] = 1 - cost_res[:, 0]  # 假反例率
cost_res[:10]
Out[17]:
array([[1.        , 0.        ],
       [1.        , 0.01666667],
       [1.        , 0.03333333],
       [1.        , 0.05      ],
       [1.        , 0.06666667],
       [1.        , 0.08333333],
       [1.        , 0.1       ],
       [1.        , 0.11666667],
       [1.        , 0.13333333],
       [1.        , 0.15      ]])
In [18]:
fig, ax = plt.subplots(figsize=(12, 8))
for fnr, fpr in cost_res:
    ax.plot([0, 1], [fpr, fnr])
ax.set_xlabel("Positive example probability cost")
ax.set_ylabel("Normalized cost")
plt.title("Cost curve and expected total cost")  # 用百度翻译的qwq
plt.show()

额...挺好看的.

标准化

$Min-max$ 标准化

定义画图函数, 方便画图.

In [19]:
def temp_scatter_show(new_positive, new_negitive):
    fig, ax = plt.subplots(figsize=(12, 8))
    ax.scatter(new_positive['Exam1'], new_positive['Exam2'], marker='o', c='b', label = 'Admitted')
    ax.scatter(new_negitive['Exam1'], new_negitive['Exam2'], marker='x', c='r', label = 'Not Admitted')
    ax.legend()
    ax.set_xlabel('Exam1')
    ax.set_ylabel('Exam2')
    plt.show()
In [20]:
def min_max(new_data):  # 定义min_max标准化函数
    X = new_data.iloc[:, :2]
    new_data.iloc[:, :2] = ((X - X.min()) / (X.max() - X.min()))  # 我们让min' = 0, max' = 1, 也就是数据分布在[0, 1].
    new_positive = new_data[new_data['Admitted'].isin([1])]
    new_negitive = new_data[new_data['Admitted'].isin([0])]
    temp_scatter_show(new_positive, new_negitive)
In [21]:
min_max(data.copy())

我们看看加入极端值会发生什么.

In [22]:
temp_data = data.copy()
temp_data.loc[temp_data.shape[0]] = [500, 500, 0]
min_max(temp_data.copy())

问题应该很明显了吧, 除了极端值, 其它点几乎缩在一起.

$z-score$ 标准化

In [23]:
def z_score(new_data):  # 定义z-score标准化函数
    X = new_data.iloc[:, :2]
    new_data.iloc[:, :2] = (X - X.mean()) / X.std()
    new_positive = new_data[new_data['Admitted'].isin([1])]
    new_negitive = new_data[new_data['Admitted'].isin([0])]
    temp_scatter_show(new_positive, new_negitive)
In [24]:
z_score(data.copy())

同样的, 我们看看加入极端点会发生什么.

In [25]:
z_score(temp_data.copy())

也许你会认为两个都差不多, 但是注意到它们的比例, 就会发现 $z-score$ 对极端点更不敏感. (当然相对尺度是差不多的) .