正则化逻辑回归【Python详细注释实现】

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()

image-20221007204614543

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()

image-20221007204812155

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 x1x2x1x2x12x22...X1nX2n

因为更多的特征进行逻辑回归时,得到的分割线可以是任意高阶函数的形状

如下,原本只有x_1x_2两个特征,通过最高6次方的多项式组合,最终生成了=1+2+3+4+5+6+7=28个特征项

feature_map

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次方,乘积组成新的特征值。 x1ipx2p:意思为x1ip次方乘x2p次方,乘积组成新的特征值。我们的数据只有两列,因此 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()

image-20221007205131683

4. 正则化代价函数

代价函数 c o s t cost cost可以用来评估一个模型预测的好坏程度, c o s t cost cost越小,说明预测值与真实值的误差越小。

但实际上,我们在评估一个模型好坏程度时,还需要考虑过拟合的问题,即 c o s t cost cost在训练集中的表现堪称完美,然而在测试集中的表现却不如预期那样好。这便是发生了过拟合的情况,太过于专注将训练集中的正确结果进行囊括,从而拟合出一个特别复杂的预测函数,而这一函数往往只在单例中有效而不具有普适性。

image-20221006204841855

那么如何防止过拟合呢?

研究发现,当 θ \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,即在决策边界中只负责函数的上下移动,对于形成复杂图形毫无影响,对其正则化没有意义,所以可以不利用正则化去控制其处于一个较小的值【本人的猜测,仅供参考】

regularized_cost

如果 λ \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)

image-20221007205357367

# 由于初始θ值全为0,因此正则化项也为0,所以代价值与为正则化之前一致
regularized_cost(theta,X,Y)

image-20221007205414086

7. 正则化梯度

J ( θ ) J(\theta) J(θ)正则化后,对于,其对 θ j \theta_j θj的偏导会发生一定变化【简单的求导知识】,结果如下:

regularized_gradient

对应的, θ 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)

image-20221007205553901

# 加了正则项后各θ的偏导【由于初始θ为0,因此加不加正则项,偏导都一样】
regularized_gradient(theta,X,Y)

image-20221007205612709

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)

image-20221007210029036

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)

image-20221007210952651

# λ==0,说明此时未加入正则化项,可能出现过拟合
draw_boundary(power=6,l=0)

image-20221007211018465

# λ==100,说明此时正则化程度很大,可能出现欠拟合
draw_boundary(power=6,l=100)

image-20221007211044646

13. 写在最后

本文代码主体来自于这位大佬的笔记👉吴恩达机器学习笔记,在此基础上加上了自己对于一些代码的注释从而更加方便理解,本人是新手入门,有错误在所难免,当中也有有一些问题没有解决,望各位不吝赐教。若本文对于你理解正则化逻辑回归有些许帮助,也不妨给个赞鼓励一下,你们的支持是我创作最大的动力!

  • 6
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值