前言
文章概述《深度学习详解》- 2
书中深入探讨了深度学习模型的应用及其面临的挑战,特别是模型的偏差、优化问题、过拟合及其解决方案。首先,作者指出在应用机器学习算法时,应先检查训练数据的损失,以评估模型是否在训练集上良好学习。接着,详细讨论了模型偏差和优化问题对模型性能的影响,提供了通过增加模型复杂度、改变模型结构或采用不同优化算法来改善模型表现的方法。此外,文章着重讲解了过拟合现象的原因及对策,包括增加训练数据、使用正则化和dropout等技术来减少模型的过度拟合。文中还介绍了交叉验证作为一种有效的方法来选择最佳模型,以避免仅基于训练数据或验证数据作出的决策导致的过拟合或欠拟合问题。最后,文章提出了应对不匹配问题的策略,强调了解题的关键在于对训练集和测试集之间差异的理解,并据此采取适当的措施。整体而言,本文全面地概述了深度学习模型开发过程中遇到的主要问题及其解决方法,为读者提供了一份宝贵的实践指南。
个人学习笔记以及概念梳理,望对大家有所帮助。
思维导图2
常见的几种过拟合的类型
补充说明
1. 模型复杂度过高
描述:当模型具有过多的自由度或参数时,它可能会过分地拟合训练数据中的噪声。
2. 特征选择不当
描述:如果使用的特征与目标变量关联不大,模型可能会学习到无关紧要的信息。
3. 训练数据不足
描述:当训练数据量不足以支撑模型的学习时,模型可能会过度拟合已有的数据。
4. 模型结构不合适
描述:如果模型结构不适合解决手头的问题,也可能导致过拟合。
5. 正则化不足
描述:没有适当使用正则化技术,模型可能过于复杂。
涉及的一些术语
术语 | 解释 |
模型偏差(Model Bias) | 当模型过于简单,无法捕捉数据中的复杂模式时出现的现象。这种情况下,即使经过充分训练,模型也无法很好地拟合训练数据。 |
模型灵活性(Model Flexibility) | 模型能够适应数据变化的程度。更灵活的模型可以拟合更复杂的函数,但也更容易过拟合。 |
优化问题(Optimization Problem) | 在训练模型时寻找最佳参数集以最小化损失函数的过程。优化问题可能由于算法陷入局部最小值而难以解决。 |
局部最小值(Local Minimum) | 在优化过程中,损失函数的某个值低于其邻近点但并非全局最小值的位置。 |
数据增强(Data Augmentation) | 通过对现有数据进行变换生成新数据样本的技术,以提高模型的泛化能力。例如,对图像进行旋转、缩放或翻转等操作。 |
正则化(Regularization) | 一种防止过拟合的技术,通过添加惩罚项来限制模型参数的大小,从而使模型更加简单。 |
早停(Early Stopping) | 在验证集性能开始恶化时提前终止训练的一种策略,用于避免过拟合。 |
丢弃法(Dropout Method) | 一种正则化技术,通过在训练过程中随机关闭一部分神经元来减少模型依赖特定路径的风险,从而提高模型的鲁棒性。 |
卷积神经网络(Convolutional Neural Network, CNN) | 专门设计用于处理网格状排列的数据(如图像)的深度学习模型。CNN 通过共享权重和局部感受野等机制减少参数数量,从而降低模型复杂度。 |
全连接网络(Fully-Connected Network) | 每一层的每个神经元都与下一层的所有神经元相连的神经网络。相比 CNN,全连接网络通常具有更高的灵活性和更多的参数。 |
学习过程遇到的一些问题的理解:
1深度学习中的优化问题与模型选择
补充部分(供参考)
5. 多目标优化问题
在某些场景下,我们需要同时优化多个目标函数。例如,在图像风格迁移任务中,我们既希望保持原图的内容特征,又希望获得目标风格的特性。
6. 在线学习优化问题
这种类型的问题涉及到连续的数据流,并且模型需要在接收到新数据后进行实时更新。
7. 分布式优化问题
随着数据集规模的增长,单机优化变得不再可行,需要利用多台机器并行处理数据。
8. 迁移学习优化问题
迁移学习是指将从一个任务中学到的知识应用于另一个相关任务上。
9. 联邦学习优化问题
联邦学习是一种隐私保护的学习框架,它允许在不集中数据的情况下进行模型训练。
代码运行
运用python 对波士顿房价数据集进行处理
其中波士顿房价数据集是一个典型的回归问题,我们的目标是预测房价的中位数(MEDV),这是一个连续的数值。这个问题没有显式约束,因此可以被归类为无约束优化问题。
线性回归模型
我们先构建一个简单的线性回归模型,若对回归其他类型感兴趣,可见入门Task1:《深度学习详解》(Datawhale X 李宏毅苹果书 AI夏令营)-CSDN博客
代码如下:
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
# Load the dataset
data_url = "http://lib.stat.cmu.edu/datasets/boston"
raw_df = pd.read_csv(data_url, sep="\s+", skiprows=22, header=None)
data = np.hstack([raw_df.values[::2, :], raw_df.values[1::2, :2]])
label = raw_df.values[1::2, 2]
# Convert data to tensors
data_tensor = torch.tensor(data, dtype=torch.float32)
label_tensor = torch.tensor(label, dtype=torch.float32).view(-1, 1)
# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(data_tensor, label_tensor, test_size=0.2, random_state=42)
# 定义线性回归模型
class LinearRegression(nn.Module):
def __init__(self, input_dim):
super(LinearRegression, self).__init__()
self.linear = nn.Linear(input_dim, 1)
def forward(self, x):
return self.linear(x)
# 数据预处理
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 将数据转换为 PyTorch 张量
X_train_tensor = torch.from_numpy(X_train_scaled).float()
X_test_tensor = torch.from_numpy(X_test_scaled).float()
y_train_tensor = y_train.float().view(-1, 1) # 修改这一行
y_test_tensor = y_test.float().view(-1, 1)
# 初始化模型、损失函数和优化器
input_dim = X_train.shape[1]
model = LinearRegression(input_dim)
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
# 训练模型
num_epochs = 1000
for epoch in range(num_epochs):
# 前向传播
outputs = model(X_train_tensor)
loss = criterion(outputs, y_train_tensor)
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (epoch + 1) % 100 == 0:
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')
# 评估模型
model.eval()
with torch.no_grad():
y_pred = model(X_test_tensor)
mse = criterion(y_pred, y_test_tensor).item()
r2_score = 1 - (torch.sum((y_test_tensor - y_pred) ** 2) / torch.sum((y_test_tensor - torch.mean(y_test_tensor)) ** 2)).item()
# 打印评估指标
print(f'Test MSE: {mse:.4f}')
print(f'R2 Score: {r2_score:.4f}')
# 可视化预测结果
plt.figure(figsize=(10, 6))
plt.plot(range(len(y_test_tensor[:30])), y_test_tensor[:30].numpy(), 'yo--', label='Actual')
plt.plot(range(len(y_pred[:30])), y_pred[:30].detach().numpy(), 'bo-', label='Predicted')
# 添加文本框来显示评估指标
textstr = f'Test MSE: {mse:.4f}\nR2 Score: {r2_score:.4f}'
props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
plt.text(0.05, 0.95, textstr, transform=plt.gca().transAxes, fontsize=12,
verticalalignment='top', bbox=props)
plt.title('Linear Model Prediction')
plt.legend()
plt.show()
运行结果:
可见,还是效果还是不错的,能大致跟上变化趋势
多层感知机(MLP)
之后,基于波士顿房价数据集这类问题,通常使用的是数值型特征而非图像数据,所以我就选择了较为简单的深度学习模型,如多层感知机(MLP)。
代码如下:
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
# Load the dataset
data_url = "http://lib.stat.cmu.edu/datasets/boston"
raw_df = pd.read_csv(data_url, sep="\s+", skiprows=22, header=None)
data = np.hstack([raw_df.values[::2, :], raw_df.values[1::2, :2]])
label = raw_df.values[1::2, 2]
# Convert data to tensors
data_tensor = torch.tensor(data, dtype=torch.float32)
label_tensor = torch.tensor(label, dtype=torch.float32).view(-1, 1)
# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(data_tensor, label_tensor, test_size=0.2, random_state=42)
# 数据预处理
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 将数据转换为 PyTorch 张量
X_train_tensor = torch.from_numpy(X_train_scaled).float()
X_test_tensor = torch.from_numpy(X_test_scaled).float()
y_train_tensor = y_train.float().view(-1, 1)
y_test_tensor = y_test.float().view(-1, 1)
# 定义多层感知机模型
class MLPRegressor(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super(MLPRegressor, self).__init__()
self.fc1 = nn.Linear(input_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, hidden_dim)
self.fc3 = nn.Linear(hidden_dim, output_dim)
self.relu = nn.ReLU()
def forward(self, x):
x = self.relu(self.fc1(x))
x = self.relu(self.fc2(x))
return self.fc3(x)
# 初始化模型、损失函数和优化器
input_dim = X_train.shape[1]
hidden_dim = 64 # 隐藏层大小
output_dim = 1
model = MLPRegressor(input_dim, hidden_dim, output_dim)
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001) # 使用Adam优化器并调整学习率
# 训练模型
num_epochs = 1000
for epoch in range(num_epochs):
# 前向传播
outputs = model(X_train_tensor)
loss = criterion(outputs, y_train_tensor)
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (epoch + 1) % 100 == 0:
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')
# 评估模型
model.eval()
with torch.no_grad():
y_pred = model(X_test_tensor)
mse = criterion(y_pred, y_test_tensor).item()
r2_score = 1 - (torch.sum((y_test_tensor - y_pred) ** 2) / torch.sum((y_test_tensor - torch.mean(y_test_tensor)) ** 2)).item()
# 打印评估指标
print(f'Test MSE: {mse:.4f}')
print(f'R2 Score: {r2_score:.4f}')
# 可视化预测结果
plt.figure(figsize=(10, 6))
plt.plot(range(len(y_test_tensor[:30])), y_test_tensor[:30].numpy(), 'yo--', label='Actual')
plt.plot(range(len(y_pred[:30])), y_pred[:30].detach().numpy(), 'bo-', label='Predicted')
# 添加文本框来显示评估指标
textstr = f'Test MSE: {mse:.4f}\nR2 Score: {r2_score:.4f}'
props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
plt.text(0.05, 0.95, textstr, transform=plt.gca().transAxes, fontsize=12,
verticalalignment='top', bbox=props)
plt.title('MLP Model Prediction')
plt.legend()
plt.show()
运行结果:
可见,虽mse几乎下降了一倍,但还是有一定的优化空间的。
进一步可供优化的方法如下
1. 特征选择与工程
特征选择:可以通过相关性分析、主成分分析(PCA)等方法来确定哪些特征对预测最有用。
特征组合:选择几个特征的组合,构成新的特征来进行预测。
2. 模型选择与调参
隐藏层数量:增加或减少隐藏层数量以找到最佳模型。
隐藏层大小:调整每个隐藏层的神经元数量。
激活函数:尝试不同的激活函数(如ReLU、tanh等)以观察效果。
正则化:使用L1、L2正则化或Dropout来防止过拟合。
学习率:调整学习率以加快收敛速度或避免振荡。
3. 优化算法
学习率调度:使用学习率衰减策略或动态调整学习率。
优化器:尝试不同的优化器,如Adam、RMSprop等,以找到最适合任务的优化器。
4. 批量大小与迭代次数
批量大小:调整批量大小可以影响训练速度和模型性能。
迭代次数:增加迭代次数可以改善模型性能,但也可能导致过拟合。
5. 早停策略
早停:在验证集上的性能不再改善时停止训练,以防止过拟合。
6. 交叉验证
k折交叉验证:使用交叉验证来评估模型的泛化能力。
7. 集成学习
Bagging:通过集成多个模型的结果来提高稳定性。
Boosting:通过逐步修正错误来增强模型性能。
8. 超参数调优
网格搜索:系统地遍历超参数空间。
随机搜索:随机选择超参数组合进行测试。
贝叶斯优化:使用贝叶斯方法来优化超参数选择。
9. 数据增强
合成数据:可以使用合成数据来扩大训练集。
噪声注入:在训练数据中添加轻微的噪声来增强模型的鲁棒性。
10. 模型融合
Stacking:将多个模型的结果组合起来,以提高整体预测性能。
我们分别简单展示运行以下优化方式
1. 特征选择与工程
特征选择:可以通过相关性分析、主成分分析(PCA)等方法来确定哪些特征对预测最有用。
代码如下:
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
import seaborn as sns
# Load the dataset
data_url = "http://lib.stat.cmu.edu/datasets/boston"
raw_df = pd.read_csv(data_url, sep="\s+", skiprows=22, header=None)
data = np.hstack([raw_df.values[::2, :], raw_df.values[1::2, :2]])
label = raw_df.values[1::2, 2]
# 定义多层感知机模型
class MLPRegressor(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super(MLPRegressor, self).__init__()
self.fc1 = nn.Linear(input_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, hidden_dim)
self.fc3 = nn.Linear(hidden_dim, output_dim)
self.relu = nn.ReLU()
def forward(self, x):
x = self.relu(self.fc1(x))
x = self.relu(self.fc2(x))
return self.fc3(x)
# Create DataFrame with feature names
feature_names = ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT']
df = pd.DataFrame(np.hstack([data, label[:, None]]), columns=feature_names + ['MEDV'])
# Compute correlation matrix
corr_matrix = df.corr()
# 绘制热力图
plt.figure(figsize=(10, 8))
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Correlation Matrix Heatmap')
plt.show()
# Initialize lists to store MSE and R2 scores
mse_values = []
r2_scores = []
for i in range(13):
# Select features based on correlation with target variable
correlated_features = corr_matrix.nlargest(int(i+2), 'MEDV')['MEDV'].index
correlated_features = correlated_features.drop('MEDV') # Exclude target variable from features
# Select the top 5 most correlated features
selected_features = df[correlated_features].values
target = df['MEDV'].values
# Convert data to tensors
data_tensor = torch.tensor(selected_features, dtype=torch.float32)
label_tensor = torch.tensor(target, dtype=torch.float32).view(-1, 1)
# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(data_tensor, label_tensor, test_size=0.2, random_state=42)
# 数据预处理
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 将数据转换为 PyTorch 张量
X_train_tensor = torch.from_numpy(X_train_scaled).float()
X_test_tensor = torch.from_numpy(X_test_scaled).float()
y_train_tensor = y_train.float().view(-1, 1)
y_test_tensor = y_test.float().view(-1, 1)
# 初始化模型、损失函数和优化器
input_dim = X_train.shape[1]
hidden_dim = 64 # 隐藏层大小
output_dim = 1
model = MLPRegressor(input_dim, hidden_dim, output_dim)
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001) # 使用Adam优化器并调整学习率
# 训练模型
num_epochs = 1000
for epoch in range(num_epochs):
# 前向传播
outputs = model(X_train_tensor)
loss = criterion(outputs, y_train_tensor)
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 评估模型
model.eval()
with torch.no_grad():
y_pred = model(X_test_tensor)
mse = criterion(y_pred, y_test_tensor).item()
r2_score = 1 - (torch.sum((y_test_tensor - y_pred) ** 2) / torch.sum((y_test_tensor - torch.mean(y_test_tensor)) ** 2)).item()
# 打印评估指标
print(f'Features used: {correlated_features.tolist()}')
print(f'Test MSE: {mse:.4f}')
print(f'R2 Score: {r2_score:.4f}')
# Store the metrics
mse_values.append(mse)
r2_scores.append(r2_score)
# 绘制MSE变化的折线图
plt.figure(figsize=(10, 6))
plt.plot(range(2, 15), mse_values, 'bo-', label='MSE')
# 添加每个数据点的MSE数值
for i, mse in enumerate(mse_values):
plt.text(i + 2, mse, f'{mse:.4f}', ha='center', va='bottom', fontsize=8)
plt.title('MSE vs Number of Selected Features')
plt.xlabel('Number of Features')
plt.ylabel('MSE')
plt.xticks(range(2, 15))
plt.legend()
plt.grid(True)
# 显示图表
plt.show()
根据热力图,展示特征与特征之间的关联度
我们保留影响比较的几个特征,依旧使用多层感知机(MLP),来看看mse随特征数量减少的变化
注:横坐标的数值是 选择预测的特征数量 与 目标数值预测数量(1个)之和
可见,还是得把涉及的特征都运用,mse才最小
初步认为造成此的原因:
1.这几个特征确实对目标预测有较大的影响,不可去除
2.此数据集可能预先已经被初步处理过了,留下的特征都比较重要
3. 优化算法
学习率调度:使用学习率衰减策略或动态调整学习率。
优化器:尝试不同的优化器,如Adam、RMSprop和SGD等,以找到最适合任务的优化器。
代码如下:
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
# Load the dataset
data_url = "http://lib.stat.cmu.edu/datasets/boston"
raw_df = pd.read_csv(data_url, sep="\s+", skiprows=22, header=None)
data = np.hstack([raw_df.values[::2, :], raw_df.values[1::2, :2]])
label = raw_df.values[1::2, 2]
# Convert data to tensors
data_tensor = torch.tensor(data, dtype=torch.float32)
label_tensor = torch.tensor(label, dtype=torch.float32).view(-1, 1)
# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(data_tensor, label_tensor, test_size=0.2, random_state=42)
# 数据预处理
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 将数据转换为 PyTorch 张量
X_train_tensor = torch.from_numpy(X_train_scaled).float()
X_test_tensor = torch.from_numpy(X_test_scaled).float()
y_train_tensor = y_train.float().view(-1, 1)
y_test_tensor = y_test.float().view(-1, 1)
# 定义多层感知机模型
class MLPRegressor(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super(MLPRegressor, self).__init__()
self.fc1 = nn.Linear(input_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, hidden_dim)
self.fc3 = nn.Linear(hidden_dim, output_dim)
self.relu = nn.ReLU()
def forward(self, x):
x = self.relu(self.fc1(x))
x = self.relu(self.fc2(x))
return self.fc3(x)
# 初始化模型、损失函数和优化器
input_dim = X_train.shape[1]
hidden_dim = 64 # 隐藏层大小
output_dim = 1
# 定义一个函数来训练和评估模型
def train_and_evaluate_model(optimizer_type, learning_rate=0.001):
# 初始化模型
model = MLPRegressor(input_dim, hidden_dim, output_dim)
# 选择优化器
if optimizer_type == 'Adam':
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
elif optimizer_type == 'RMSprop':
optimizer = optim.RMSprop(model.parameters(), lr=learning_rate)
elif optimizer_type == 'SGD':
optimizer = optim.SGD(model.parameters(), lr=learning_rate, momentum=0.9)
criterion = nn.MSELoss()
# 训练模型
num_epochs = 1000
for epoch in range(num_epochs):
# 前向传播
outputs = model(X_train_tensor)
loss = criterion(outputs, y_train_tensor)
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (epoch + 1) % 100 == 0:
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')
# 评估模型
model.eval()
with torch.no_grad():
y_pred = model(X_test_tensor)
mse = criterion(y_pred, y_test_tensor).item()
r2_score = 1 - (torch.sum((y_test_tensor - y_pred) ** 2) / torch.sum(
(y_test_tensor - torch.mean(y_test_tensor)) ** 2)).item()
# 返回评估指标
return mse, r2_score, model, y_pred
# 使用不同优化器训练模型并评估性能
optimizers = ['Adam', 'RMSprop', 'SGD']
mse_values = []
r2_scores = []
best_optimizer = None
best_mse = float('inf')
best_r2_score = None
best_model = None
best_y_pred = None
for optimizer in optimizers:
mse, r2, model, y_pred = train_and_evaluate_model(optimizer)
mse_values.append(mse)
r2_scores.append(r2)
if mse < best_mse:
best_mse = mse
best_r2_score = r2
best_optimizer = optimizer
best_model = model
best_y_pred = y_pred
print(f'{optimizer} Test MSE: {mse:.4f}')
print(f'{optimizer} R2 Score: {r2:.4f}')
# 可视化不同优化器的结果
plt.figure(figsize=(10, 6))
plt.plot(optimizers, mse_values, 'bo-', label='MSE')
plt.title('MSE vs Optimizer')
plt.xlabel('Optimizer')
plt.ylabel('MSE')
plt.xticks(optimizers)
plt.legend()
plt.grid(True)
plt.show()
# 可视化预测结果
plt.figure(figsize=(10, 6))
plt.plot(range(len(y_test_tensor[:30])), y_test_tensor[:30].numpy(), 'yo--', label='Actual')
plt.plot(range(len(best_y_pred[:30])), best_y_pred[:30].detach().numpy(), 'bo-', label='Predicted')
# 添加文本框来显示评估指标
textstr = f'Test MSE: {best_mse:.4f}\nR2 Score: {best_r2_score:.4f}'
props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
plt.text(0.05, 0.95, textstr, transform=plt.gca().transAxes, fontsize=12,
verticalalignment='top', bbox=props)
plt.title('MLP Model Prediction Using ' + best_optimizer)
plt.legend()
plt.show()
运行结果:
在确定最好的优化器后,再次以此进行运行
可见,mse有所下降。
注意:每次运行的结果可能会不同,优化器也会随之改变,若不想改变可预先固定 随机种子 之类影响因素。
6. 交叉验证
k折交叉验证:使用交叉验证来评估模型的泛化能力。
代码如下:
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import KFold
# Load the dataset
data_url = "http://lib.stat.cmu.edu/datasets/boston"
raw_df = pd.read_csv(data_url, sep="\s+", skiprows=22, header=None)
data = np.hstack([raw_df.values[::2, :], raw_df.values[1::2, :2]])
label = raw_df.values[1::2, 2]
# Convert data to tensors
data_tensor = torch.tensor(data, dtype=torch.float32)
label_tensor = torch.tensor(label, dtype=torch.float32).view(-1, 1)
# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(data_tensor, label_tensor, test_size=0.2, random_state=42)
# 数据预处理
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 将数据转换为 PyTorch 张量
X_train_tensor = torch.from_numpy(X_train_scaled).float()
X_test_tensor = torch.from_numpy(X_test_scaled).float()
y_train_tensor = y_train.float().view(-1, 1)
y_test_tensor = y_test.float().view(-1, 1)
# 定义多层感知机模型
class MLPRegressor(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super(MLPRegressor, self).__init__()
self.fc1 = nn.Linear(input_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, hidden_dim)
self.fc3 = nn.Linear(hidden_dim, output_dim)
self.relu = nn.ReLU()
def forward(self, x):
x = self.relu(self.fc1(x))
x = self.relu(self.fc2(x))
return self.fc3(x)
# 初始化模型、损失函数和优化器
input_dim = X_train.shape[1]
hidden_dim = 64 # 隐藏层大小
output_dim = 1
# 定义一个函数来训练和评估模型
def train_and_evaluate_model(optimizer_type, learning_rate=0.001):
# 初始化模型
model = MLPRegressor(input_dim, hidden_dim, output_dim)
# 选择优化器
if optimizer_type == 'Adam':
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
elif optimizer_type == 'RMSprop':
optimizer = optim.RMSprop(model.parameters(), lr=learning_rate)
elif optimizer_type == 'SGD':
optimizer = optim.SGD(model.parameters(), lr=learning_rate, momentum=0.9)
criterion = nn.MSELoss()
# 训练模型
num_epochs = 1000
for epoch in range(num_epochs):
# 前向传播
outputs = model(X_train_tensor)
loss = criterion(outputs, y_train_tensor)
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (epoch + 1) % 100 == 0:
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')
# 评估模型
model.eval()
with torch.no_grad():
y_pred = model(X_test_tensor)
mse = criterion(y_pred, y_test_tensor).item()
r2_score = 1 - (torch.sum((y_test_tensor - y_pred) ** 2) / torch.sum(
(y_test_tensor - torch.mean(y_test_tensor)) ** 2)).item()
# 返回评估指标
return mse, r2_score, model, y_pred
# 使用不同优化器训练模型并评估性能
optimizers = ['Adam', 'RMSprop', 'SGD']
mse_values = []
r2_scores = []
best_optimizer = None
best_mse = float('inf')
best_r2_score = None
best_model = None
best_y_pred = None
for optimizer in optimizers:
mse, r2, model, y_pred = train_and_evaluate_model(optimizer)
mse_values.append(mse)
r2_scores.append(r2)
if mse < best_mse:
best_mse = mse
best_r2_score = r2
best_optimizer = optimizer
best_model = model
best_y_pred = y_pred
print(f'{optimizer} Test MSE: {mse:.4f}')
print(f'{optimizer} R2 Score: {r2:.4f}')
# 可视化不同优化器的结果
plt.figure(figsize=(10, 6))
plt.plot(optimizers, mse_values, 'bo-', label='MSE')
plt.title('MSE vs Optimizer')
plt.xlabel('Optimizer')
plt.ylabel('MSE')
plt.xticks(optimizers)
plt.legend()
plt.grid(True)
plt.show()
# 展示没有使用K折交叉验证的情况下最佳模型的预测结果
plt.figure(figsize=(10, 6))
plt.plot(range(len(y_test_tensor[:30])), y_test_tensor[:30].numpy(), 'yo--', label='Actual')
plt.plot(range(len(best_y_pred[:30])), best_y_pred[:30].detach().numpy(), 'bo-', label='Predicted')
# 添加文本框来显示评估指标
textstr = f'Test MSE: {best_mse:.4f}\nR2 Score: {best_r2_score:.4f}'
props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
plt.text(0.05, 0.95, textstr, transform=plt.gca().transAxes, fontsize=12,
verticalalignment='top', bbox=props)
plt.title('MLP Model Prediction Without K-Fold Cross-Validation Using ' + best_optimizer)
plt.legend()
plt.show()
# 定义K折交叉验证的参数
k_folds = 7
kf = KFold(n_splits=k_folds, shuffle=True, random_state=42)
# 初始化用于存储每轮验证结果的列表
mse_values_kfold = []
r2_scores_kfold = []
# 对最佳优化器进行K折交叉验证
for fold, (train_indices, val_indices) in enumerate(kf.split(X_train_scaled)):
print(f"Processing Fold {fold + 1}/{k_folds}")
# 获取训练集和验证集
X_train_fold, y_train_fold = X_train_scaled[train_indices], y_train_tensor[train_indices]
X_val_fold, y_val_fold = X_train_scaled[val_indices], y_train_tensor[val_indices]
# 转换为PyTorch张量
X_train_fold_tensor = torch.from_numpy(X_train_fold).float()
y_train_fold_tensor = torch.from_numpy(y_train_fold.numpy()).float().view(-1, 1)
X_val_fold_tensor = torch.from_numpy(X_val_fold).float()
y_val_fold_tensor = torch.from_numpy(y_val_fold.numpy()).float().view(-1, 1)
# 初始化模型
model = MLPRegressor(input_dim, hidden_dim, output_dim)
# 使用最佳优化器进行训练
optimizer = optim.Adam(model.parameters(), lr=0.001) # 使用之前找到的最佳优化器类型
criterion = nn.MSELoss()
# 训练模型
num_epochs = 1000
for epoch in range(num_epochs):
# 前向传播
outputs = model(X_train_fold_tensor)
loss = criterion(outputs, y_train_fold_tensor)
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (epoch + 1) % 100 == 0:
print(f'Fold {fold + 1}: Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')
# 评估模型
model.eval()
with torch.no_grad():
y_pred_val = model(X_val_fold_tensor)
mse = criterion(y_pred_val, y_val_fold_tensor).item()
r2_score = 1 - (torch.sum((y_val_fold_tensor - y_pred_val) ** 2) / torch.sum(
(y_val_fold_tensor - torch.mean(y_val_fold_tensor)) ** 2)).item()
# 存储评估指标
mse_values_kfold.append(mse)
r2_scores_kfold.append(r2_score)
print(f'Fold {fold + 1}: Validation MSE: {mse:.4f}')
print(f'Fold {fold + 1}: Validation R2 Score: {r2_score:.4f}')
# 计算平均MSE和R2 Score
avg_mse_kfold = sum(mse_values_kfold) / len(mse_values_kfold)
avg_r2_score_kfold = sum(r2_scores_kfold) / len(r2_scores_kfold)
print(f'Average Validation MSE: {avg_mse_kfold:.4f}')
print(f'Average Validation R2 Score: {avg_r2_score_kfold:.4f}')
# 使用K折交叉验证后最佳模型的预测结果
# 为了得到最终的预测结果,我们可以使用整个训练集训练一个模型
model_kfold = MLPRegressor(input_dim, hidden_dim, output_dim)
optimizer_kfold = optim.Adam(model_kfold.parameters(), lr=0.001) # 使用最佳优化器
criterion = nn.MSELoss()
# 训练模型
num_epochs = 1000
for epoch in range(num_epochs):
# 前向传播
outputs = model_kfold(X_train_tensor)
loss = criterion(outputs, y_train_tensor)
# 反向传播和优化
optimizer_kfold.zero_grad()
loss.backward()
optimizer_kfold.step()
if (epoch + 1) % 100 == 0:
print(f'Final Model: Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')
# 评估模型
model_kfold.eval()
with torch.no_grad():
y_pred_kfold = model_kfold(X_test_tensor)
mse_kfold = criterion(y_pred_kfold, y_test_tensor).item()
r2_score_kfold = 1 - (torch.sum((y_test_tensor - y_pred_kfold) ** 2) / torch.sum(
(y_test_tensor - torch.mean(y_test_tensor)) ** 2)).item()
# 可视化预测结果
plt.figure(figsize=(10, 6))
plt.plot(range(len(y_test_tensor[:30])), y_test_tensor[:30].numpy(), 'yo--', label='Actual')
plt.plot(range(len(y_pred_kfold[:30])), y_pred_kfold[:30].detach().numpy(), 'bo-', label='Predicted')
# 添加文本框来显示评估指标
textstr = f'Test MSE: {mse_kfold:.4f}\nR2 Score: {r2_score_kfold:.4f}'
props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
plt.text(0.05, 0.95, textstr, transform=plt.gca().transAxes, fontsize=12,
verticalalignment='top', bbox=props)
plt.title('MLP Model Prediction With K-Fold Cross-Validation Using ' + best_optimizer)
plt.legend()
plt.show()
运行结果
在确定合适的优化器的基础上,对其使用k(k=7)折交叉验证,
![]() | ![]() |
发现,有使用k折交叉验证的mse相较确实有所优化。
最后,相较于单一的方法,多种方法共同作用下,获得的结果会更加优异。
思考点:
若还想进一步优化,大家可自行使用剩余的几种方法或深度神经网络等方法,来看看是否更好的效果。
小结
过拟合的原因
- 模型复杂度过高: 过多的自由度或参数使得模型能够学习到训练数据中的噪声。
- 特征选择不当: 使用与目标变量关联性不强的特征会导致模型学习无关紧要的信息。
- 训练数据不足: 数据量不足以支撑模型的学习需求。
- 模型结构不合适: 不适合问题本身的模型结构也会导致过拟合。
- 正则化不足: 缺乏适当的正则化技术会让模型过于复杂。
解决方案
- 特征选择与工程: 通过相关性分析、主成分分析(PCA)等方法来确定最有用的特征。
- 模型选择与调参: 调整隐藏层数量、隐藏层大小、激活函数等。
- 优化算法: 使用不同的优化器(如Adam、RMSprop等)和学习率调度策略。
- 批量大小与迭代次数: 调整批量大小和迭代次数以平衡训练速度和性能。
- 早停策略: 在验证集性能不再改善时停止训练,避免过拟合。
- 交叉验证: 使用k折交叉验证来评估模型的泛化能力。
- 集成学习: 通过Bagging和Boosting等技术提高模型的稳定性和性能。
- 超参数调优: 使用网格搜索、随机搜索或贝叶斯优化来选择最佳超参数。
- 数据增强: 通过合成数据或噪声注入来增强模型的鲁棒性。
- 模型融合: 使用Stacking等技术结合多个模型的结果。
波士顿房价数据集案例
-
线性回归模型:
- 实现了一个简单的线性回归模型,并使用均方误差(MSE)和R²分数评估模型性能。
- 结果显示模型能够较好地跟随数据的变化趋势。
-
多层感知机(MLP):
- 构建了一个包含两个隐藏层的多层感知机模型。
- 使用Adam优化器和较低的学习率进行训练。
- 结果表明MSE比线性回归模型更低,但仍有一定优化空间。
尝试优化策略
-
特征选择与工程:
- 在波士顿房价数据集上,所有的特征似乎都很重要,因此没有进行特征降维或删除。
- 可能原因是数据集已经进行了预处理,保留下来的特征都比较关键。
-
优化算法:
- 尝试了不同的优化器(如Adam)和学习率调度策略。
- Adam优化器通常能更好地处理稀疏梯度和非平稳目标函数。
-
交叉验证:
- 使用k折交叉验证来评估模型的泛化能力。
- 发现使用交叉验证后MSE有所优化。
思考
- 进一步优化:
- 可以尝试使用更深的神经网络结构或使用更高级的正则化技术。
- 使用不同的超参数调优策略,比如贝叶斯优化。
- 探索集成学习方法,如Stacking。