逻辑回归--理论介绍及代码实践

引言

在机器学习和统计建模中,逻辑回归是一种广泛使用的分类算法,特别是在二分类问题中。尽管它的名字中有“回归”二字,但它实际上是一种分类算法,因为它的输出是离散的类别标签。

什么是逻辑回归?

逻辑回归是一种预测分析方法,用于估计一个事件的发生概率。它通过使用逻辑函数将线性回归的输出映射到0或1(或更普遍的,到0和1之间的概率)来实现这一点。

逻辑回归的原理

逻辑回归的核心原理是将线性回归模型与逻辑函数结合起来,以预测一个事件发生的概率。具体步骤如下:

  1. 线性模型:首先,逻辑回归构建一个线性模型,该模型的输出是一个线性组合,即 L(y, \hat{y}) = -[y \log(\hat{y}) + (1 - y) \log(1 - \hat{y})]

  2. 逻辑函数:然后,将这个线性组合𝑧 通过逻辑函数(Sigmoid函数)转换为概率 𝑝,即 p(y=1|x) = \frac{1}{1 + e^{-z}}

  3. 概率解释:逻辑回归的输出是一个介于0和1之间的概率值,这使得它非常适合于表示事件发生的可能性。

  4. 决策边界:逻辑回归通过设定一个阈值(通常是0.5)来确定分类结果。如果 𝑝(𝑦=1∣𝑥) 大于阈值,则预测 𝑦=1,否则预测 𝑦=0。

逻辑回归模型

逻辑回归模型的数学表达式可以表示为:

p(y=1|x) = \frac{1}{1 + e^{-(\beta_0 + \beta_1x_1 + \beta_2x_2 + \ldots + \beta_nx_n)}}

其中:

  • 𝑝(𝑦=1∣𝑥)是给定输入 𝑥 时,输出为1的概率。
  • 𝛽0,𝛽1,...,𝛽𝑛是模型参数。
  • 𝑒 是自然对数的底数。

这个函数被称为Sigmoid函数或逻辑函数,它将任何实数映射到(0,1)区间,非常适合表示概率。

Sigmoid函数图像

优化算法

为了找到最佳参数 𝛽 ,我们通常使用梯度下降或其变体(如随机梯度下降)来最小化损失函数。

逻辑回归的优缺点

优点

  1. 易于理解和实现:逻辑回归模型相对简单,容易理解和实现,对于初学者来说是一个很好的起点。

  2. 快速计算:逻辑回归的计算相对快速,特别是当使用现代优化算法时。

  3. 概率解释:输出可以解释为概率,这在许多应用场景中非常有用,如风险评估和决策制定。

  4. 适用于大规模数据集:逻辑回归可以很好地扩展到大规模数据集。

  5. 特征工程友好:逻辑回归对特征工程友好,可以通过添加交互项、多项式项等来增强模型。

缺点

  1. 假设线性关系:逻辑回归假设特征与输出的对数几率是线性关系,这可能不适用于所有数据。

  2. 容易过拟合:如果模型中有太多的特征,逻辑回归可能会过拟合。

  3. 对异常值敏感:逻辑回归对异常值比较敏感,这可能会影响模型的性能。

  4. 不适合多分类问题:标准逻辑回归模型是为二分类问题设计的,虽然可以通过一对多(One-vs-All)的方法扩展到多分类问题,但这种方法可能会增加计算复杂性。

  5. 对特征尺度敏感:逻辑回归对特征的尺度比较敏感,因此在应用之前通常需要进行特征标准化或归一化。

逻辑回归的应用

逻辑回归在多个领域都有广泛的应用,包括:

  • 信用评分:预测个人违约的概率。
  • 医疗诊断:根据症状预测疾病的存在。
  • 垃圾邮件过滤:识别电子邮件是否为垃圾邮件。
  • 推荐系统:预测用户对产品或服务的偏好。

代码实践

逻辑回归相关部分:

class LogisticRegression:
    def __init__(self, lr=0.05, num_step=1000):
        self.lr = lr
        self.num_step = num_step
        self.weights = None
        self.bias = None

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

    def fit(self, X, y):
        num_samples, num_features = X.shape
        # 初始化权重和偏置 w全部置为1 b置为0
        self.weights = np.ones(num_features)
        self.bias = 0

        # 梯度下降优化
        for _ in range(self.num_step):
            # 得到线性模型预测值,结果用sigmod函数映射到(0, 1)上作为预测的概率
            linear_model = np.dot(X, self.weights) + self.bias
            y_predicted = self.sigmoid(linear_model)

            # 计算梯度
            dw = (1 / num_samples) * np.dot(X.T, (y_predicted - y))
            db = (1 / num_samples) * np.sum(y_predicted - y)

            # 更新权重和偏置
            self.weights -= self.lr * dw
            self.bias -= self.lr * db

    def predict(self, X):
        linear_model = np.dot(X, self.weights) + self.bias
        y_predicted = self.sigmoid(linear_model)
        # 预测概率大于0.5,则预测结果为1
        pred = [1 if i > 0.5 else 0 for i in y_predicted]
        return np.array(pred)

 数据集部分: 

def generate_data(num_samples=100):

    X = np.random.rand(num_samples, 2) * 10
  
    y = np.zeros(num_samples)
    x1, x2 = 0, 0
    for i in range(num_samples):
        x1 += X[i, 0]
        x2 += X[i, 1]
    x1 /= num_samples
    x2 /= num_samples
    for i in range(num_samples):
        if X[i, 0] * 10 + X[i, 1] * 5 > 100:
            y[i]=1
    return X, y
 
def split_data(X, y, rate):
    data_len = y.size
    y.resize((data_len, 1))
    data = numpy.concatenate((X, y), axis=1)
 
    numpy.random.shuffle(data)
    test_len = data_len - int(rate*data_len)
 
    return data[:test_len, :2], data[:test_len, 2], data[test_len:, :2], data[test_len:, 2]

 

 

 

 

import numpy as np
import matplotlib.pyplot as plt
from pylab import mpl

mpl.rcParams["font.sans-serif"] = ["SimHei"]


class LogisticRegression:
    def __init__(self, lr=0.05, num_step=1000):
        self.lr = lr
        self.num_step = num_step
        self.weights = None
        self.bias = None

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

    def fit(self, X, y):
        num_samples, num_features = X.shape
        # 初始化权重和偏置 w全部置为1 b置为0
        self.weights = np.ones(num_features)
        self.bias = 0

        # 梯度下降优化
        for _ in range(self.num_step):
            # 得到线性模型预测值,结果用sigmod函数映射到(0, 1)上作为预测的概率
            linear_model = np.dot(X, self.weights) + self.bias
            y_predicted = self.sigmoid(linear_model)

            # 计算梯度
            dw = (1 / num_samples) * np.dot(X.T, (y_predicted - y))
            db = (1 / num_samples) * np.sum(y_predicted - y)

            # 更新权重和偏置
            self.weights -= self.lr * dw
            self.bias -= self.lr * db

    def predict(self, X):
        linear_model = np.dot(X, self.weights) + self.bias
        y_predicted = self.sigmoid(linear_model)
        # 预测概率大于0.5,则预测结果为1
        pred = [1 if i > 0.5 else 0 for i in y_predicted]
        return np.array(pred)


def generate_data(num_samples=100):
    # 生成随机特征
    X = np.random.rand(num_samples, 2) * 10

    # 根据特定规则生成标签,在中心点的右上角全部为1
    y = np.zeros(num_samples)
    x1, x2 = 0, 0
    for i in range(num_samples):
        x1 += X[i, 0]
        x2 += X[i, 1]
    x1 /= num_samples
    x2 /= num_samples
    for i in range(num_samples):
        if X[i, 0] * 10 + X[i, 1] * 5 > 100:
            y[i] = 1
    return X, y


def split_data(X, y, rate):
    data_len = y.size
    y.resize((data_len, 1))
    data = np.concatenate((X, y), axis=1)

    np.random.shuffle(data)
    test_len = data_len - int(rate * data_len)

    return data[:test_len, :2], data[:test_len, 2], data[test_len:, :2], data[test_len:, 2]


def plot_data(X, y, title='data'):
    plt.scatter(X[y == 0][:, 0], X[y == 0][:, 1], color='blue', label='类别 0')
    plt.scatter(X[y == 1][:, 0], X[y == 1][:, 1], color='red', label='类别 1')
    plt.xlabel('X1')
    plt.ylabel('X2')
    plt.title(title)
    plt.legend()
    plt.show()


def plot_data_test(X, y, pred, title='data'):
    right_X, right_y = X[y == pred], pred[y == pred]
    wrong_X, wrong_y = X[y != pred], pred[y != pred]

    plt.scatter(right_X[right_y == 0][:, 0], right_X[right_y == 0][:, 1], color='blue', label='类别 0')
    plt.scatter(right_X[right_y == 1][:, 0], right_X[right_y == 1][:, 1], color='red', label='类别 1')

    plt.scatter(wrong_X[wrong_y == 0][:, 0], wrong_X[wrong_y == 0][:, 1], color='blue', marker='x',
                label='类别 0(真实为类别 1)')
    plt.scatter(wrong_X[wrong_y == 1][:, 0], wrong_X[wrong_y == 1][:, 1], color='red', marker='x',
                label='类别 1(真实为类别 0)')

    plt.xlabel('X1')
    plt.ylabel('X2')
    plt.title(title)
    plt.legend()
    plt.show()


def plot_decision_boundary(X, y, pred, model):
    # 确定坐标轴范围
    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1

    # 生成网格点坐标
    xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
                         np.arange(y_min, y_max, 0.1))

    # 使用模型进行预测
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)

    # 绘制决策边界
    plt.contourf(xx, yy, Z, alpha=0.4)
    # plt.scatter(X[:, 0], X[:, 1], c=y, s=20, edgecolors='k')

    right_X, right_y = X[y == pred], pred[y == pred]
    wrong_X, wrong_y = X[y != pred], pred[y != pred]

    plt.scatter(right_X[right_y == 0][:, 0], right_X[right_y == 0][:, 1], color='blue', label='类别 0')
    plt.scatter(right_X[right_y == 1][:, 0], right_X[right_y == 1][:, 1], color='red', label='类别 1')

    plt.scatter(wrong_X[wrong_y == 0][:, 0], wrong_X[wrong_y == 0][:, 1], color='blue', marker='x',
                label='类别 0(真实为类别 1)')
    plt.scatter(wrong_X[wrong_y == 1][:, 0], wrong_X[wrong_y == 1][:, 1], color='red', marker='x',
                label='类别 1(真实为类别 0)')

    plt.xlabel('X1')
    plt.ylabel('X2')
    plt.title('决策边界')
    plt.show()


num_samples, rate = 1000, 0.1

# 构造数据集
X, y = generate_data(num_samples)
train_X, train_y, test_X, test_y = split_data(X, y, rate)
y.resize(y.size)
plot_data(X, y, "生成的数据分布情况")
plot_data(train_X, train_y, "训练数据分布情况")
plot_data(test_X, test_y, "测试数据分布情况")

classifier = LogisticRegression()
classifier.fit(train_X, train_y)

pred = classifier.predict(test_X)
result = pred == test_y
plot_data_test(test_X, test_y, pred, f"精确率:{result.sum() * 100 / result.size}%")
plot_decision_boundary(test_X, test_y, pred, classifier)

分类结果:

 

 

结论

逻辑回归是一种强大的分类算法,适用于各种二分类问题。它简单、易于理解和实现,并且对于初学者来说是一个很好的起点。然而,它也有一些局限性,比如对特征的线性关系假设较强,对于复杂的非线性问题可能需要更高级的模型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值