混淆矩阵 | 预测值:正(狗) | 预测值:负(猫) |
---|---|---|
真实值:正(狗) | 5 | 1 |
真实值:负(猫) | 0 | 4 |
混淆矩阵 | 预测值:正(狗) | 预测值:负(猫) |
---|---|---|
真实值:正(狗) | TP | FN |
真实值:负(猫) | FP | TN |
问题: 在多元回归中,R²(判定系数)有一个明显缺点:只要增加自变量(哪怕是不相关的变量),R²就会变大。这会导致模型看起来拟合得很好,但实际上可能加入了无用的变量,反而降低了模型的可靠性。
解决方法:为了修正这个问题,我们使用调整后的R²。它会根据样本量(n)和自变量个数(k)自动惩罚无意义的变量,避免R²被高估。计算公式如下:
调整后的 R 2 = 1 − ( 1 − R 2 ) × n − 1 n − k − 1 \text{调整后的} R^2 = 1 - (1 - R^2) \times \frac{n-1}{n-k-1} 调整后的R2=1−(1−R2)×n−k−1n−1
关键点:
在回归分析(尤其是多元回归)中,调整后的R²比普通R²更准确,因为它会惩罚无用的自变量,避免模型“虚高”的拟合效果。因此,我们通常用调整后的R²来评估模型的真实拟合度。
调整后的R²是否合格?临界值:0.5
import torch
from torch import nn
from torch.utils.data import DataLoader, TensorDataset
import torch.nn.functional as f
# 构造训练集
train_x = torch.rand(100, 28, 28)
train_y = torch.randn(100, 28, 28)
train_x = torch.cat((train_x, train_y), dim=0)
# 构造标签 前100个元素为1 后100个元素为0
labels = [1] * 100 + [0] * 100
# 将标签列转为张量
labels = torch.tensor(labels, dtype=torch.long)
# 设置网络结构
"""
__init__方法中定义三个全连接层:fc1、fc2和fc3。
forward方法实现前向传播过程,对输入数据进行展平,通过全连接层和激活函数进行处理。
num_flat_features方法用于计算输入数据的展平特征数量。
"""
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fc1 = nn.Linear(28 * 28, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 2)
def forward(self, x):
# 将输入张量x展平为一维张量,并计算展平后的特征数量
x = x.view(-1, self.num_flat_features(x))
x = f.relu(self.fc1(x))
x = f.relu(self.fc2(x))
x = self.fc3(x)
return x
# 计算输入x的展平特征数量的函数
def num_flat_features(self, x):
size = x.size()[1:] # 获取输入x除了第一个维度外的尺寸
num_features = 1 # 初始化特征数量为1
for s in size: # 遍历尺寸列表
num_features *= s # 计算特征数量
return num_features # 返回特征数量
# 训练集数据处理
class train_data_set(TensorDataset):
def __init__(self, train_features, train_labels):
self.x_data = train_features # 保存训练特征
self.y_data = train_labels # 保存训练标签
self.len = len(train_labels) # 保存训练样本数量
# 获取训练数据
def __getitem__(self, index):
return self.x_data[index], self.y_data[index]
# 获取训练数据数量
def __len__(self):
return self.len
# 设置损失函数
loss_func = nn.CrossEntropyLoss()
"""
get_k_fold_data函数用于将数据集分为K折,并返回当前折的训练集数据、训练集标签、验证集数据和验证集标签。
"""
# 设置k划分
def get_k_fold_data(k, i, x, y):
"""
获取k折交叉验证数据
:param k: 折数
:param i: 当前折的索引
:param x: 输入的数据
:param y: 标签
:return:
x_train(torch.Tensor):训练集数据
y_train(torch.Tensor):训练集标签
x_valid(torch.Tensor):验证集数据
y_valid(torch.Tensor):验证集标签
"""
assert k > 1
fold_size = x.shape[0] // k # 每折的大小
x_train, y_train = None, None
for j in range(k):
idx = slice(j * fold_size, (j + 1) * fold_size)
x_part, y_part = x[idx, :], y[idx]
if j == i:
x_valid, y_valid = x_part, y_part
elif x_train is None:
x_train, y_train = x_part, y_part
else:
x_train = torch.cat((x_train, x_part), dim=0)
y_train = torch.cat((y_train, y_part), dim=0)
return x_train, y_train, x_valid, y_valid
"""
k_fold函数则在K折交叉验证中循环训练模型,并计算和累积每折的训练集和验证集的损失与准确度。
"""
def k_fold(k, x_train, y_train, num_epochs=3, learning_rate=0.001, weight_decay=0.1, batch_size=5):
"""
进行k折验证
:param k: 折数
:param x_train: 训练数据
:param y_train: 训练标签
:param num_epochs: 训练轮数 默认值为3
:param learning_rate: 学习率 默认值为0.001
:param weight_decay: 权重衰减 默认值为0.1
:param batch_size: 批次大小 默认为5
:return:
train_loss_sum(float):训练集损失总和
valid_loss_sum(float):验证集损失总和
train_acc_sum(float):训练集准确度总和
valid_acc_sum(float):验证集准确度总和
"""
train_loss_sum, valid_loss_sum = 0.0, 0.0
train_acc_sum, valid_acc_sum = 0.0, 0.0
for i in range(k):
data = get_k_fold_data(k, i, x_train, y_train)
net = Net() # 创建网络实例
train_ls, valid_ls = train(net, *data, num_epochs, learning_rate, weight_decay, batch_size)
print('*' * 10, '第', i + 1, '折', '*' * 10)
print('训练集损失:%.6f' % train_ls[-1][0], '训练集准确度:%.4f' % valid_ls[-1][1],
'测试集损失:%.6f' % valid_ls[-1][0], '测试集准确度:%.4f' % valid_ls[-1][1])
train_loss_sum += train_ls[-1][0]
valid_loss_sum += valid_ls[-1][0]
train_acc_sum += train_ls[-1][1]
valid_acc_sum += valid_ls[-1][1]
print('#' * 5, '最终k折交叉验证结果', '#' * 5)
print('训练集累积损失:%.4f' % (train_loss_sum / k), '训练集累积准确度:%.4f' % (train_acc_sum / k),
'测试集累积损失:%.4f' % (valid_loss_sum / k), '测试集累积准确度:%.4f' % (valid_acc_sum / k))
return train_loss_sum, valid_loss_sum, train_acc_sum, valid_acc_sum
# 设置训练函数
def train(net, train_features, train_labels, test_features, test_labels, num_epochs, learning_rate, weight_decay,
batch_size):
"""
设置训练函数
:param net: 神经网络模型
:param train_features: 训练数据特征
:param train_labels: 训练数据标签
:param test_features: 测试数据特征
:param test_labels: 测试数据标签
:param num_epochs: 训练轮数
:param learning_rate: 学习率
:param weight_decay: 权重衰减
:param batch_size: 批次大小
:return:
train_ls: 训练集的损失和准确度损失
test_ls: 测试集的损失和准确度列表
"""
# 初始化训练集和测试集的损失和准确度列表
train_ls, test_ls = [], []
# 创建训练数据集和数据加载器
dataset = train_data_set(train_features, train_labels)
train_iter = DataLoader(dataset, batch_size, shuffle=True)
# 创建优化器
optimizer = torch.optim.Adam(params=net.parameters(), lr=learning_rate, weight_decay=weight_decay)
# 遍历每个训练轮次
for epoch in range(num_epochs):
# 遍历每个批次
for X, y in train_iter:
output = net(X)
loss = loss_func(output, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 将当前轮次的训练集损失和准确度添加到列表
train_ls.append(log_rmse(0, net, train_features, train_labels))
# 测试集损失和准确度添加到列表中
if test_labels is not None:
test_ls.append(log_rmse(1, net, test_features, test_labels))
# 返回训练集和测试集的损失和准确度列表
return train_ls, test_ls
# 设置准确度计算函数
def log_rmse(flag, net, x, y):
"""
计算对数均方根误差和准确度的函数
:param flag: 0表示训练集,1表示测试集
:param net: 评估的模型
:param x: 数据特征
:param y: 数据标签
:return:
loss.data.item(): 损失值
accuracy: 准确度
"""
# 如果是测试集,设置网络为评估模式
if flag == 1:
net.eval()
# 前向传播 获取预测结果
output = net(x)
result = torch.max(output, 1)[1].view(y.size())
# 计算正确预测的数量
corrects = (result.data == y.data).sum().item()
# 计算准确度
accuracy = corrects * 100.0 / len(y)
# 计算损失
loss = loss_func(output, y)
# 如果是测试集,设置网络为训练模式
net.train()
return loss.data.item(), accuracy
if __name__ == '__main__':
"""
执行k折交叉验证
"""
# 调用交叉验证函数
k_fold(10, train_x, labels)
********** 第 1 折 **********
训练集损失:0.039320 训练集准确度:100.0000 测试集损失:0.024422 测试集准确度:100.0000
********** 第 2 折 **********
训练集损失:0.042435 训练集准确度:100.0000 测试集损失:0.024841 测试集准确度:100.0000
********** 第 3 折 **********
训练集损失:0.043264 训练集准确度:100.0000 测试集损失:0.024778 测试集准确度:100.0000
********** 第 4 折 **********
训练集损失:0.033361 训练集准确度:100.0000 测试集损失:0.018814 测试集准确度:100.0000
********** 第 5 折 **********
训练集损失:0.039068 训练集准确度:100.0000 测试集损失:0.024000 测试集准确度:100.0000
********** 第 6 折 **********
训练集损失:0.039641 训练集准确度:95.0000 测试集损失:0.370682 测试集准确度:95.0000
********** 第 7 折 **********
训练集损失:0.040322 训练集准确度:95.0000 测试集损失:0.447232 测试集准确度:95.0000
********** 第 8 折 **********
训练集损失:0.038236 训练集准确度:95.0000 测试集损失:0.425054 测试集准确度:95.0000
********** 第 9 折 **********
训练集损失:0.036090 训练集准确度:95.0000 测试集损失:0.414253 测试集准确度:95.0000
********** 第 10 折 **********
训练集损失:0.037329 训练集准确度:100.0000 测试集损失:0.353675 测试集准确度:100.0000
##### 最终k折交叉验证结果 #####
训练集累积损失:0.0389 训练集累积准确度:100.0000 测试集累积损失:0.2128 测试集累积准确度:98.0000
********** 第 1 折 **********
训练集损失:0.690276 训练集准确度:0.0000 测试集损失:0.714697 训练集准确度:0.0000
********** 第 2 折 **********
训练集损失:0.686439 训练集准确度:0.0000 测试集损失:0.777147 测试集准确度:0.0000
train_y = torch.rand(100, 28, 28)
将其修改为:
train_y = torch.randn(100, 28, 28)
torch.rand
:生成的是 [0, 1)
区间内的均匀分布随机数。torch.randn
:生成的是标准正态分布(均值为0,方差为1)的随机数,取值范围更广。train_x
和 train_y
)都是从相同分布(如 rand
)生成的,那么这两类样本之间没有可区分的特征模式。更严重的是,rand
数据集中在 [0,1] 区间,导致模型输出难以区分两个类别,从而准确率始终为 0。train_x
和 train_y
都使用了 torch.rand
,它们本质上是同一类数据,只是被人为地赋予了不同的标签。这种构造方式使得模型无法学到任何有意义的分类边界。将 train_y = torch.rand(100, 28, 28)
改为:
train_y = torch.randn(100, 28, 28)
带来的变化包括:
对比项 | torch.rand |
torch.randn |
---|---|---|
分布类型 | 均匀分布 | 正态分布 |
数值范围 | [0, 1) | 大致 [-3, 3] |
样本差异性 | 小 | 大 |
是否适合用于分类任务 | 否 | 是 |
torch.randn
,train_x
和 train_y
的数据分布出现明显差异,这为模型提供了可学习的特征差异。因此,模型可以逐渐学习如何区分这两个类别,准确率也随之提升。错误点 | 原因 | 影响 | 解决方案 |
---|---|---|---|
使用 torch.rand 构造数据 |
数据分布单一、无类别差异 | 模型无法学习分类边界 | 改用 torch.randn 提供更大差异性 |
缺乏真实数据 | 输入无语义信息 | 模型无法收敛 | 使用真实图像数据(如 MNIST)效果更佳 |