5.3 用PyTorch实现Logistic回归

一、数据准备

Logistic回归常用于解决二分类问题。

为了便于描述,我们分别从两个多元高斯分布 N₁(μ₁,Σ₁ )、N₂(μ₂,Σ₂)中生成数据 x₁ 和 x₂,这两个多元高斯分布分别表示两个类别,分别设置其标签为 y₁ 和 y₂。

PyTorch 的 torch.distributions 提供了 MultivariateNormal 构建多元高斯分布

下面代码设置两组不同的均值向量和协方差矩阵,μ₁(mul)和 μ₂(mul)是二维均值向量,Σ₁(sigmal)和Σ₂(sigma2)是2*2的协方差矩阵。

前面定义的均值向量和协方差矩阵作为差数传入 MultivariateNormal,就实例化了两个多元高斯分布 m₁和 m₂。

调用 m₁和 m₂ 的sample方法分别生成100个样本。

设置样本对应的标签 y,分别用 0 和 1 表示不同高斯分布的数据,也就是正样本和负样本。

使用 cat 函数将 x₁(m1)和 x₂(m2)组合在一起。

打乱样本和标签的顺序,将数据重新随机排列,这是十分重要的步骤,否则算法的每次迭代只会学习到同一个类别的信息,容易造成模型过拟合。 

将生成的样本用 plt.scatter 绘制出来。

绘制结果如图:

可以明显的看出多元高斯分布生成的样本聚成了两个簇,并且簇的中心分别处于不同的位置(多元高斯分布的均值向量决定了其位置)。

右上角簇的样本分布比较稀疏,而左下角簇的样本分布紧凑(多元高斯分布的协方差矩阵决定了分布形状)。

【可调整

mu1 = -3 * torch.ones(2)

mu2 = 3 * torch.ones(2)

的参数,观察变化!

二、线性方程

Logistic回归用输入变量x的线性函数表示样本为正类的对数概率。nn.Linear 实现了 y = xAᵀ + b,我们可以直接调用它来实现Logistic回归的线性部分。

定义线性模型的输入维度D_in 和输出维度 D_out,因为前面定义的多元高斯分布 m₁(m1)和 m₂(m2)产生的变量是二维的,所以线性模型的输入维度应该定义为D_in = 2 ;而Logistic回归是二分类模型,预测的是变量为正类的概率,所以输出的维度应该为D_in = 1。

实例化了nn.Linear,将线性模型应用到数据 x 上,得到计算结果output。

Linear的初始参数是随机设置的,可以调用Linear.weight 和 Linear.bias 获取线性模型的参数。

输出输入的变量x,模型参数weight和bias,以及计算结果output的维度。

定义线性模型my_linear,将my_linear的计算结果和PyTorch的计算结果output进行比较,可以发现他们是一致的。

输出:

三、激活函数

前面介绍了nn.Linear可用于实现线性模型,除此之外,torch.nn还提供了机器学习中常用的激活函数。当Logistic回归用于二分类问题时,使用sigmoid函数将线性模型的计算结果映射到0和1之间,得到的计算结果作为样本为正类的置信概率。nn.Sigmoid提供了sigmoid函数的计算,在使用时,将Sigmoid类实例化,再将需要计算的变量作为参数传递给实例化的对象。

输出:

【def my_sigmoid(x):

        x = 1 / (1 + torch.exp(-x))

        return x手动实现sigmoid函数;

print(torch.sum(sigmoid(output) - sigmoid_(output)))通过PyTorch验证我们的实现结果,其结果一致】

四、损失函数

1、

Logistic回归使用交叉熵作为损失函数

PyTorch的torch.nn提供了许多标准的损失函数,我们可以直接使用 nn.BCELoss 计算二值交叉熵损失。

调用BCELoss来计算我们实现Logistic回归模型的输出结果sigmoid(output)和数据的标签y。

自定义二值交叉熵函数

将my_loss 和PyTorch的BCELoss进行比较,发现其结果一致。

2、

前面的代码中,我们使用了torch.nn包中的线性模型nn.Linear、激活函数nn.Softmax、损失函数nn.BCELoss,他们都继承自nn.Module类。

而在PyTorch中,我们通过继承nn.Module来构建我们自己的模型。

下面用nn.Module来实现Logistic回归。

输出:

3、

当通过继承nn.Module实现自己的模型时,forward方法是必须被子类覆写的,

在forward内部应当定义每次调用模型时执行的计算。

从代码中可以看出,nn.Module类的主要作用就是接收Tensor然后计算并返回结果。

在一个Module中,还可以嵌套其他的Module,被嵌套的Module的属性就可以被自动获取,比如可以调用nn.Module.parameters方法获取Module所有保留的参数,调用nn.Module.to方法将模型的参数放置到GPU上等。

输出:

五、优化算法

Logistic回归通常采用梯度下降法优化目标函数。

PyTorch的torch.optim包实现了大多数常用的优化算法,使用起来非常简单。

首先构建一个优化器,在构建时,需要将学习的参数传入,然后传入优化器需要的参数,比如学习率。

构建完优化器,就可以迭代地对模型进行训练,

两个步骤:

(1)调用损失函数的backward方法计算模型的梯度

(1)调用优化器的step方法更新模型的参数。

需要注意:应当提前调用优化器的zero_grad方法清空参数的梯度。

六、模型可视化

Logistic回归模型的判决边界在高维空间是一个超平面,而我们的数据集是二维的,所以判决边界只是平面内的一条直线,在线的一侧被预测为正类,另一侧被预测为负类。下面我们实现draw_decision_boundary函数。

它接收线性模型的参数w和b,以及数据集x。

绘制判决边界的方法十分简单。

,只需要计算一些数据在线性模型的映射值,然后调用plt.plot绘制线条即可。如下图:

七、代码:

import matplotlib.pyplot as plt
import numpy as np
import torch
from torch import nn, sigmoid_, optim
from torch.distributions import MultivariateNormal

###
#设置两组不同的均值向量和协方差矩阵
mu1 = -3 * torch.ones(2)
mu2 = 3 * torch.ones(2)
sigma1 = torch.eye(2) * 0.5
sigma2 = torch.eye(2) * 2

#各从两个多元高斯分布中生成100个样本
m1 = MultivariateNormal(mu1,sigma1)
m2 = MultivariateNormal(mu2,sigma2)
x1 = m1.sample((100,))
x2 = m2.sample((100,))

#设置正负样本的标签
y = torch.zeros((200,1))
y[100:] = 1

#组合、打乱样本
x = torch.cat((x1, x2), dim=0)
idx = np.random.permutation(len(x))
x = x[idx]
y = y[idx]

#绘制样本
plt.scatter(x1.numpy()[:,0],x1.numpy()[:,1])
plt.scatter(x2.numpy()[:,0],x2.numpy()[:,1])
plt.show()

###
D_in , D_out = 2,1
linear = nn.Linear(D_in,D_out,bias=True)
output = linear(x)

print(x.shape , linear.weight.shape , linear.bias.shape , output.shape)

def my_linear(x,w,b):
    return torch.mm(x,w.t()) + b
torch.sum((output - my_linear(x,linear.weight,linear.bias)))


###
sigmoid = nn.Sigmoid()
scores = sigmoid(output)

def my_sigmoid(x):
    x = 1 / (1 + torch.exp(-x))
    return x

print(torch.sum(sigmoid(output) - sigmoid_(output)))


#
loss = nn.BCELoss()
loss(sigmoid(output),y)

def my_loss(x,y):
    loss = - torch.mean(torch.log(x)*y+torch.log(1-x)*(1-y))
    return loss

print(loss(sigmoid(output),y) - my_loss(sigmoid_(output),y))


#
class LogisticRegression(nn.Module):
    def __init__(self,D_in):
        super(LogisticRegression,self).__init__()
        self.linear = nn.Linear(D_in,1)
        self.sigmoid = nn.Sigmoid()
    def forward(self,x):
        x = self.linear(x)
        output = self.sigmoid(x)
        return output

lr_model = LogisticRegression(2)
loss = nn.BCELoss()
print(loss(lr_model(x),y))

#
class MyModel(nn.Module):
    def __init__(self):
        super(MyModel,self).__init__()
        self.linear1 = nn.Linear(1,1,bias=False)
        self.linear2 = nn.Linear(1,1,bias=False)
    def forward(self):
        pass

for param in MyModel().parameters():
    print(param)


###
    from torch import optim
    optimizer = optim.SGD(lr_model.parameters(),lr = 0.03)

batch_size = 10
iters = 10
#for input,target in dataset:
for _ in range(iters):
    for i in range(int(len(x)/batch_size)):
        input = x[i*batch_size:(i+1)*batch_size]
        target = y[i*batch_size:(i+1)*batch_size]
        optimizer.zero_grad()
        output = lr_model(input)
        l = loss(output,target)
        l.backward()
        optimizer.step()

#测试
output = lr_model(x)

###
pred_neg = (output <= 0.5).view(-1)
pred_pos = (output > 0.5).view(-1)
plt.scatter(x[pred_neg,0],x[pred_neg,1],color='blue', marker='o')
plt.scatter(x[pred_pos,0],x[pred_pos,1],color='red', marker='x')

w = lr_model.linear.weight[0]
b = lr_model.linear.bias[0]

def draw_decision_boundary(w,b,x0):
    x0 = x0.clone().detach().requires_grad_(True).to(torch.float32).reshape(-1, 1)
    x1 = (-b - w[0] * x0)/w[1]
    plt.plot(x0.detach().numpy(),x1.detach().numpy(),'r-')

draw_decision_boundary(w,b,torch.linspace(x.min(),x.max(),50))
plt.show()

      --------------------------------------------------------------------------------------------------------------

八、回归VS分类

在之前的回归任务中,我们是预测分值是多少;

分类任务中就可以变成根据学习时间判断是否能通过考试,即结果分为两类:fail、pass。

我们的任务就是计算不同时间 x 分别是 fail、pass 的概率。(二分类问题其实只需要计算一个概率; 另一个概况就是1-算的概率)

如果预测pass概率为0.6,fail概率就是0.4,那么判断为pass。

1、sigmoid函数

σ(x)= 1 / 1+e⁻ˣ

sigmoid函数在x无限趋近于正无穷、 负无穷时,y无线趋近于1、0;

可以看到当x非常大或者非常小的时候,函数梯度变化就非常小了。这 种函数称为饱和函数

九、逻辑回归

1、逻辑回归模型

只是在线性回归之后加了一个sigmoid激活函数!将值映 射在【0,1】之间。

在线性回归中,我们假设随机变量𝑥1,⋯,𝑥𝑛与𝑦之间的关系是线性的。

但在实际中,我们通常会遇到非线性关系。这个时候,我们可以使用一个非线性变化g(·),使得线性回归模型 𝑓(⋅) 实际上对g(y)而非y进行拟合,即:

y = g⁻¹(f(x))

其中 f(·)仍为:

f(x)= wᵀx + b

因此这样的回归模型称为广义线性回归模型

广义线性回归模型使用非常广泛。例如在二元分类任务中,我们的目标是拟合这样一 个分离超平面𝑓(𝒙)=𝒘ᵀ𝒙+𝑏,使得目标分类𝑦可表示为以下阶跃函数:

但是在分类问题中,由于𝑦取离散值,这个阶跃判别函数是不可导的。

不可导的性质使得许多数学方法不能使用。我们考虑 通常可以使用一个函数σ(·)来近似这个离散的阶跃函数,通常可以使用 logistic(Sigmoid函数)

2、损失函数

MSE loss:计算 数值之间的差异

BCE loss:计算 分布之间的差异

3、Logistic回归代码实现

训练结果为:

十、代码

import torch
import torch.nn.functional as F
import numpy as np
import matplotlib.pyplot as plt
#定义输入数据 x_data 和对应的标签 y_data
x_data = torch.Tensor([[1.0], [2.0], [3.0]])
y_data = torch.Tensor([[0], [0], [1]])  #y_data 是对应的标签,表示每个样本的类别(0或1)

#定义逻辑回归模型类 LogisticRegressionModel,继承自 torch.nn.Module
class LogisticRegressionModel(torch.nn.Module):
    def __init__(self):
        super(LogisticRegressionModel, self).__init__()
        #定义了一个线性层 self.linear
        self.linear = torch.nn.Linear(1, 1)#输入维度为1,输出维度也为1

    #在 forward 方法中,将输入 x 通过线性层进行前向传播,并使用 Sigmoid 函数将输出转换为概率值
    def forward(self, x):
        y_pred = F.sigmoid(self.linear(x))
        return y_pred
#创建逻辑回归模型实例 model
model = LogisticRegressionModel()
#定义损失函数和优化器:
#使用了二元交叉熵损失函数(Binary Cross Entropy Loss)作为损失函数
#并将损失值求和
criterion = torch.nn.BCELoss(reduction='sum')
#随机梯度下降(Stochastic Gradient Descent)算法
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

#模型训练
#使用模型对输入数据进行预测,得到预测结果 y_pred
for epoch in range(1000):
    y_pred = model(x_data)

    #计算预测结果与真实标签之间的损失值 loss
    loss = criterion(y_pred, y_data)
    print(epoch, loss.item())
    #清空优化器的梯度缓存,执行反向传播计算梯度,并更新模型参数
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()


#测试和绘图
#先生成一个从0到10的等差数列 x,并将其转换为张量 x_t
x = np.linspace(0, 10, 200)
x_t = torch.Tensor(x).view((200, 1))
#使用训练好的模型对 x_t 进行预测,得到预测结果 y_t
y_t = model(x_t)

#将 y_t 转换为 NumPy 数组 y,并使用 Matplotlib 绘制预测结果的曲线图
y = y_t.data.numpy()
plt.plot(x, y)
#在概率=0.5时画一条红色直线
plt.plot([0, 10], [0.5, 0.5], c='r')
plt.xlabel('Hours')
plt.ylabel('Probability of Pass')
plt.grid()#添加网格线
plt.show()

  • 41
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值