import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.optimize as opt
1. 读取数据
文件下方百度云自取👇:
链接:https://pan.baidu.com/s/1S51B43jP7nshx_thNe27xw 提取码:oa6i
# 路径根据自己下载位置修改
path='ML-homework-main\ex2-logistic regression\ex2data2.txt'
data=pd.read_csv(path,header=None,names=['Test1','Test2','Accepted'])
data.head()
2. 数据散点图绘制
# 绘制当前散点图的方法
def plot_scatter():
positive=data[data['Accepted'].isin([1])]
negative=data[data['Accepted'].isin([0])]
plt.figure(figsize=(12,8))
plt.scatter(positive['Test1'],positive['Test2'],c='b',marker='o',label='Positive')
plt.scatter(negative['Test1'],negative['Test2'],c='r',marker='x',label='Negative')
plt.xlabel('Test1')
plt.ylabel('Test2')
plt.legend()
# 调用方法
plot_scatter()
plt.show()
3. 特征映射
如果样本量多,逻辑回归问题很复杂,而原始特征只有 x 1 , x 2 x1,x2 x1,x2,那么可以用多项式创建更多的特征 x 1 、 x 2 、 x 1 x 2 、 x 1 2 、 x 2 2 、 . . . X 1 n X 2 n x1、x2、x1x2、x1^2、x2^2、... X1^nX2^n x1、x2、x1x2、x12、x22、...X1nX2n。
因为更多的特征进行逻辑回归时,得到的分割线可以是任意高阶函数的形状
如下,原本只有x_1x_2两个特征,通过最高6次方的多项式组合,最终生成了=1+2+3+4+5+6+7=28个特征项
x 1 i − p x 2 p :意思为 x 1 的 i − p 次方乘 x 2 的 p 次方,乘积组成新的特征值。 x_{1}^{i-p}x_{2}^{p}:意思为x_1的i-p次方乘x_2的p次方,乘积组成新的特征值。 x1i−px2p:意思为x1的i−p次方乘x2的p次方,乘积组成新的特征值。我们的数据只有两列,因此 x 1 x_1 x1与 x 2 x_2 x2实则对应 x x x轴与 y y y轴数据
def feature_mapping(x,y,power=6,as_ndarry=False):
# i和i-p的值分别会填充到字符串中的0,1两个占位符中
# i和p的值会在下方两个for循环中产生,p的值是依附于i的,且永远小于i,但其值逐步递增,所以i-p是逐步递减的
#
data={'f{0}{1}'.format(i-p,p):np.power(x,i-p)*np.power(y,p)
for i in range(power+1)
for p in range(i+1)
}
# 是否将DataFrame转为ndarray形式
if as_ndarry:
return pd.DataFrame(data).values
else:
return pd.DataFrame(data)
3.1 测试特征映射函数
x1=data['Test1'].values
x2=data['Test2'].values
dic=feature_mapping(x1,x2)
dic.head()
4. 正则化代价函数
代价函数 c o s t cost cost可以用来评估一个模型预测的好坏程度, c o s t cost cost越小,说明预测值与真实值的误差越小。
但实际上,我们在评估一个模型好坏程度时,还需要考虑过拟合的问题,即 c o s t cost cost在训练集中的表现堪称完美,然而在测试集中的表现却不如预期那样好。这便是发生了过拟合的情况,太过于专注将训练集中的正确结果进行囊括,从而拟合出一个特别复杂的预测函数,而这一函数往往只在单例中有效而不具有普适性。
那么如何防止过拟合呢?
研究发现,当 θ \theta θ值越小,拟合出的预测函数图形就会越平滑,而平滑的函数相对而言不会特别复杂,具有普适性,由此我们引出了正则化这个概念。
通过在 c o s t cost cost中添加一个关于 θ \theta θ的多项式【正则化项】来影响 c o s t cost cost的取值。当 c o s t cost cost变小, θ \theta θ也一定会变小,从而保证拟合出来的预测函数是一平滑曲线。
又因为 θ 0 \theta_0 θ0对应的参数恒为 1 1 1,即在决策边界中只负责函数的上下移动,对于形成复杂图形毫无影响,对其正则化没有意义,所以可以不利用正则化去控制其处于一个较小的值【本人的猜测,仅供参考】
如果 λ \lambda λ足够大,为了使得 J ( θ ) J(\theta) J(θ)整体小,那么 θ j ( j > 0 ) \theta_j(j>0) θj(j>0)一定会尽可能小,最后趋近于 0 0 0,因此 θ 0 + θ 1 x 1 + . . . \theta_0+\theta_1x_1+... θ0+θ1x1+...中只会剩下 θ 0 \theta_0 θ0这一项,即决策边界是一条平行于 x x x轴的直线
# 激活函数,不知道是啥的可看看普通的逻辑回归
def sigmoid(z):
return 1/(1+np.exp(-z))
# 普通代价函数
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)
# 正则化代价函数,l为λ
def regularized_cost(theta, X, Y, l=1):
# θj的取值,除去θ0
theta_ln = theta[1:]
regularized_term = l/(2*len(X))*np.power(theta_ln, 2).sum()
# 普通代价函数+正则化项就是正则化代价函数
return cost(theta, X, Y)+regularized_term
5. 数据处理
# 进行特征映射后产生的多个新特征值
X=feature_mapping(x1,x2,power=6,as_ndarry=True)
Y=data['Accepted']
# 有几个特征值就对应几个θ
theta = np.zeros(X.shape[1])
6. 测试代价函数
# 普通代价函数
cost(theta,X,Y)
# 由于初始θ值全为0,因此正则化项也为0,所以代价值与为正则化之前一致
regularized_cost(theta,X,Y)
7. 正则化梯度
J ( θ ) J(\theta) J(θ)正则化后,对于,其对 θ j \theta_j θj的偏导会发生一定变化【简单的求导知识】,结果如下:
对应的, θ j \theta_j θj的具体迭代公式也会发生变化,我们由此写出新的梯度下降函数。此处我们仅实现求导函数,并未实现迭代函数【后边用库函数实现】
def gradient(theta, X, Y):
# 返回的是一个向量,里边放了J(θ)对各个θ求偏导的值【未加正则化项】
return 1/len(X)*X.T@(sigmoid(X@theta.T)-Y)
def regularized_gradient(theta,X,Y,l=1):
# 取除θ1...θn外的所有θ
theta_ln=theta[1:]
# 在θ前加上参数λ/m
regularized_theta=l/len(X)*theta_ln
# 为regularized_theta多加一列元素0,使得J(θ)对θ0与θ1的求偏导的表达式能够统一
regularized_term=np.concatenate([np.array([0]),regularized_theta])
return gradient(theta,X,Y)+regularized_term
8. 测试正则化后的 J ( θ ) J(\theta) J(θ)偏导公式
# 未加正则项时各θ的偏导
gradient(theta, X, Y)
# 加了正则项后各θ的偏导【由于初始θ为0,因此加不加正则项,偏导都一样】
regularized_gradient(theta,X,Y)
9. 拟合后的 θ \theta θ
传入代价函数以及代价函数的求偏导函数,调用库函数进行自动迭代,得到当下最优的 θ \theta θ值
# func是要最小化的函数【代价函数】
# x0是最小化函数的自变量【θ值】
# fprime是最小化的方法【梯度下降】
# 普通梯度下降迭代后的θ值
result = opt.fmin_tnc(func=cost, x0=theta, fprime=gradient, args=(X, Y))
#正则化梯度下降迭代后的θ值
result1 = opt.fmin_tnc(func=regularized_cost, x0=theta, fprime=regularized_gradient, args=(X, Y))
10. 预测分析
# 将预测值带入激活函数
def predict(theta,X):
# 带入预测函数
probability=sigmoid(X@theta.T)
return [1 if i>=0.5 else 0 for i in probability]
# 准确率输出
def predict_accuracy(theta,X,Y):
# predictions存放当前X对应的预测值
predictions = predict(theta, X)
# ^是异或符号,二者同说明预测成功,此时为异或值0,在correct中标记为1
# zip函数起打包作用,即同时从predictions,Y中取对应元素a,b
correct = [1 if a^b == 0 else 0 for (a,b) in zip(predictions, Y)]
# 预测正确的长度除总长度即预测准确率
accuracy = (sum(correct) / len(correct))
print(f'accuracy = {accuracy*100}%')
return None
# 输出使用普通梯度下降与正则化后梯度下降的预测准确率
#【反向优化,准确率下降了...不过这是对于当前训练集而言的,事实上我们确实达到我们的目的,即不让模型拟合的太"过"】
print('根据普通代价函数得到的预测函数准确率:')
predict_accuracy(result[0],X,Y)
print('根据正则化后代价函数得到的预测函数准确率:')
predict_accuracy(result1[0],X,Y)
11. 决策边界
# 根据power和λ得到theta
def find_theta(power, l):
# 初始化theta值
theta = np.zeros(X.shape[1])
res = opt.fmin_tnc(func=regularized_cost, x0=theta, fprime=regularized_gradient, args=(X, Y,l))
return res[0]
# 决策边界,thetaX = 0, thetaX <= threshhold
def find_decision_boundary(density, power, theta, threshhold):
# 生成[-1,1.2]均等的density份数据,存储在list中
t1 = np.linspace(-1, 1.2, density)
t2 = np.linspace(-1, 1.2, density)
# 将数据对应上,每份对应的一个tuple
cordinates = [(x, y) for x in t1 for y in t2]
# 把x的数据放在一起,把y的数据放在一起【看上去像是整个坐标轴的点】
x_cord, y_cord = zip(*cordinates)
# 将这些点进行特征映射
mapped_cord = feature_mapping(x_cord, y_cord, power)
# 得到预测值
pred = mapped_cord.values @ theta.T
# 只有高于threshhold的值才被我们需要【有个问题,threshhold如何确定?】
decision = mapped_cord[np.abs(pred) <= threshhold]
# f10对应的是x轴数据,f01对应的是y轴数据
return decision.f10, decision.f01
# 画决策边界
def draw_boundary(power, l):
theta=find_theta(power,l)
# 数据密度
density = 1000
# 筛选数据的门槛值【不知道如何根据什么来确定其大小】
threshhold = 1 * 10**-3
# 绘制散点图的办法【上边已经封装成函数】
plot_scatter()
# 根据上边的决策边界函数得到我们最后需要画的坐标值
x, y = find_decision_boundary(density, power, theta, threshhold)
plt.scatter(x, y, s=20, c='g', marker='.', label='Decision Boundary')
plt.legend()
plt.show()
12. 测试决策边界
# power越大,特征映射的新特征越多;l是代价函数中的λ
draw_boundary(power=6,l=1)
# λ==0,说明此时未加入正则化项,可能出现过拟合
draw_boundary(power=6,l=0)
# λ==100,说明此时正则化程度很大,可能出现欠拟合
draw_boundary(power=6,l=100)
13. 写在最后
本文代码主体来自于这位大佬的笔记👉吴恩达机器学习笔记,在此基础上加上了自己对于一些代码的注释从而更加方便理解,本人是新手入门,有错误在所难免,当中也有有一些问题没有解决,望各位不吝赐教。若本文对于你理解正则化逻辑回归有些许帮助,也不妨给个赞鼓励一下,你们的支持是我创作最大的动力!