《机器学习》 西瓜书的代码实现.
这里我们拥有一个训练样本集, 是关于学生的分数与录取的情况的 ( 1 为被录取, 0 为被拒绝) .
# 导入头文件
import numpy as np
np.set_printoptions(suppress=True) # 禁用科学计数
import pandas as pd
import matplotlib.pyplot as plt
path = 'work/data.txt'
data = pd.read_csv(
path,
names=['Exam1', 'Exam2', 'Admitted'])
data.head() # 先来看一下长啥样
接下来, 我们进行可视化.
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()
可以看到, 大概是有一个分类界限的. 这里使用逻辑回归进行分类 (至于逻辑回归是什么, 现在还不需要了解) .
X = data.iloc[:, :2] # 切分
y = data.iloc[:, 2]
X.head()
y.head()
from sklearn.linear_model import LogisticRegression
clf = LogisticRegression()
clf.fit(X, y) # 训练
我们将训练后的结果可视化
# 获得网格
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
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$ 曲线
positive_num = positive.shape[0]
negitive_num = negitive.shape[0]
positive_num, negitive_num
先将每点预测的概率和 y 和在一起, 然后依据概率降序排序. 接着按书里的进行描点绘画, 最后连接.
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)
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()
看起来不错, 不是吗.
接下来根据概率曲线绘制代价曲线.
cost_res = res.copy()
cost_res[:, 0] = 1 - cost_res[:, 0] # 假反例率
cost_res[:10]
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()
额...挺好看的.
定义画图函数, 方便画图.
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()
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)
min_max(data.copy())
我们看看加入极端值会发生什么.
temp_data = data.copy()
temp_data.loc[temp_data.shape[0]] = [500, 500, 0]
min_max(temp_data.copy())
问题应该很明显了吧, 除了极端值, 其它点几乎缩在一起.
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)
z_score(data.copy())
同样的, 我们看看加入极端点会发生什么.
z_score(temp_data.copy())
也许你会认为两个都差不多, 但是注意到它们的比例, 就会发现 $z-score$ 对极端点更不敏感. (当然相对尺度是差不多的) .