目录
之前我们已经学习了 二分类问题,二分类就像抛硬币正面和反面,只有两种情况。
这里我们要探讨一个 多类别分类模型,比如输入一张图片,分类它是pizza、牛排或者寿司,这里的类别是三,就是多类别分类问题。
1 多类别分类模型
1.1 创建数据
# 创建数据
from sklearn.datasets import make_blobs
import torch
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import torch.nn as nn
import torch.optim as optim
RANDOM_SEED = 42
NUM_CLASSES = 4
NUM_FEATURES = 2
# 创建多类别数据
X, y = make_blobs(n_samples = 1000,
n_features = NUM_FEATURES, # X features
centers = NUM_CLASSES, # y labels
cluster_std = 1.5, # 让数据抖动
random_state = RANDOM_SEED)
# 将数据转换为 tensor
X = torch.from_numpy(X).type(torch.float)
y = torch.from_numpy(y).type(torch.LongTensor)
print(X.dtype, y.dtype)
# 将数据集划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X,
y,
test_size=0.2,
random_state=RANDOM_SEED)
len(X_train), len(y_train), len(X_test), len(y_test)
print(X_train[:5], y_train[:5])
# 可视化数据
plt.figure(figsize=(10, 7))
plt.scatter(X[:,0], X[:,1], c=y, cmap=plt.cm.RdYlBu)
# 创建设备无关的代码
device = "cuda" if torch.cuda.is_available() else "cpu"
device
1.2 创建模型
# 创建模型
# 这次创建模型特殊的点在于我们在 __init__()括号的参数选择中,多添加了参数
# input_features:输入 output_features:输出 hidden_units:隐藏层中神经元的个数
class BlobModel(nn.Module):
def __init__(self, input_features, output_features, hidden_units=8):
super().__init__()
self.linear_stack = nn.Sequential(
nn.Linear(in_features=input_features, out_features=hidden_units),
nn.ReLU(),
nn.Linear(in_features=hidden_units, out_features=hidden_units),
nn.ReLU(),
nn.Linear(in_features=hidden_units, out_features=output_features)
)
def forward(self, x):
return self.linear_stack(x)
model_0 = BlobModel(input_features=NUM_FEATURES, output_features=NUM_CLASSES, hidden_units=8).to(device)
model_0
# 将数据和模型统一到目标设备上
X_train, y_train = X_train.to(device), y_train.to(device)
X_test, y_test = X_test.to(device), y_test.to(device)
1.3 模型传出的数据
# 先看一下模型会传出什么数据吧
model_0.eval()
with torch.inference_mode():
y_logits = model_0(X_train)
print(y_logits[:5])
print(y_train[:5])
很明显这里模型的输出和我们真实的输出是不一样的,是无法进行比较的,所以我们需要将logits -> prediction probabilities -> prediction labels
logits 就是我们模型原始的输出, prediction probabilities 是预测概率,表示我将这个数据预测为这个类别的概率,概率值最大的,那模型就可以认为数据被分为这个类最可靠最可信 prediction labels 预测标签,比如这里我们有四类数据,就是 [0, 1, 2, 3]
还记得我们之前是如何将 logits -> prediction probabilities 的吗?之前的二分类,我们使用的是sigmoid()
,这里多类比分类,我们使用softmax方法
y_pred_probs = torch.softmax(y_logits, dim=1)
y_pred_probs
# 上次二分类我们使用的 torch.round() 进行四舍五入, 那这里我们的多类别分类该如何进行呢?
# 由于这里四个类别,我们选取概率最大的值,找到概率最大值的下标位置,我们就知道它是哪个类别了
# 想起来,我们学过的tensor基础课了么
# 没错,就是 argmax() 返回最大值的位置
y_preds = torch.argmax(y_pred_probs[0])
print(y_preds)
这里的数据都是随机的,是没有训练的,所以效果是不太好的。
1.4 损失函数和优化器
# 创建损失函数
loss_fn = nn.CrossEntropyLoss()
# 创建优化器
optimizer = optim.SGD(params=model_0.parameters(),
lr=0.1)
# 定义一个计算Accuracy的函数
def accuracy_fn(y_true, y_pred):
correct = torch.eq(y_true, y_pred).sum().item()
return (correct / len(y_pred))*100
1.5 训练和测试
# 训练数据
# 设置训练周期
epochs = 100
for epoch in range(epochs):
# 模型训练
model_0.train()
y_logits = model_0(X_train)
y_preds = torch.softmax(y_logits, dim=1).argmax(dim=1)
loss = loss_fn(y_logits,
y_train)
acc = accuracy_fn(y_true=y_train,
y_pred = y_preds)
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 模型测试
model_0.eval()
with torch.inference_mode():
test_logits = model_0(X_test)
test_preds = torch.softmax(test_logits, dim=1).argmax(dim=1)
test_loss = loss_fn(test_logits,
y_test)
test_acc = accuracy_fn(y_true=y_test,
y_pred=test_preds)
# 打印输出
if epoch % 10 == 0:
print(f"Epoch:{epoch} | Train Loss:{loss:.4f} | Train Accuracy:{acc:.2f}% | Test Loss:{test_loss:.4f} | Test Accuracy:{test_acc:.2f}%")
# 可视化
from helper_functions import plot_decision_boundary
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.title("Train")
plot_decision_boundary(model=model_0, X=X_train, y=y_train)
plt.subplot(1, 2, 2)
plt.title("Test")
plot_decision_boundary(model=model_0, X=X_test, y=y_test)
哇塞,这个分类很赞吧,但是看这个划分,我们可以观察出可能不使用非线性函数也可以,咱们把模型里面的ReLU()
去掉就可以啦,大家自己去试试吧,这里就不赘述啦~
1.6 衡量模型性能的指标
from torchmetrics import Accuracy
torchmetrics_accuracy = Accuracy(task='multiclass', num_classes=4).to(device)
# 计算准确度
torchmetrics_accuracy(test_preds, y_test)
OK,我们还可以调用函数计算准确度呢!结束了这里的学习,开始我们的练习来检测我们的学习吧~
2 练习Exercise
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
import torch.optim as optim
RANDOM_SEED = 42
X, y = make_moons(n_samples=1000,
noise=0.03,
random_state=RANDOM_SEED)
X[:5], y[:5]
# 可视化数据
plt.figure(figsize=(10, 7))
plt.scatter(X[:,0], X[:,1], c=y, cmap=plt.cm.RdYlBu)
查看数据,我们发现,特征输入是2,输出是1,哇,看图像,是个二分类喔,这个模型肯定是要用到非线性的.
# 将数据转换为 Tensor
X = torch.from_numpy(X).type(torch.float)
y = torch.from_numpy(y).type(torch.float)
print(X.dtype, y.dtype)
print(X[:5], y[:5])
# 将数据划分为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X,
y,
test_size=0.2,
random_state=RANDOM_SEED)
len(X_train), len(X_test), len(y_train), len(y_test)
# 设备无关
device = "cuda" if torch.cuda.is_available() else "cpu"
device
class MoonModel(nn.Module):
def __init__(self, input_features, output_features, hidden_units):
super().__init__()
self.linear_stack = nn.Sequential(
nn.Linear(in_features=input_features, out_features=hidden_units),
# nn.ReLU(),
nn.Tanh(),
nn.Linear(in_features=hidden_units, out_features=hidden_units),
# nn.ReLU(),
nn.Tanh(),
nn.Linear(in_features=hidden_units, out_features=output_features)
)
def forward(self, x):
return self.linear_stack(x)
model_0 = MoonModel(input_features=2,
output_features=1,
hidden_units=10).to(device)
model_0
# 损失函数和优化器
loss_fn = nn.BCEWithLogitsLoss()
optimizer = optim.SGD(params=model_0.parameters(),
lr=0.1)
# 将数据都放到统一的设备上
X_train, y_train = X_train.to(device), y_train.to(device)
X_test, y_test = X_test.to(device), y_test.to(device)
# 计算正确率的函数
def accuracy_fn(y_true, y_pred):
correct = torch.eq(y_true, y_pred).sum().item()
return (correct / len(y_pred))*100
print(y_logits.shape, y_train.shape)
print(len(y_train))
# 训练和测试
# 设置训练周期
epochs = 1000
for epoch in range(epochs):
# 训练
model_0.train()
y_logits = model_0(X_train).squeeze()
y_preds = torch.round(torch.sigmoid(y_logits))
loss = loss_fn(y_logits,
y_train)
acc = accuracy_fn(y_true=y_train,
y_pred=y_preds)
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 测试
model_0.eval()
with torch.inference_mode():
test_logits = model_0(X_test).squeeze()
test_preds = torch.round(torch.sigmoid(test_logits))
test_loss = loss_fn(test_logits,
y_test)
test_acc = accuracy_fn(y_true=y_test,
y_pred=test_preds)
# 打印输出
if epoch % 10 == 0:
print(f"Epoch:{epoch} | Train Loss:{loss:.4f} | Train Accuracy:{acc:.2f}% | Test Loss:{test_loss:.4f} | Test Accuracy:{test_acc:.2f}%")
# 可视化
from helper_functions import plot_decision_boundary
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.title("Training")
plot_decision_boundary(model=model_0, X=X_train, y=y_train)
plt.subplot(1, 2, 2)
plt.title("Testing")
plot_decision_boundary(model=model_0, X=X_test, y=y_test)
# Code for creating a spiral dataset from CS231n
import numpy as np
N = 100 # number of points per class
D = 2 # dimensionality
K = 3 # number of classes
X = np.zeros((N*K,D)) # data matrix (each row = single example)
y = np.zeros(N*K, dtype='uint8') # class labels
for j in range(K):
ix = range(N*j,N*(j+1))
r = np.linspace(0.0,1,N) # radius
t = np.linspace(j*4,(j+1)*4,N) + np.random.randn(N)*0.2 # theta
X[ix] = np.c_[r*np.sin(t), r*np.cos(t)]
y[ix] = j
# lets visualize the data
plt.scatter(X[:, 0], X[:, 1], c=y, s=40, cmap=plt.cm.Spectral)
plt.show()
# 创建 spiral 数据集
RANDOM_SEED = 42
import numpy as np
N = 100 # 每一类点的数量
D = 2 # 维度
K = 3 # 类别的数量
X = np.zeros((N*K, D))
y = np.zeros(N*K, dtype='uint8') # 类别标签
for j in range(K):
ix = range(N*j, N*(j+1))
r = np.linspace(0.0, 1, N) # 半径
t = np.linspace(j*4, (j+1)*4, N) + np.random.randn(N) * 0.2 # theta
X[ix] = np.c_[r*np.sin(t), r*np.cos(t)]
y[ix] = j
# 数据可视化
plt.scatter(X[:,0], X[:,1],c=y, s=40, cmap=plt.cm.Spectral)
plt.show()
print(X[:5],y[:5])
print(X.shape, y.shape)
可以看出我们的数据,输入是2,输出是3,3个类别嘛
# 将数据转化为tensor
X = torch.from_numpy(X).type(torch.float)
y = torch.from_numpy(y).type(torch.LongTensor)
X.dtype, y.dtype
# 将数据划分为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X,
y,
test_size=0.2,
random_state=RANDOM_SEED)
len(X_train), len(X_test), len(y_train), len(y_test)
len(X), len(y)
# 设备无关
device = "cuda" if torch.cuda.is_available() else "cpu"
device
# 将数据放到统一的设备上
X_train, y_train = X_train.to(device), y_train.to(device)
X_test, y_test = X_test.to(device), y_test.to(device)
# 创建模型
class SpiralModel(nn.Module):
def __init__(self, input_features, output_features, hidden_layers):
super().__init__()
self.linear_stack = nn.Sequential(
nn.Linear(in_features=input_features, out_features=hidden_layers),
nn.Tanh(),
nn.Linear(in_features=hidden_layers, out_features=hidden_layers),
nn.Tanh(),
nn.Linear(in_features=hidden_layers, out_features=output_features)
)
def forward(self, x):
return self.linear_stack(x)
model_1 = SpiralModel(2, 3, 10).to(device)
model_1
# 损失函数和优化器
loss_fn = nn.CrossEntropyLoss()
optimizer = optim.Adam(params=model_1.parameters(),
lr=0.1)
print(y_logits.shape)
print(y_train.shape)
print(y_preds)
torch.manual_seed(RANDOM_SEED)
torch.cuda.manual_seed(RANDOM_SEED)
# 训练和测试
epochs = 100
for epoch in range(epochs):
# 训练
model_1.train()
y_logits = model_1(X_train)
y_preds = torch.softmax(y_logits, dim=1).argmax(dim=1)
loss = loss_fn(y_logits,
y_train)
acc = accuracy_fn(y_true=y_train,
y_pred=y_preds)
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 测试
model_1.eval()
with torch.inference_mode():
test_logits = model_1(X_test)
test_preds = torch.softmax(test_logits, dim=1).argmax(dim=1)
test_loss = loss_fn(test_logits,
y_test)
test_acc = accuracy_fn(y_true=y_test,
y_pred=test_preds)
# 打印输出
if epoch % 10 == 0:
print(f"Epoch:{epoch} | Train Loss:{loss:.4f} | Train Accuracy:{acc:.2f}% | Test Loss:{test_loss:.4f} | Test Accuracy:{test_acc:.2f}%")
# 可视化看一下
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.title("Training")
plot_decision_boundary(model=model_1, X=X_train, y=y_train)
plt.subplot(1, 2, 2)
plt.title("Testing")
plot_decision_boundary(model=model_1, X=X_test, y=y_test)
哇,看这个图像拟合的多么好,太厉害了!
This work is so good!
BB,今天的学习就到这里啦!
话说户部巷烤面筋尊嘟嘎嘎好吃捏
BB,如果文档对您有用的话,记得给我点个赞赞撒!
靴靴BB,谢谢BB~