《统计学习方法》之逻辑斯蒂回归代码实现

本文深入解析逻辑斯蒂回归模型,解释对数几率回归的概念,介绍如何使用极大似然估计求解参数,并提供手写数字识别的代码实现。

    逻辑斯蒂回归,又被称为对数几率回归。下面根据两个名字对模型进行解释。
    先说为什么被称为对数几率回归,什么是几率?假设一个事件发生的概率为p则事件发生的几率为p1−p\frac {p}{1-p}1pp,那么对数几率也就表示为log(p1−p)log(\frac {p}{1-p})log(1pp),现在考虑二分类问题,假设标签为0,1,p表示预测标签为1的概率,也就是p=P(y=1∣X)p=P(y=1|X)p=P(y=1X),我们用线性模型来拟合事件的对数几率,也就是
log(p1−p)=log(P(y=1∣X)1−P(y=1∣X))=wx+blog(\frac {p}{1-p})=log(\frac {P(y=1|X)}{1-P(y=1|X)})=wx+blog(1pp)=log(1P(y=1X)P(y=1X))=wx+b
通过上面的式子就得到了:
P(y=1∣X)=ewx+b1+ewx+bP(y=1|X)=\frac {e^{wx+b}} {1+e^{wx+b}}P(y=1X)=1+ewx+bewx+b

P(y=0∣X)=11+ewx+bP(y=0|X)=\frac {1} {1+e^{wx+b}}P(y=0X)=1+ewx+b1
有了上面的式子就可以根据极大似然估计求解参数w了。
    再说逻辑斯蒂回归,先计算出线性模型的值,wx+bwx+bwx+b,由于我们是要做分类问题,当然可直接选择一个阈值,通过将线性模型得到的值与阈值比较大小进行类别判断,但这样略显粗糙?(这样难以定义损失函数进行优化)。于是我们找来了逻辑函数
P(t)=11+e−tP(t)=\frac {1} {1+e^{-t}}P(t)=1+et1

将线性模型的输出值作为t输入到逻辑函数函数中,将输出作为标签为1的概率,即:
P(y=1∣X)=11+e−(wx+b)=ewx+b1+ewx+bP(y=1|X)=\frac {1}{1+e^{-(wx+b)}}=\frac {e^{wx+b}} {1+e^{wx+b}}P(y=1X)=1+e(wx+b)1=1+ewx+bewx+b

实验注意事项:实验中使用的数据为手写数字数据集,每个像素点的取值范围为[0,255],如果不对数据进行预处理,则在进行梯度更新时很容易出现溢出,针对这个问题有三种解决办法,具体见代码中的注释。

下面是代码实现部分:

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
Created on 2020/1/31 17:22
@author: phil
"""

from dataloader import load_data        # 自定义
import numpy as np
from random import shuffle
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import StandardScaler


def preprocess_data(images, labels):
    # 只保留数字为0,1的图片
    mask = (labels[:, 0] == 0)+(labels[:, 0] == 1)
    images = images[mask]
    labels = labels[mask]

    # 在输入数据中新增一列1,作为wx+b中的常数项,后面直接使用Wx表示Wx+b
    one_column = np.ones((images.shape[0], 1))
    images = np.c_[images, one_column]

    # 数据集分割,将20%数据作为测试集
    X_train, X_test, y_train, y_test = train_test_split(images, labels, test_size=0.2, random_state=42)

    # 如果直接使用原始数据,每个样本中的数据值取值范围为[0,255],计算过程会使得exp(wx)过大导致结算结果出错
    # 有三种解决办法
    # 方法1:将p(y=1|x)=exp(wx)/(1+exp(wx))改写为p(y=1|x)=1/(1+exp(-wx)),见fit函数
    # 方法2:将输入数据归一化 X = X / 255,使得属性取值范围缩到[0,1]
    # X_train = X_train / 255
    # X_test = X_test / 255
    # 方法3:将输入数据标准化,代码如下:注意测试集的标准化参数来自训练集
    # stdSca = StandardScaler()
    # X_train = stdSca.fit_transform(X_train)
    # X_test = stdSca.transform(X_test)

    return X_train, X_test, y_train, y_test


class LogisticRegression():
    def __init__(self):
        self.W = None

    def fit(self, X, y, learning_rate=0.01, epoch=10):
        # X每一行是一个样本
        n, dim = X.shape
        # 初始化参数
        self.W = np.zeros((dim, 1))
        # 这里使用随机梯度下降算法,每次选择一个样本对参数进行更新
        for i in range(epoch):
            # 打乱训练集顺序
            index = list(range(n))
            shuffle(index)
            X, y = X[index], y[index]
            for j in range(n):
                Xj = X[j].reshape(1, -1)
                yj = y[j]
                # 原始计算公式
                # e_Wx = np.exp(Xj.dot(self.W))
                # p1 = e_Wx / (1+e_Wx)
                # 避免数值溢出
                p1 = 1 / (1 + np.exp(-Xj.dot(self.W)))
                self.W += learning_rate * (yj - p1) * Xj.T

    def predict(self, X):
        preds = []
        for i in range(len(X)):
            Xi = X[i]
            # 见fit函数
            # e_Wx = np.exp(Xi.dot(self.W))
            # p1 = e_Wx / (1+e_Wx)
            p1 = 1 / (1 + np.exp(-Xi.dot(self.W)))
            if p1 >= 0.5:
                preds.append(1)
            else:
                preds.append(0)
        return np.array(preds)


if __name__ == "__main__":
    images, labels = load_data()
    X_train, X_test, y_train, y_test = preprocess_data(images, labels)
    print("train size", len(X_train))
    print("test size", len(X_test))
    model = LogisticRegression()
    model.fit(X_train, y_train)
    print("accuracy score:", accuracy_score(model.predict(X_test), y_test))

"""
Output:
    train size 10132
    test size 2533
    accuracy score: 0.9976312672720095
"""
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值