机器学习:逻辑回归

虽然名字里带回归,但实际上是一种分类方法,主要用于两分类问题,即只有两种分类

优点:计算代价不高,易于理解和实现
缺点:容易欠拟合,分类精度可能不高

原理

线性回归函数
  z=f(X)=XW
  其中
    X 是特征值
    W 是回归系数
  XW 都是向量,可展开为
    z=XW=X0W0+X1W1+...+XnWn
  线性方程其实应该是
    z=XW+b
  为此这里固定
    X0=1
    W0=b
  其他 X 值才是用户输入,这样变成两个向量相乘方便计算

逻辑回归函数 (Sigmoid 函数)
  
  y=g(z)=11+ez
  
  该函数模拟阶跃函数 (在某个跳跃点从 0 瞬间跳到 1,跳跃点两边的值固定为 0 和 1)
  可以得出
    y={0.12z=20.5z=00.88z=2
  且满足
    g(z)+g(z)=1
  
  在 z 轴比较长的情况下看起来就像跳跃点为 0 的阶跃函数

  

分类器
  结合线性回归函数和逻辑回归函数
  
    y=g(z)=11+ez=11+eXW
  
  对特征向量 X 进行计算,得出 0~1 之间的值
  大于 0.5 属于分类 1,小于 0.5 属于分类 0
  所以逻辑回归也可以被看成是一种概率估计

训练分类器
  通过最优化算法(通常是梯度下降算法),寻找最佳回归系数 W 的值

梯度下降算法

要找到某函数的最小值,最好的方法是沿着该函数的梯度方向向下探寻
就像等高线,选择最高点的最快方法就是不断沿着梯度走
  
    
  
逻辑回归函数 g(z) 的结果实际是一个 (0,1) 区间的概率估计
为此损失函数采取逻辑损失函数
  
  L=(y)log(g(z))(1y)log(1g(z))
  
y=0 时,L=log(1g(z))g(z)范围是(0,1),对应 L 范围是 (0,)
y=1 时,L=log(g(z))g(z)范围是(0,1),对应 L 范围是 (,0)

梯度下降算法的目的就是找最佳的 w 参数,使得所有样本的 L 值的总和最小
  
  L(w)=i=0n( (y)log(g(z))+(1y)log(1g(z)) )
  
梯度算法的迭代公式如下
  w:=wαL(w)
其中
  L(w) 是函数 Lw 处的梯度,代表使 L 值变化率最大的方向
  α 是步长,即沿梯度方向变化的大小,必须取一个很小的值

该公式一直被迭代执行,即w 沿着梯度方向不断减少,使L值以最快的速率不断下降
直至达到某个停止条件,如迭代次数达到阈值,或L值达到某个可以允许的误差范围
进一步了解梯度,参考 导数、偏导数、方向导数、梯度、梯度下降
  
已知对下列函数求导
  g(z)=11+ez
  
  f(x)=log(x)
可得
  g(z)z=g(z)(1g(z))
  
  f(x)x=1x
  
梯度在每个轴上的分量是函数在该轴的偏导数
  
  L(w)=(L(w)w0,...,L(w)wj,...,L(w)wm)
  
结合以上内容求解 L(w) 的偏导数(i 代表样本,共 n 个,j 代表特征,共 m 个)

  L(w)wj=i=0n(((y)log(g(z)))wj+((1y)log(1g(z)))wj)
  
      =i=0n(y1g(z)g(z)wj(1y)11g(z)g(z)wj)
  
      =i=0n((yg(z)(1y)1g(z))g(z)wj)
  
      =i=0n((yg(z)(1y)1g(z))g(z)(1g(z))zwj)
  
      =i=0n((y(1g(z))(1y)g(z))xwwj)
  
      =i=0n((yg(z))xwwj)
  
      =i=0n((yg(z))xj)
  
      =i=0n(e(i)xj(i))
  
      =[xj(0)...xj(i)...xj(n)][e0...ei...en]

将所有偏导组成梯度

   L(w)=(L(w)w0,...,L(w)wj,...,L(w)wm)
  
      =[x0(0)...x0(i)...x0(n)xj(0)...xj(i)...xj(n)xm(0)...xm(i)...xm(n)][e0...ei...en]
  
      =XTE

进一步求得梯度下降公式

  W=WαL(W)
  
     =W+αXTE
  
     =W+αXT(Yg(XW))

其中
  样本集有 n 条数据,m 个特征
  X(nm) 矩阵
  W(m1) 矩阵
  Y(n1) 矩阵
  α>0

随机梯度下降(SGD - Stochastic Gradient Descent)

可以看到,每一次迭代都要对所有样本计算,计算量太大
可以改为每一次迭代都随机取一部分样本计算就可以

代码

# coding=utf-8
import numpy as np
import random


# 阶跃函数
def sigmoid(z):
    return 1.0/(1+np.exp(-z))


# 梯度下降算法
# 根据样本(X,Y) 算法最佳的 W
def gradDescent(sampleData, classLabels):
    """
    sampleData  - 样本特征,(n,m) 的二维数组,n 是样本数,m 是特征数
                  每行的第一个值 X0 固定为 1,从 X1 开始才是真正的特征值,目的是简化向量的计算
                   y = x1*w1 + ... + xm*wm + b
                     = x1*w1 + ... + xm*wm + x0w0
                     = XW

    classLabels - 样本标签,(1,n) 的一维数组
    """

    # 转为 NumPy 矩阵
    dataMatrix = np.mat(sampleData)

    # 将 (1,n) 转为 (n,1) 方便后面的矩阵计算
    labelMatrix = np.mat(classLabels).transpose()

    # n 个样本,m 个特征值
    n, m = np.shape(dataMatrix)

    # 梯度下降的步长
    alpha = 0.001

    # 最大迭代次数
    maxCycles = 500

    # 初始化 W 为 (m,1) 数组, 默认值为 1
    weights = np.ones((m, 1))

    # 迭代
    for k in range(maxCycles):
        # (n,m) 矩阵乘以 (m,1) 矩阵,得到 (n,1) 矩阵,再通过逻辑回归函数得到样本的 Y
        h = sigmoid(dataMatrix*weights)

        # 两个 (n,1) 矩阵,得到每个样本的误差
        error = (labelMatrix - h)

        # w = w + a*(X^T)*(Y-g(XW))
        #   = w + a*(X^T)*E
        weights = weights + alpha * dataMatrix.transpose() * error

    return weights


# 分类
def classify(data, weights):
    dataMatrix = np.mat(data)
    resultMatrix = sigmoid(dataMatrix * weights) > 0.5
    
    """
    for result in resultMatrix:
        print(result.item())
    """
    return resultMatrix


# 随机梯度
def stocGradDescent0(sampleData, classLabels):
    dataMatrix = np.mat(sampleData)
    labelMatrix = np.mat(classLabels).transpose()

    n, m = np.shape(dataMatrix)
    alpha = 0.01
    weights = np.ones((m, 1))

    for i in range(n):
        # 每次迭代只取一个样本,迭代次数为样本个数
        h = sigmoid(dataMatrix[i]*weights)
        error = labelMatrix[i] - h
        weights = weights + alpha * dataMatrix[i].transpose() * error

    return weights


# 改进的随机梯度
def stocGradDescent1(sampleData, classLabels, numIter=150):
    dataMatrix = np.mat(sampleData)
    labelMatrix = np.mat(classLabels).transpose()

    n, m = np.shape(dataMatrix)
    weights = np.ones((m, 1))

    # 自己选择迭代次数
    for j in range(numIter):
        dataIndex = range(n)

        # 每次迭代又迭代了每一个样本
        for i in range(n):
            # 每次迭代都改变步长,0.0001 用于防止出现 0 的情况
            alpha = 4/(1.0+j+i)+0.0001

            # 随机选择一个样本
            randIndex = int(random.uniform(0, len(dataIndex)))

            h = sigmoid(dataMatrix[randIndex]*weights)
            error = labelMatrix[randIndex] - h

            # 计算新的 W
            weights = weights + alpha * dataMatrix[randIndex].transpose() * error

            # 删除该样本下标
            del(dataIndex[randIndex])

    return weights
 
 


posted @   moon~light  阅读(187)  评论(0编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 提示词工程——AI应用必不可少的技术
· 地球OL攻略 —— 某应届生求职总结
· 字符编码:从基础到乱码解决
· SpringCloud带你走进微服务的世界
点击右上角即可分享
微信分享提示