题目:
构建模型预测某个学生是否能被大学录取,你有之前申请学生的两次考试成绩和最终录取的结果,需要你构建一个评估录取的分类模型。
首先准备数据,绘出散点图
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 读取数据
path = 'ex2data1.txt'
data = pd.read_csv(path, header=None, names=['Exam1', 'Exam2', 'Admitted'])
data.head()
data.describe() # 更详细的数据
positive = data[data['Admitted'].isin([1])]
negative = data[data['Admitted'].isin([0])]
fig, ax = plt.subplots(figsize=(12, 8))
ax.scatter(positive['Exam1'], positive['Exam2'], s=50, c='b', marker='o', label='Admitted')# s=50设置了数据点的大小
ax.scatter(negative['Exam1'], negative['Exam2'], s=50, c='r', marker='x', label='Not Admitted')
ax.legend()
ax.set_xlabel('Exam1 Score')
ax.set_ylabel('Exam2 Score')
plt.show()
- isin()是Pandas中的一个函数,用于过滤数据框的行,只保留那些指定列中的值在给定列表中的行。在这里,它筛选出'Admitted'列中值为1的行。
-
散点图如下:
使用sigmoid函数,将模型的输出转换为概率值
- Sigmoid函数可以将任何实数映射到0和1之间
- 当输入值非常大或非常小时,输出值会趋近于0或1。
从而可以被解释为概率。例如,在逻辑回归中,Sigmoid函数常常被用作输出层,将线性回归模型的输出转换为概率值。
- 下面是使用NumPy库中的exp函数计算e的-z次幂
- 这个表达式是Sigmoid函数的标准形式,用于将任何实数z映射到0和1之间的一个值。
- Sigmoid函数通常用作激活函数,将模型的输出转换为概率值。
- 例如,在二分类问题中,模型的输出可以被解释为属于正类的概率。
def sigmoid(z):
return 1 / (1 + np.exp(-z)) # 将输入z映射到0和1之间的一个值。
# Sigmoid函数经常用于将模型的 输出 转换为 概率值
nums = np.arange(-10, 10, step=0.5)
fig, ax = plt.subplots(figsize=(12, 8))
ax.plot(nums, sigmoid(nums), 'r')
plt.show()
所绘图形如下:👇
使用代价函数
1. 首先对data进行预处理
# 对输入(特征)做扩展
data.insert(0, 'Ones', 1)
data.head()
也就是添加了一行
q
2. 使用代价函数,算出初始的代价
!!!公式代码理解:
- 定义一个
cost
的函数,它接受三个参数:theta
(模型的参数),X
(输入数据),和Y
(真实标签) - 第一部分:
- first = Y * np.log(sigmoid(X@theta.T))
- `X@theta.T`:这是矩阵乘法操作,其中 `X` 是输入数据矩阵,`theta.T` 是参数向量 `theta` 的转置。这个操作计算了所有样本的预测概率。
- `sigmoid(X@theta.T)`:将预测概率通过 Sigmoid 函数转换为介于 0 和 1 之间的值。
- `Y * np.log(sigmoid(X@theta.T))`:对于每个样本,如果其真实标签 `Y` 为 1,则计算 `np.log(sigmoid(X@theta.T))`;如果 `Y` 为 0,则计算 `0 * np.log(sigmoid(X@theta.T))`,即计算 0。这实际上只关心那些真实标签为 1 的样本的预测概率。
- 第二部分:
- second = (1 - Y) * np.log(1 - sigmoid(X@theta.T))
- `1 - Y`:对于每个样本,如果其真实标签 `Y` 为 1,则 `1 - Y` 为 0;如果 `Y` 为 0,则 `1 - Y` 为 1。
- 第二部分同理
- 然后计算损失:return -1 * np.mean(first + second)
- `first + second`:将第一部分和第二部分相加,得到每个样本的损失。
- `np.mean(first + second)`:计算所有样本的平均损失。
- `-1 * np.mean(first + second)`:由于对数损失通常定义为负数(因为我们希望最大化对数似然,这等价于最小化负对数似然),所以这里取平均损失的负值作为最终的损失。
def cost(theta, X, Y):
first = Y * np.log(sigmoid(X@theta.T))
second = (1 - Y) * np.log(1 - sigmoid(X@theta.T))
return -1 * np.mean(first + second)
X
用作特征输入,而Y
用作标签或目标输出
X和y
是一个NumPy数组,包含了data
中除了最后一列之外的所有数据。
X = data.iloc[:, 0: -1].values # 左闭右开
Y = data.iloc[:, -1].values # .values是将其转化为numpy数组
theta = np.zeros(3)
theta
# array([0., 0., 0.])
X.shape, Y.shape, theta.shape
#((100, 3), (100,), (3,))
# 计算初始的代价(theta=0),为0.6931471805599453
cost(theta, X, Y)
将梯度下降运用到正则化代价函数中
# 计算步长
# gradient只是计算了梯度下降 θ更新的步长,使用Scipy.optimize.fmin_tnc拟合最优的 θ
def gradient(theta, X, Y):
return (1/len(X) * X.T @ (sigmoid(X @ theta.T) - Y)) # X转置相当于后面这部分与求和了
gradient(theta, X, Y)
# array([ -0.1 , -12.00921659, -11.26284221])
拟合参数
import scipy.optimize as opt
# func=cost,就是传的损失函数;x0初始点;args是传样本的输入和输出;jac传的梯度
res = opt.minimize(fun=cost, x0=np.array(theta), args=(X, np.array(Y)), method='Newton-CG', jac=gradient)
res
fun: 0.20349770451259855 jac: array([1.62947970e-05, 1.11339134e-03, 1.07609314e-03]) message: 'Optimization terminated successfully.' nfev: 71 nhev: 0 nit: 28 njev: 242 status: 0 success: True x: array([-25.16576744, 0.20626712, 0.20150754])
cost(res.x, X, Y)
# 0.20349770451259855
此时通过使用Scipy.optimize.minimize拟合出了最优的θ(与上面0.6形成对比)
用训练集预测和验证
# theta就是我们刚刚训练出来的theta
def predict(theta, X):
probability = sigmoid(X @ theta.T)
return [1 if x >= 0.5 else 0 for x in probability]
然后使用了一个方法:
final_theta = res.x
y_pred = predict(final_theta,X)
# support标签中出现的次数
# precision查准率,recall召回率,f1-score调和平均数
from sklearn.metrics import classification_report
print(classification_report(y,y_pred))
precision recall f1-score support 0 0.87 0.85 0.86 40 1 0.90 0.92 0.91 60 accuracy 0.89 100 macro avg 0.89 0.88 0.88 100 weighted avg 0.89 0.89 0.89 100
真实 | 类别 | ||
0 | 1 | ||
预测 | 0 | TN(真反例) | FN(假反例) |
类别 | 1 | FP(假正例) | TP(真正例) |
precision=tp / tp+fp 查准率 意思是:在所有正例中真正的正例占比,也就是准确率
recall=tp / tp+fn 召回率 意思是:你这个算法可以从真实的类别中回收回来多少正例
f1-score=2pr / p+r 调和平均数
这就是决策边界:coef = -res.x / res.x[2]
最后画出决策边界
print(res.x)# theta
# [-25.16576744 0.20626712 0.20150754]
coef = -res.x / res.x[2]
coef
# array([124.88747248, -1.02361985, -1. ])
coef[0]
# 124.88747248
coef[1]
# -1.0236198475377516
上面代码理解如下图👇
然后在图上画出来:
x = np.arange(30, 100, 0.5)
y = coef[0] + coef[1] * x # y就是算出来的决策边界
fig, ax = plt.subplots(figsize=(12, 8))
ax.scatter(positive['Exam1'], positive['Exam2'], s=50, c='b', marker='o', label='Admitted')
ax.scatter(negative['Exam1'], negative['Exam2'], s=50, c='r', marker='x', label='Not Admitted')
ax.plot(x, y, label='Decision Boundary', c='grey')
ax.legend()
ax.set_xlabel('Exam1 Score')
ax.set_ylabel('Exam2 Score')
plt.show()