pytorch分类任务

学习二分类问题的模型,熟悉逻辑损失函数、pandas.DataFrame()的基础应用。本博文主要用于整理个人的知识框架,希望也能帮到大家。如有不足,欢迎留言。🙏

Pytorch分类任务

运行环境:https://colab.google/

数据准备

准备数据集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from sklearn.datasets import make_circles

"""
make_circles 是 scikit-learn 库中的 sklearn.datasets 模块中的一个函数。
它用于生成一个带有圆形决策边界的玩具数据集。这个函数对于测试和可视化处理非线性可分数据的算法非常有用。
"""

n_samples = 1000
X, y =make_circles(n_samples, noise = 0.03, random_state = 666)
# make_circles生成的数据集通常用于二分类问题,类别标签通常是 0 或 1。生成的数据集中的每个样本都属于两个类别中的一个
# y 的标签是根据每个样本点是否在大圆圈内来确定的,如果在大圆圈内则标签为 1,否则标签为 0。

X.shape, y.shape # X.shape: 这将返回一个元组,描述了X数组的维度
# (1000,2)二维数组,1000为行数,2为列数
# (1000,) 一位数组,即label

type(X), type(y)
--> (numpy.ndarray, numpy.ndarray)

# 数据类型的转换
# 为什么 要把numpy转为tensoar?

# 统一的计算环境
# 动态图 vs 静态图
# 内存管理
# GPU加速
# 自动求导和优化器
import torch
X = torch.from_numpy(X)
y = torch.from_numpy(y)

X = X.type(torch.float)
y = y.type(torch.float)
"""
在将 NumPy 数组转换为 PyTorch 张量时,如果没有指定数据类型,PyTorch 将会使用与原始数组相同的数据类型。因此,如果原始的 NumPy 数组中的数据类型是 float32 或 float64,那么转换后的 PyTorch 张量的数据类型也会是相应的 torch.float32 或 torch.float64。

我们需要明确指定数据类型,以确保数据类型与模型和计算设备(如 GPU)的要求相匹配

torch.float 实际上是 torch.float32 的别名。
使用 32 位的内存空间来表示浮点数,具有较高的计算性能,通常在深度学习中被广泛使用。
"""

划分数据集

1
2
3
4
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 666)
# 随机20%划分完成后,X_train 和 y_train 分别包含训练集的特征数据和标签数据,X_test 和 y_test 分别包含测试集的特征数据和标签数据。

可视化

pandas.DataFrame

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pandas as pd # 用于引入DataFrame
circles = pd.DataFrame({"X1":X[:,0], "X2":X[:,1], "label":y})
"""
DataFrame 是 Pandas 中最重要的数据结构之一,它类似于电子表格或数据库表格。
DataFrame 是一个二维标记数据结构,可以容纳多种类型的数据,并且每一列都可以有不同的数据类型。
DataFrame 允许你以一种类似于 SQL 或 Excel 的方式轻松地对数据进行操作、筛选、分组和汇总。

X[:,0] 表示取 X 中所有行的第一个特征,即将所有样本的第一个特征存储到 X1 列中。
X[:,1] 表示取 X 中所有行的第二个特征,即将所有样本的第二个特征存储到 X2 列中。
y 则表示样本的标签,将其存储到 label 列中。
最终生成的 DataFrame circles 包含了两个特征列 X1 和 X2,以及一个标签列 label,用来存储每个样本的特征和标签信息。
"""
circles.head(10)
-->如下表
index X1 X2 label
0 0.5232532787040866 0.6130051138010815 1
1 0.5590261056027346 -0.7938079756641505 0
2 0.07279950491964153 1.0158009611913412 0
3 0.643975455169496 0.4792458217715751 1
4 0.7277505858717157 -0.3306116318780561 1
5 0.8114212398289349 -0.5947803838171337 0
6 -0.9228566785254152 -0.31323880736337917 0
7 0.8211164440168314 -0.52126486439117 0
8 -0.862636456661466 0.052975218510684215 1
9 -0.7868902607268425 0.10441672781411158 1

matplotlib.pyplot

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import matplotlib.pyplot as plt

plt.scatter(x = X[:,0], y = X[:,1], c = y, cmap = plt.cm.RdYlBu)
"""

这段代码使用 Matplotlib 库创建了一个散点图,
其中 x 轴表示数据集中的第一个特征(X1),
y 轴表示数据集中的第二个特征(X2),并根据标签(y)对点的颜色进行着色。

plt.scatter() 函数用于创建散点图。
x = X[:,0] 指定 x 轴的值为数据集中所有样本的第一个特征。
y = X[:,1] 指定 y 轴的值为数据集中所有样本的第二个特征。
c = y 指定散点的颜色根据标签 y 的值来确定。注意,此处的y表示label的y,而不是数据集的第二个特征
cmap = plt.cm.RdYlBu 指定了使用的颜色映射,这里使用了红黄蓝的颜色映射。
"""

image-20240212150355179

建立模型

模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import torch
from torch import nn

# 确保设备一致性
device = "cuda" if torch.cuda.is_available() else "cpu"


class CircleModelV0(nn.Module):
def __init__(self):
super().__init__()
# nn.Linear 是 PyTorch 中的一个类,用于创建线性(全连接)层。它的作用是将输入的数据进行线性变换
# 这种映射是复杂的的变换和映射。每个线性层都引入了一组可学习的权重参数,这些参数会在训练过程中进行调整,以使模型能够更好地拟合训练数据并进行预测。而 不是将输入的元素简单的重新按维度组合。
self.layer_1 = nn.Linear(in_features = 2, out_features = 5) # 这个线性层将输入的特征空间维度从 2 维映射到了 5 维。
self.layer_2 = nn.Linear(in_features = 5, out_features = 1)
self.relu = nn.ReLU() # 激活函数引入非线性属性,从而增加神经网络的表达能力和学习能力。

def forward(self, x): # forward()定义模型的前向传播过程
return self.layer_2(self.relu(self.layer_1(x)))

声明模型对象

1
model_0 = CircleModelV0().to(device)

分类函数的损失函数如何来定

思考:如何把预测值映射为 0 还是 1?

使用sigmoid函数,然后将 1 / (1 + e^-x) 与0.5来比,使用torch.round()函数四舍五入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# logistic函数,即sigmoid函数,把实数压缩到0~1

# torch.nn.BCELoss() 没有经过logistic回归层
"""
Logistic回归是一种用于解决二分类问题的线性模型,通常用于估计样本属于某一类的概率。
Logistic回归可以被视为一个单层的神经网络,其输出通过一个sigmoid函数进行转换,将线性变换的结果压缩到 (0, 1) 区间,表示样本属于某一类的概率。

在PyTorch中,可以使用 nn.Linear 创建一个全连接层,然后通过 nn.Sigmoid 激活函数将线性变换的结果转换为 (0, 1) 区间的概率值。
这样的组合通常被称为Logistic回归层,用于二分类问题的预测。

例如,在PyTorch中,可以如下定义一个简单的Logistic回归层:
import torch
import torch.nn as nn

class LogisticRegression(nn.Module):
def __init__(self, input_dim):
super(LogisticRegression, self).__init__()
self.linear = nn.Linear(input_dim, 1)
self.sigmoid = nn.Sigmoid()

def forward(self, x):
out = self.linear(x)
out = self.sigmoid(out)
return out

"""

# 损失函数定义
loss_fn = torch.nn.BCEWithLogitsLoss() # 经过logistic回归层



"""
torch.nn.BCEWithLogitsLoss()这个损失函数结合了 Sigmoid 激活函数和二元交叉熵损失函数,同时计算了两者的结果
使用 BCEWithLogitsLoss 可以简化代码,提高计算效率,尤其适用于二分类问题。

import torch
import torch.nn as nn

# 创建模型
model = YourModel()

# 定义损失函数
criterion = nn.BCEWithLogitsLoss()

# 假设预测结果和真实标签已经准备好
outputs = model(inputs)
loss = criterion(outputs, targets)

# 清零梯度
optimizer.zero_grad()

# 反向传播
loss.backward()

# 更新参数
optimizer.step()

"""


optimizer = torch.optim.SGD(params = model_0.parameters(), lr = 1)



辅助观察函数声明

1
2
3
4
5
6
7
# 用于计算 预测结果的一致程度
def accuracy_fn(y_true, y_pred):
correct = torch.sum(y_true == y_pred).item()

acc = correct / len(y_true) * 100

return acc

训练模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
torch.manual_seed(666)
epochs = 185
X_train, y_train = X_train.to(device), y_train.to(device)
X_test, y_test = X_test.to(device), y_test.to(device)

for epoch in range(epochs):
model_0.train() # 将模型设置为训练模式

# squeeze()移除单维度,方便比较
# 将 (1, n) 或 (n, 1) 的张量,变为 (n,) 的一维张量。有些函数可能要求输入张量的形状必须是一维的
# 使用 squeeze() 可以使得代码更加简洁和清晰,避免了一些不必要的单维度,提高了代码的可读性和可维护性。
y_logits = model_0(X_train).squeeze()
y_pred = torch.round(torch.sigmoid(y_logits)) # torch.sigmoid() 将y_logits变为 0 ~ 1 ,torch.round() 进行四舍五入
loss = loss_fn(y_logits, y_train) # 用去除维度后的y_logits与y_train计算损失函数
acc = accuracy_fn(y_train, y_pred)

optimizer.zero_grad()
loss.backward()
optimizer.step()

model_0.eval()
# model_0.eval()和torch.inference_mode()的区别:
# 都用于将模型设置为评估模式(inference mode),但它们有一些区别。
# 总的来说,model_0.eval() 更适合在模型级别进行模式切换,而 torch.inference_mode() 更适合在全局级别临时切换模式。

with torch.inference_mode():
test_logits = model_0(X_test).squeeze()
test_pred = torch.round(torch.sigmoid(test_logits))

test_loss = loss_fn(test_logits, y_test)
test_acc = accuracy_fn(y_test, test_pred)

print(f"{epoch} | Loss:{loss} | acc:{acc} | Test Loss:{test_loss} | Test Acc:{test_acc}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 输出结果
-->
...
168 | Loss:0.5972417593002319 | acc:72.125 | Test Loss:0.601545512676239 | Test Acc:71.5
169 | Loss:0.595664918422699 | acc:72.5 | Test Loss:0.6001173257827759 | Test Acc:73.0
170 | Loss:0.5940487384796143 | acc:73.0 | Test Loss:0.5980546474456787 | Test Acc:72.5
171 | Loss:0.5923799276351929 | acc:73.5 | Test Loss:0.5966604948043823 | Test Acc:73.0
172 | Loss:0.5906763076782227 | acc:74.0 | Test Loss:0.5944149494171143 | Test Acc:74.0
173 | Loss:0.5889647006988525 | acc:74.375 | Test Loss:0.593086302280426 | Test Acc:75.0
174 | Loss:0.5872295498847961 | acc:74.375 | Test Loss:0.5907705426216125 | Test Acc:74.0
175 | Loss:0.5854421854019165 | acc:74.625 | Test Loss:0.5894856452941895 | Test Acc:76.0
176 | Loss:0.5837037563323975 | acc:75.875 | Test Loss:0.5870495438575745 | Test Acc:75.5
177 | Loss:0.5819454789161682 | acc:75.25 | Test Loss:0.5859463214874268 | Test Acc:76.5
178 | Loss:0.580162763595581 | acc:76.25 | Test Loss:0.5832081437110901 | Test Acc:76.0
179 | Loss:0.5783393979072571 | acc:76.375 | Test Loss:0.5823167562484741 | Test Acc:77.5
180 | Loss:0.5764892101287842 | acc:77.625 | Test Loss:0.5792547464370728 | Test Acc:77.0
181 | Loss:0.5746139883995056 | acc:76.75 | Test Loss:0.5787341594696045 | Test Acc:77.5
182 | Loss:0.5727413296699524 | acc:78.5 | Test Loss:0.5753249526023865 | Test Acc:77.0
183 | Loss:0.5708712339401245 | acc:77.25 | Test Loss:0.5753326416015625 | Test Acc:80.5
184 | Loss:0.5689650177955627 | acc:81.875 | Test Loss:0.5713624954223633 | Test Acc:80.0
...

评论