Published on

机器学习 | 逻辑回归简单操作

Authors
  • avatar
    Name
    Jiaqi Wang(Ramis)
    Twitter

[ 最后更新 于 2024-06-26 ] 祝贺失踪鹅仔回归!(??)

逻辑回归 简单代码实现

这篇练习记录一下logistic regression的代码实操以及简单的数据分析的一般流程

使用的是Kaggle上的数据:https://www.kaggle.com/datasets/fsadjadpour/binary/data

Graduate Admission 研究生录取预测

1. Data Import and Exploration

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

通过pandas读取csv文件, 这里也可以使用csv的url的链接

raw_df=pd.read_csv("Graduate Admission.csv")

首先熟悉数据,用head查看前十条数据 用.tail(n)可以查看最后n条,用describe()返回数据基本的统计特征值概览

raw_df.head(10)
raw_df.describe() 

尝试用seaborn的boxplot画了一个箱型图来观察一下录取结果和GPA,GRE以及排名之间的联系。可以看出GRE和GPA都和录取结果呈一定的正相关,而虽然不知道具体rank所指是什么为什么只有1到3,但是排名在1到2之间录取结果是几乎是板上钉钉。因此这三个量都应当可以作为逻辑回归的参数。

fig, axes = plt.subplots(1, 3, figsize=(12, 6))
colors = ["#ed45b2", "#87e8c1"] 
for i in range(3):
    sns.boxplot(x='admit', y=raw_df.columns[i+1], data=raw_df, ax=axes[i],palette=colors)
    f_heading=raw_df.columns[i+1].upper()
    axes[i].set_title('Admission Status by '+f_heading)
    axes[i].set_xlabel('Admission Status (0: Not Admitted, 1: Admitted)')
    axes[i].set_ylabel(raw_df.columns[i+1])

# Adjust layout
plt.tight_layout()
plt.show()
plotbox

2. 数据预处理 Data Pre-Processing

此时我们将pandas中的数据转入numpy:

X_input = raw_df[['gre', 'gpa', 'rank']].to_numpy()
Y_input = raw_df['admit'].to_numpy()    

进行标准化:


def z_score(data):
    mean=np.mean(data,axis=0) #注意此处 axis=0 表示按照列汇总——>也就是说 mean的结果与行格式相同
    std=np.std(data,axis=0)
    return (data-mean)/std

X_train=z_score(X_input)
Y_train=Y_input

可以看到此时的训练集X数字都按比例放缩了,这便于接下来进行梯度递降。

    X_train

    array([[-1.80026271,  0.57907221,  0.54596793],
           [ 0.62666824,  0.73692924,  0.54596793],
           [ 1.84013372,  1.60514292, -1.57429586],
           ...,
           [-1.10685387, -1.99925931, -0.51416397],
           [ 0.97337266,  0.68431023, -0.51416397],
           [ 0.10661161,  1.31573836,  0.54596793]])

3. 梯度递降法实现逻辑回归

要开始梯度递降首先要准备三个函数:

其一是此前介绍过的sigmoid函数,这是逻辑回归的核心函数,用来估算概率。

def sigmoid(z):
    return(1/(1+np.exp(-z)))

其二,定义一个compute_cost函数来求cost function的值。因为每次梯度下降更新w和b之后都要重新计算一次cost以便比较。

def compute_cost(X,y,w,b,*argv):
    m,n=X.shape
    cost=0
    for i in range(0,m):
        z=np.dot(w,X[i])+b
        f=sigmoid(z)
        loss=-y[i]*np.log(f)-(1-y[i])*np.log(1-f)
        cost+=loss
    cost=cost/m
    return cost

最后还要定义compute_graduate函数来求每次下降时要更新的梯度。 需要注意,此处w是向量,更新的过程中对向量的每一维都需要分别求值。

def compute_gradient(X,y,w,b,*argv):
    m,n=X.shape
    dj_dw=np.zeros(w.shape)
    dj_db=0
    for i in range(0,m):
        z_wb=np.dot(w,X[i])+b
        f_wb=sigmoid(z_wb)
        dj_db_i=f_wb-y[i]
        dj_db+=dj_db_i
        for j in range(n):
            dj_dw[j]+=dj_db_i*X[i][j]
    dj_dw=dj_dw/m
    dj_db=dj_db/m
    return dj_dw,dj_db
        

有了这三个函数,可以开始进行梯度递降的常规操作,这个过程和之前线性函数中的没什么不同。

def gradient_descent(X,y,w_in,b_in,cost_function,gradient_function,alpha,itrs,lambda_):
    m=len(X)
    J_history=[]
    w_history=[]
    for i in range(itrs):
        if i<100000:
            cost=compute_cost(X,y,w_in,b_in,lambda_)
            J_history.append(cost)
        if (i%math.ceil(itrs/10)==0) or (i==itrs-1):
            w_history.append(w_in)
            print(f"Iteration {i:4}: Cost {float(J_history[-1]):8.2f} w:{w_in}")
        dj_dw,dj_db=gradient_function(X,y,w_in,b_in,lambda_)
        w_in=w_in-alpha*dj_dw
        b_in=b_in-alpha*dj_db
    return w_in,b_in,J_history,w_history

初始化后开始运算。

np.random.seed(1)
initial_w = 0.01 * (np.random.rand(3) - 0.5)
initial_b = 0
# Some gradient descent settings
iterations = 10000
alpha = 0.01

w,b, J_history,w_history = gradient_descent(X_train,Y_train, initial_w, initial_b, compute_cost, compute_gradient, alpha, iterations, 0)

#返回结果:

    Iteration    0: Cost     0.69 w:[-0.00082978  0.00220324 -0.00499886]
    Iteration 1000: Cost     0.58 w:[ 0.24319593  0.25340642 -0.43003393]
    Iteration 2000: Cost     0.57 w:[ 0.26287299  0.28477028 -0.50475713]
    Iteration 3000: Cost     0.57 w:[ 0.26502824  0.29237737 -0.52258257]
    Iteration 4000: Cost     0.57 w:[ 0.26496915  0.2944698  -0.52689643]
    Iteration 5000: Cost     0.57 w:[ 0.2647973   0.29507623 -0.5279349 ]
    Iteration 6000: Cost     0.57 w:[ 0.26471095  0.29525696 -0.52818506]
    Iteration 7000: Cost     0.57 w:[ 0.26467699  0.29531164 -0.52824578]
    Iteration 8000: Cost     0.57 w:[ 0.26466489  0.29532831 -0.52826072]
    Iteration 9000: Cost     0.57 w:[ 0.26466079  0.29533342 -0.52826447]
    Iteration 9999: Cost     0.57 w:[ 0.26465945  0.29533499 -0.52826542]

可以看到结果停留在了cost大约在0.57的位置。

def predict(X, w, b): 
    
    m, n = X.shape   
    p = np.zeros(m)
   
    for i in range(m):   
        z_wb = 0
   
        for j in range(n): 
            z_wb += X[i][j]*w[j]

        z_wb += b
        f_wb = sigmoid(z_wb)
        if f_wb>=0.5: p[i]=1 
        else: p[i]=0
    return p

我们可以计算y hat的值来看看这个模型的正确率是多少:

p = predict(X_train, w,b)
print('Train Accuracy: %f'%(np.mean(p == Y_train) * 100))

Train Accuracy: 70.500000

小结:

在重度拖延中提交这一篇。本来后面还想写一写正则化和模型选用的问题,但是感到还有很多要看的东西,所以干脆推到下一篇吧~(?)

这次的代码是按照课程笔记以及理解一点点纯手敲再对照讲义debug的,感觉很简单实际写下来还是需要很多细心呀。再次感叹还是做调包侠舒服…可惜这段时间还要先把numpy和pandas用熟了。

最近也已经把神经网络最简单的原理篇看完了,感觉这个东西实在太暴力啦。真就是到了算力到了就是可以为所欲为的世界呢…或许这周末也可以开始动笔写原理篇了。

再有就是想整理一个pandas实用方法合集;) 最近的to do也太多啦…跑走


Cat

此处 碎碎念

-鹅仔 2024/06/26 21:08