@浙大疏锦行仔细回顾一下神经网络到目前的内容,没跟上进度的同学补一下进度。
●作业:对之前的信贷项目,利用神经网络训练下,尝试用到目前的知识点让代码更加规范和美观。
●探索性作业(随意完成):尝试进入nn.Module中,查看他的方法
python
import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import numpy as np
import pandas as pd
# 导入 Pipeline 和相关预处理工具
from sklearn.pipeline import Pipeline # 用于创建机器学习工作流
from sklearn.compose import ColumnTransformer # 用于将不同的预处理应用于不同的列,之前是对datafame的某一列手动处理,如果在pipeline中直接用standardScaler等函数就会对所有列处理,所以要用到这个工具
from sklearn.preprocessing import OrdinalEncoder, OneHotEncoder, StandardScaler # 用于数据预处理
from sklearn.impute import SimpleImputer # 用于处理缺失值
data = pd.read_csv('data.csv') # 读取数据
y = data['Credit Default']
X = data.drop(['Credit Default'], axis=1)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
object_cols = X.select_dtypes(include=['object']).columns.tolist()
# 有序分类特征 (对应你之前的标签编码)
# 注意:OrdinalEncoder默认编码为0, 1, 2... 对应你之前的1, 2, 3...需要在模型解释时注意
# 这里的类别顺序需要和你之前映射的顺序一致
ordinal_features = ['Home Ownership', 'Years in current job', 'Term']
# 定义每个有序特征的类别顺序,这个顺序决定了编码后的数值大小
ordinal_categories = [
['Own Home', 'Rent', 'Have Mortgage', 'Home Mortgage'], # Home Ownership 的顺序 (对应1, 2, 3, 4)
['< 1 year', '1 year', '2 years', '3 years', '4 years', '5 years', '6 years', '7 years', '8 years', '9 years', '10+ years'], # Years in current job 的顺序 (对应1-11)
['Short Term', 'Long Term'] # Term 的顺序 (对应0, 1)
]
# 先用众数填充分类特征的缺失值,然后进行有序编码
ordinal_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')), # 用众数填充分类特征的缺失值
('encoder', OrdinalEncoder(categories=ordinal_categories, handle_unknown='use_encoded_value', unknown_value=-1))
])
# 分类特征
nominal_features = ['Purpose'] # 使用原始列名
# 先用众数填充分类特征的缺失值,然后进行独热编码
nominal_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')), # 用众数填充分类特征的缺失值
('onehot', OneHotEncoder(handle_unknown='ignore', sparse_output=False)) # sparse_output=False 使输出为密集数组
])
# 连续特征
# 从X的列中排除掉分类特征,得到连续特征列表
continuous_features = X.columns.difference(object_cols).tolist() # 原始X中非object类型的列
# 先用众数填充缺失值,然后进行标准化
continuous_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')), # 用众数填充缺失值 (复现你的原始逻辑)
('scaler', StandardScaler()) # 标准化,一个好的实践
])
# --- 构建 ColumnTransformer ---
# 将不同的预处理应用于不同的列子集,构造一个完备的转化器
preprocessor = ColumnTransformer(
transformers=[
('ordinal', ordinal_transformer, ordinal_features),
('nominal', nominal_transformer, nominal_features),
('continuous', continuous_transformer, continuous_features)
],
remainder='passthrough' # 保留没有在transformers中指定的列(如果存在的话),或者 'drop' 丢弃
)
#对训练集和测试集应用预处理(必须先处理再转换为张量)
X_train_processed = preprocessor.fit_transform(X_train) # 训练集:拟合+转换
X_test_processed = preprocessor.transform(X_test) # 测试集:仅转换(复用训练集参数)
# 查看预处理后的特征数(关键:用于模型输入层维度)
print(f"预处理后训练集特征数:{X_train_processed.shape[1]}") # 假设输出为 M(例如20)
input_feature_num = X_train_processed.shape[1] # 存储特征数,用于模型定义
# 将数据转换为 PyTorch 张量,因为 PyTorch 使用张量进行训练
# y_train和y_test是整数,所以需要转化为long类型,如果是float32,会输出1.0 0.0
X_train = torch.FloatTensor(X_train_processed)
y_train = torch.LongTensor(y_train.values)
X_test = torch.FloatTensor(X_test_processed)
y_test = torch.LongTensor(y_test.values)
class MLP(nn.Module): # 定义一个多层感知机(MLP)模型,继承父类nn.Module
def __init__(self): # 初始化函数
super(MLP, self).__init__() # 调用父类的初始化函数
# 前三行是八股文,后面的是自定义的
self.fc1 = nn.Linear(input_feature_num, 20) # 输入层到隐藏层
self.relu = nn.ReLU()
self.fc2 = nn.Linear(20, 2) # 隐藏层到输出层
# 输出层不需要激活函数,因为后面会用到交叉熵函数cross_entropy,交叉熵函数内部有softmax函数,会把输出转化为概率
def forward(self, x):
out = self.fc1(x)
out = self.relu(out)
out = self.fc2(out)
return out
# 实例化模型
model = MLP()
# 分类问题使用交叉熵损失函数
criterion = nn.CrossEntropyLoss()
# 使用随机梯度下降优化器
optimizer = optim.SGD(model.parameters(), lr=0.01)
# # 使用自适应学习率的化器
# optimizer = optim.Adam(model.parameters(), lr=0.001)
# 训练模型
num_epochs = 20000 # 训练的轮数
# 用于存储每个 epoch 的损失值
losses = []
import time
start_time = time.time() # 记录开始时间
for epoch in range(num_epochs): # range是从0开始,所以epoch是从0开始
# 前向传播
outputs = model.forward(X_train) # 显式调用forward函数
# outputs = model(X_train) # 常见写法隐式调用forward函数,其实是用了model类的__call__方法
loss = criterion(outputs, y_train) # output是模型预测值,y_train是真实标签
# 反向传播和优化
optimizer.zero_grad() #梯度清零,因为PyTorch会累积梯度,所以每次迭代需要清零,梯度累计是那种小的bitchsize模拟大的bitchsize
loss.backward() # 反向传播计算梯度
optimizer.step() # 更新参数
# 记录损失值
losses.append(loss.item())
# 打印训练信息
if (epoch + 1) % 100 == 0: # range是从0开始,所以epoch+1是从当前epoch开始,每100个epoch打印一次
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
time_all = time.time() - start_time # 计算训练时间
print(f'Training time: {time_all:.2f} seconds')
import matplotlib.pyplot as plt
# 可视化损失曲线
plt.plot(range(num_epochs), losses)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss over Epochs')
plt.show()
