【PyTorch深度学习与Logistic回归实践指南】

前言

本文将引导读者深入探索机器学习的基础知识,涵盖PyTorch深度学习基础、Logistic回归和梯度下降算法等重要内容。通过学习PyTorch的基础操作和实现Logistic回归模型,读者将建立起对机器学习核心概念的扎实理解。同时,我们将深入讨论梯度下降算法的原理及实际应用,帮助读者掌握优化模型的关键技术。本文旨在为初学者提供清晰易懂的学习指南,让大家能够轻松入门机器学习,并在实践中不断提升自己的技能。让我们一起开始这段令人兴奋的机器学习之旅吧!


一、pyTorch深度学习基础

1.1、Tensor对象及运算

PyTorch 的 Tensor 和NumPy的ndarray 十分类似,但是Tensor 具备两个ndarray 不具备而对于深度学习来说非常重要的功能。其一是Tensor 能用利用GPU计算,GPU根据芯片性能的不同,在进行矩阵运算时,能比CPU快几十倍。其二是Tensor在计算时,能够作为节点自动地加入计算图,而计算图可以为其中的每个节点自动计算微分,也就是说当我们使用Tensor时,就不需要手动计算微分了。下面,我们首先介绍Tensor 对象及其运算。

import torch
import numpy as np

# 创建两个标量tensor
print('torch.tensor默认为:{}'.format(torch.tensor(1).dtype))
print('torch.Tensor默认为:{}'.format(torch.Tensor(1).dtype))

# 用列表构建tensor,指定数据类型为float64
a = torch.tensor([[1,2],[3,4]], dtype=torch.float64)
# 用ndarray构建tensor,指定数据类型为uint8
b = torch.tensor(np.array([[1,2],[3,4]]), dtype=torch.uint8)
print(a)
print(b)

# 通过device参数指定使用的设备(这里指定为cuda:0,即GPU上)
cuda0 = torch.device('cuda:0')
c = torch.ones((2,2), device=cuda0)
print(c)

# 将c tensor从GPU移动到CPU,并同时改变数据类型为double
c = c.to('cpu', torch.double)
print(c.device)
# 将c tensor从GPU移动到CPU,并同时改变数据类型为double
c = c.to('cpu', torch.double)
print(c.device)

# 将b tensor从CPU移动到GPU,并同时改变数据类型为float
b = b.to(cuda0, torch.float)
print(b.device)

# 逐元素相乘
a = torch.tensor([[1,2],[3,4]])
b = torch.tensor([[1,2],[3,4]])
c = a * b
print("逐元素相乘:", c)

# 矩阵乘法
c = torch.mm(a, b)
print("矩阵乘法:", c)

# 将a tensor中小于2的元素设置为2,大于3的元素设置为3
a = torch.tensor([[1,2],[3,4]])
torch.clamp(a, min=2, max=3)

# 对a tensor中的元素进行四舍五入
a = torch.Tensor([-1.1,0.5,0.501,0.99])
torch.round(a)

# 对a tensor中的元素进行双曲正切函数计算
a = torch.Tensor([-3,-2,-1,-0.5,0,0.5,1,2,3])
torch.tanh(a)

# 创建一个从0开始、步长为1的tensor
print(torch.arange(5))
# 创建一个从1开始、到5结束(不包括5)、步长为2的tensor
print(torch.arange(1,5,2))
# 在05之间创建10个均匀间隔的点
print(torch.linspace(0,5,10))

# 创建一个大小为3x3的全1矩阵
print(torch.ones(3,3))
# 创建一个大小为3x3的全0矩阵
print(torch.zeros(3,3))

# 创建一个大小为3x3的在[0,1)范围内均匀分布的随机tensor
torch.rand(3,3)
# 创建一个大小为3x3的标准正态分布随机tensor
torch.randn(3,3)
# 创建一个大小为3x3的整数随机tensor,范围为[0, 9)
torch.randint(0, 9, (3,3))

运行结果

在这里插入图片描述

1.2、Tensor的索引和切片

1.2.1、基本索引

a = torch.arange(9).view(3,3)
##基本索引
a [2,2]
##切片
a[1:,:-1]
##带步长的切片
a[::2]

1.2.2、整数索引

rows = [0,1]
cols = [2,2]
a[rows, cols]

1.2.3、布尔索引

##布尔索引
index = a>4
print(index)
print(a[index])

1.2.4、返回非零值的索引矩阵

a = torch.arange(9).view(3,3)
index = torch.nonzero(a >=0)
print(index)

a = torch.randint(0,2,(3,3))
print(a)
index = torch.nonzero(a)
print(index)

1.3、Tensor的变换、拼接和拆分

1.3.1、变换

在PyTorch中,Tensor.view 和 Tensor.reshape 都能被用来更改Tensor 的维度。它们的区别在于,Tensor.view 要求Tensor的物理存储必须是连续的,否则将报错;而Tensor.reshape则没有这种要求。但是,Tensor.view 返回的一定是一个索引,更改返回值,则原始值同样被更改;Tensor.reshape 返回的是引用还是复制是不确定的。它们的相同之处是都接收要输出的维度作为参数,且输出的矩阵元素个数不能改变,可以在维度中输入-1,PyTorch会自动推断它的数值。

#Tensor.nelement、Tensor.ndimension、ndimension.size可分别用来查看矩阵元素的个数、轴的个数以及维度,
# 属性 Tensor.shape 也b 可以用来查看 Tensor 的维度
a= torch.rand(1,2,3,4,5)
print("元素个数",a.nelement())
print("轴的个数",a.ndimension())
print("矩阵维度",a.size(), a.shape)

#Tensor.view和Tensor.reshape都能被用来更改Tensor 的维度
#维度中输入-1会自动阻断它的数值
b = a.view(2*3,4*5)
print(b.shape)
c = a.reshape(-1)
print(c.shape)
d = a.reshape(2*3,-1)
print(d.shape)

#torch.squeeze和torch.unsqueeze用于为 Tensor 去掉和添加轴。
#其中torch.squeeze 用于去掉维度为1的轴,而torch.unsqueeze用于给Tensor的指定位置添加一个维度为1的轴
e = torch.randn(1,3,1,5)
print(e.shape)
f = torch.squeeze(e)
print(f.shape)
g = torch.unsqueeze(e,dim=1)
print(g.shape)

#torch.t和torch.transpose 用于转置二维矩阵。
h = torch.randn(3,4)
print(h)
i = torch.t(h)
print(i)
j = torch.transpose(h,0,1)
print(j)

#对于高维度Tensor,可以使用permute 方法来变换维度
k = torch.randn(3,4,5)
print(k)
l = k.permute(1,0,2)
print(l)

1.3.2、拼接

PyTorch 提供了torch.cat 和torch.stack用于拼接矩阵。不同之处是,torch.cat在已有的轴dim上拼接矩阵,给定轴的维度可以不同,而其他轴的维度必须相同。torch.stack在新的轴上拼接,它要求被拼接的矩阵的所有维度都相同。下面的例子可以很清楚地表明它们的使用方式和区别

#torch.cat和torch.stack拼接矩阵
a= torch.randn(2,3)
b= torch.randn(3,3)
#默认维度为 dim=0
c= torch.cat((a,b))
d=torch.cat((b,b,b),dim =1)
print(c.shape)
print(d.shape)

c = torch.stack((b,b), dim=1)
d = torch.stack((b,b), dim=0)
print(c.shape)
print(d.shape)

1.3.3、拆分

除了拼接矩阵,PyTorch 还提供了torch.split 和torch.chunk用于拆分矩阵。它们的不同之处在于,torch.split 传人的是拆分后每个矩阵的大小,可以传入list,也可以传入整数,而 torch.chunk传入的是拆分的矩阵个数。

#torch.split和torch.chunk拆分矩阵。
a = torch.randn(10, 3)
for x in torch.split(a, [1, 2, 3, 4], dim=0):
    print(x.shape)
for x in torch.split(a,4,dim=0):
    print(x.shape)
for x in torch.chunk(a, 4, dim=0):
    print(x.shape)

1.4、pytorch 的 Reduction的操作

Reduction操作的特点是它往往对一个Tensor内的元素执行归约操作,比如torch.max找极 大值、torch.cumsum计算累加,它还提供了dim参数来指定沿矩阵的哪个维度执行操作。

import torch
#创建了一个形状为 (2, 2) 的张量 a,并使用 torch.max 函数求取了全局最大值。
a=torch.tensor([[1,2],[3,4]])
print("全局最大值:",torch.max(a))

#调用 torch.max 函数,并指定参数 dim=0,即沿着横轴(列)计算每一列的最大值及其索引。
torch.max(a,dim=0)

#创建了一个形状为 (2, 2) 的张量 a,并使用 torch.cumsum 函数沿着横轴计算了每一列的累加结果。
a=torch.tensor([[1,2],[3,4]])
print("沿着横轴计算每一列的累加:")
print(torch.cumsum(a,dim=0))
#使用 torch.cumprod 函数沿着纵轴计算了每一行的累乘结果。
print("沿着纵轴计算每一行的累乘:")
print(torch.cumprod(a,dim=1))

 #创建了一个形状为 (2, 2) 的张量 a,并分别使用 mean、median 和 std 方法计算了其均值、中值和标准差。
a=torch.Tensor([[1,2],[3,4]])
a.mean(),a.median(),a.std()

# 创建了一个形状为 (3, 3) 的张量 a,并使用 torch.randint 函数生成随机整数张量。然后分别打印出 a 的值和调用 torch.unique 函数找出矩阵中出现了哪些元素。
a=torch.randint(0,3,(3,3))
print(a)
print(torch.unique(a))

1.5、pyTorch的自动化微分

当将Tensor 的requires grad 属性设置为True时,PyTorch的torch.autograd会自动追踪它的计算轨迹。当需要计算微分的时候,只需要对最终计算结果的 Tensor 调用backward方法、所有计算节点的微分就会被保存在grad属性。

import torch

#当tensor的require__gard属性设置为ture时,PyTorch 的 torch.autograd 会自动追踪它的的计算轨迹。
x = torch.arange(9).view(3,3)
x.requires_grad

x = torch.rand(3,3,requires_grad=True)
print(x)

w = torch.ones(3,3,requires_grad=True)
y = torch.sum(torch.mm(w,x))


print(y.retain_grad())
print(y.grad)
print(x.grad)
print(w.grad)

#Tensor.detach 会将Tensor 从计算图剥离出去,不再计算它的微分。
x = torch.rand(3,3,requires_grad=True)
w = torch.ones(3,3,requires_grad=True)
print(x)
print(w)
yy = torch.mm(w,x)
detached__yy = yy.detach()
y = torch.mean(yy)
y.backward ()

print(yy.retain_grad())
print(detached__yy)
print(w.grad)
print(x.grad)

#with torch.no_grad():包括的代码段不会计算微分。
y=torch.sum(torch.mm(w,x))
print(y.requires_grad)
with torch.no_grad():
 y = torch.sum(torch.mm(w,x))
 print(y.requires_grad)

二、Logistic回归

回归是指这样一类问题:通过统计分析一组随机变量x,,x,与另一组随机变量y,…,y。之间的关系,得到一个可靠的模型,使得对于给定的x=,,X,},可以利用这个模型对y={y,,Pa进行预测。在这里,随机变量x,…,x,被称为自变量,随机变量y1,…,y。被称为因变量。例如,当预测房价时,研究员们会选取可能对房价有影响的因素,例如房屋面积、房屋楼层、房屋地点等,作为自变量加入预测模型。研究的任务即建立一个有效的模型,能够准确表示出上述因素与房价之间的关系
在这里插入图片描述

2.1、线性回归简介

2.1.1、一元线性回归

线性回归可以说是用法非常简单、用处非常广泛、含义也非常容易理解的一类算法,作为机器学习的入门算法非常合适。我们上中学的时候,都学过二元一次方程,我们将y作为因变量,x作为自变量,得到方程:
在这里插入图片描述
当我们只用一个x来预测y,就是一元线性回归,也就是在找一个直线来拟合数据。比如,我有一组数据画出来的散点图,横坐标代表广告投入金额,纵坐标代表销售量,线性回归就是要找一条直线,并且让这条直线尽可能地拟合图中的数据点。
在这里插入图片描述

2.1.2、损失函数

那既然是用直线拟合散点,为什么最终得到的直线是y = 0.0512x + 7.1884,而不是下图中的y = 0.0624x + 5呢?这两条线看起来都可以拟合这些数据啊?毕竟数据不是真的落在一条直线上,而是分布在直线周围,所以我们要找到一个评判标准,用于评价哪条直线才是最“合适”的。
在这里插入图片描述
在这里插入图片描述
这个公式是残差平方和,即SSE(Sum of Squares for Error),在机器学习中它是回归问题中最常用的损失函数。

现在我们知道了损失函数是衡量回归模型误差的函数,也就是我们要的“直线”的评价标准。这个函数的值越小,说明直线越能拟合我们的数据。

2.1.3、用Pytorch实现线性回归模型

1、数据准备

import torch
x_data = torch.Tensor([[1.0], [2.0], [3.0]])
y_data = torch.Tensor([[2.0], [4.0], [6.0]])

2、设计模型

class LinearModel(torch.nn.Module):
    def __init__(self):
        super(LinearModel, self).__init__()#调用父类构造函数,不用管,照着写。
        # torch.nn.Linear(in_featuers, in_featuers)构造Linear类的对象,其实就是实现了一个线性单元
        self.linear = torch.nn.Linear(1, 1)
    def forward(self, x):
        y_pred = self.linear(x)#调用linear对象,输入x进行预测
        return y_pred

model = LinearModel()#实例化LinearModel()

3. 构造损失函数和优化器

criterion = torch.nn.MSELoss(size_average=False)#size_average:the losses are averaged over each loss element in the batch.
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)#params:model.parameters(): w、b

4. 训练过程
预测
计算loss
梯度清零
Backward
参数更新

for epoch in range(100):
    y_pred = model(x_data)#Forward:预测
    loss = criterion(y_pred, y_data)#Forward:计算loss
    print(epoch, loss)
    optimizer.zero_grad()#梯度清零
    loss.backward()#backward:计算梯度
    optimizer.step()#通过step()函数进行参数更新

# Output weight and bias
print('w = ', model.linear.weight.item())
print('b = ', model.linear.bias.item())

# Test Model
x_test = torch.Tensor([[4.0]])
y_test = model(x_test)
print('y_pred = ', y_test.data)

2.2、Logistic简介

2.2.1、什么是Logistic?

logistic回归虽然说是回归,但确是为了解决分类问题,是二分类任务的首选方法,简单来说,输出结果不是0就是1
举个简单的例子:
癌症检测:这种算法输入病理图片并且应该辨别患者是患有癌症(1)或没有癌症(0)

2.2.2、logistic回归和线性回归的关系

线性回归,直接可以分为两类,但是对于图二来说,在角落加上一块蓝色点之后,线性回归的线会向下倾斜,参考紫色的线,
但是logistic回归(参考绿色的线)分类的还是很准确,logistic回归在解决分类问题上还是不错的
在这里插入图片描述

2.2.3、logistic回归的原理

在这里插入图片描述

2.3、用pyTorch实现logistic回归

2.3.1、数据准备

import numpy as np
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()

上述代码将生成的样本用plt.scatter 绘制出来,绘制的结果如图所示,可以很明显地看出多元高斯分布生成的样本聚成了两个簇,并且簇的中心分别处于不同的位置(多元高斯分布的均值向量决定了其位置)。右上角簇的样本分布比较稀疏,而左下角簇的样本分布紧凑(多元高斯分布的协方差矩阵决定了分布形状)。
在这里插入图片描述

2.3.2、线性方程


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

# 检查自定义线性函数是否与PyTorch的Linear层输出一致
torch.sum((output - my_linear(x, linear.weight, linear.bias)))

2.3.3、激活函数

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

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

# 检查自定义Sigmoid函数是否与PyTorch的Sigmoid一致
torch.sum(sigmoid(output) - my_sigmoid(output))

2.3.4、损失函数

Logistic 回归使用交叉熵作为损失函数。PyTorch的torch.nn 提供了许多标准的损失函数,我们可以直接使用nn.BCELoss 计算二值交叉熵损失。调用了BCELoss来计算我们实现的 Logistic 回归模型的输出结果 sigmoid(output)和数据的标签y。同样地,我们自定义了二值交叉熵函数,在第8行将my_loss 和 PyTorch 的BCELoss进行比较,发现其结果-致。


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

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

# 检查自定义损失函数是否与PyTorch的BCELoss一致
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

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

2.3.5、优化算法


optimizer = torch.optim.SGD(ir_model.parameters(), lr=0.03)

batch_size = 10
iterations = 10
for i in range(iterations):
    for j in range(int(len(x) / batch_size)):
        input_batch = x[j * batch_size: (j + 1) * batch_size]
        target_batch = y[j * batch_size: (j + 1) * batch_size]
        optimizer.zero_grad()
        output = ir_model(input_batch)
        loss_val = loss(output, target_batch)
        loss_val.backward()
        optimizer.step()

2.3.6、模型可视化

pred_neg = (output <= 0.5).view(-1)
pred_pos = (output > 0.5).view(-1)

plt.scatter(x[pred_neg, 0].numpy(), x[pred_neg, 1].numpy(), label='Negative')
plt.scatter(x[pred_pos, 0].numpy(), x[pred_pos, 1].numpy(), label='Positive')

w = ir_model.linear.weight.squeeze()
b = ir_model.linear.bias.item()

def draw_decision_boundary(w, b, x0):
    x1 = (-b - w[0] * x0) / w[1]
    plt.plot(x0.detach().numpy(), x1.detach().numpy(), 'r', label='Decision Boundary')

x0 = torch.linspace(x[:, 0].min(), x[:, 0].max(), 50)
draw_decision_boundary(w, b, x0)

plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.legend()
plt.show()

在这里插入图片描述

三、梯度下降算法

3.1、梯度下降算法

梯度下降算法是一种贪心算法,得到的解不一定是全局最优。
运行多次,随机化初始点。(SGD:随机梯度下降)
梯度下降法的初始点也是一个超参数。
在这里插入图片描述
在这里插入图片描述
代码

import numpy as np
import matplotlib.pyplot as plt

# training set
x_data = [1.0, 2.0, 3.0]
y_data = [2.0, 4.0, 6.0]

# 初始化参数
w = 1.0

# 定义线性模型 y = w*x
def forward(x):
    return x * w

# 计算mse
def cost(xs, ys):
    cost = 0
    for x, y in zip(xs, ys):
        y_pred = forward(x)
        cost += (y_pred - y) ** 2
    return cost / len(xs)

# 计算梯度
def gradient(xs, ys):
    grad = 0
    for x, y in zip(xs, ys):
        grad += 2 * x * (x * w - y)
    return grad / len(xs)

epoch_list = []
cost_list = []

print('predict (before training)', 4, forward(4))

# epoch:训练轮次,表示重复训练的次数
for epoch in range(100):
    cost_val = cost(x_data, y_data)  # 计算损失值
    grad_val = gradient(x_data, y_data)  # 计算梯度
    w -= 0.01 * grad_val  # 更新参数(梯度) 0.01 learning rate
    print('epoch', epoch, 'w=', w, 'loss=', cost_val)
    epoch_list.append(epoch)
    cost_list.append(cost_val)

# 画图
print('w=', w)
print('predict (after training)', 4, forward(4))
plt.plot(epoch_list, cost_list)
plt.ylabel('cost')
plt.xlabel('epoch')
plt.show()

损失曲线图
在这里插入图片描述

3.2、随机梯度下降

随机梯度下降算法:随机选取N个样本中的一个样本的loss来更新参数!
在这里插入图片描述
比较
在这里插入图片描述
代码

import numpy as np
import matplotlib.pyplot as plt

# training set
x_data = [1.0, 2.0, 3.0]
y_data = [2.0, 4.0, 6.0]

# 初始化参数
w = 1.0

# 定义线性模型 y = w*x
def forward(x):
    return x * w

# 计算mse
def loss(x, y):  # 计算一个样本x的损失值loss
    y_pred = forward(x)
    return (y_pred - y) ** 2

# 计算梯度
def gradient(x, y):
    return 2 * x * (x * w - y)

epoch_list = []
loss_list = []

print('predict (before training)', 4, forward(4))

# epoch:训练轮次,表示重复训练的次数
for epoch in range(100):
    for x, y in zip(x_data, y_data):
        grad = gradient(x, y)  # 计算梯度
        w = w - 0.01 * grad  # 更新参数(梯度) 0.01 learning rate
        print("grad:", x, y, grad)
        l = loss(x, y)
        print("loss:", l)

    print("progress:", epoch, "w=", w, "loss=", l)
    epoch_list.append(epoch)
    loss_list.append(l)

# 画图
print('predict (after training)', 4, forward(4))
plt.plot(epoch_list, loss_list)
plt.ylabel('loss')
plt.xlabel('epoch')
plt.show()

曲线图
在这里插入图片描述

总结

本文介绍了机器学习中的基础知识,包括PyTorch深度学习基础、Logistic回归、梯度下降算法等内容。在PyTorch深度学习基础部分,讲解了如何使用PyTorch进行张量对象的创建和操作,以及张量的索引、切片、变换、拼接、拆分等操作。在Logistic回归部分,详细介绍了一元线性回归、损失函数、激活函数等概念,并演示了如何用PyTorch实现Logistic回归模型。最后,在梯度下降算法部分,讲解了梯度下降算法和随机梯度下降算法的原理,以及通过代码实现了简单的梯度下降和随机梯度下降算法来拟合数据。整个文章内容清晰、丰富,对于初学者来说是一份很好的学习材料。如果有任何疑问或需要进一步的解释,欢迎提出。

  • 32
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值