第二课堂:阿里云天池数据挖掘:二手车价值预测
数据读取与预处理
数据读取与合并清洗
- 首先,从指定路径读取训练集 ‘used_car_train_20200313.csv’和测试集’used_car_testB_20200421.csv’。使用
pd.concat([train_data, test_data])
将训练集和测试集合并,以便统一进行数据处理。
test_data = pd.read_csv('/gemini/data-1/used_car_testB_20200421.csv', sep=' ')
test_data.shape
# 导入数据
train_data = pd.read_csv('/gemini/data-1/used_car_train_20200313.csv', sep=' ')
train_data.shape
# 合并数据
data = pd.concat([train_data, test_data])
data.shape
-
对于数据中的-,使用
data = data.replace('-', '-1')
进行替换。作用
- 统一数据格式:将特殊的符号 ‘-’ 转换为一个明确的数值 ‘-1’,统一数据格式,便于后续的数据处理和分析。
- 填补缺失或未知值:在某些情况下,‘-’ 可能表示缺失或未知的数据。将其替换为一个特定的值(如 ‘-1’ )可以明确标识这些特殊情况,方便后续对缺失值或异常值进行处理。
- 适应模型输入要求:某些模型或算法可能对输入数据的格式有特定要求,将 ‘-’ 统一替换为 ‘-1’ 可以确保数据能够顺利输入到模型中进行计算和训练,避免因为特殊符号导致的错误或异常。
- 便于计算
-
对于power值大于 600 的,通过
data.loc[data['power']>600,'power'] = 600
进行限制。 -
对于离散特征中存在的缺失值,使用
data[col] = data[col].fillna('-1')
填充为-1。 -
去除name和regionCode等可能无关的列,通过
data.drop(['name', 'egionCode'], axis=1, inplace=True)
实现。data.columns data = data.replace('-', '-1') data.notRepairedDamage = data.notRepairedDamage.astype('float32') data.loc[data['power']>600,'power'] = 600 # 处理离散数据 for col in config['cate_cols']: data[col] = data[col].fillna('-1') data = oneHotEncode(data, config['cate_cols']) # 处理(可能)无关数据 data.drop(['name', 'regionCode'], axis=1, inplace=True) data.columns
特征工程
定义了oneHotEncode函数对离散特征进行One-Hot编码
def oneHotEncode(df, colNames):
for col in colNames:
dummies = pd.get_dummies(df[col], prefix=col)
df = pd.concat([df, dummies],axis=1)
df.drop([col], axis=1, inplace=True)
return df
One-Hot
种将分类变量转换为数字向量的编码方式。在 One-hot 编码中,对于具有 n 个不同类别的分类特征,会将其转换为一个 n 维的向量。对于每个样本,如果该特征的值属于某个类别,那么在对应类别的维度上为 1,其余维度上为 0。
缺点
- 如果类别数量很多,会导致特征维度大幅增加,可能引发维度灾难问题,增加计算成本和存储成本。
- 编码后的特征变得非常稀疏,数据中存在大量的 0。
优点
- 能够处理非数值型的分类数据,将其转化为模型可以处理的数值形式。
- 在一定程度上避免了类别之间的大小、顺序等不合理的假设
对于连续特征
# 处理连续数据
for col in config['num_cols']:
#缺失值用 0 填充
data[col] = data[col].fillna(0)
#进行归一化处理
data[col] = (data[col]-data[col].min()) / (data[col].max()-data[col].min())
归一化
Min-Max 归一化(线性归一化)
均值归一化
标准化/z值归一化
模型构建
网络结构设计
#定义Network类作为模型结构。包含多个线性层,每层后接批归一化层和ReLU激活函数,最后输出一个预测值。
class Network(nn.Module):
def __init__(self, in_dim, hidden_1, hidden_2, hidden_3, hidden_4):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(in_dim, hidden_1),
nn.BatchNorm1d(hidden_1),
nn.ReLU(),
nn.Linear(hidden_1, hidden_2),
nn.BatchNorm1d(hidden_2),
nn.ReLU(),
nn.Linear(hidden_2, hidden_3),
nn.BatchNorm1d(hidden_3),
nn.ReLU(),
nn.Linear(hidden_3, hidden_4),
nn.BatchNorm1d(hidden_4),
nn.ReLU(),
nn.Linear(hidden_4, 1)
)
def forward(self, x):
y = self.layers(x)
return y
ReLU(Rectified Linear Unit,修正线性单元)函数
是一种常用的激活函数,其数学表达式为:
f ( x ) = m a x ( 0 , x ) f(x)=max(0,x) f(x)=max(0,x)优点:
- 计算速度快:
- 缓解梯度消失问题:在深层神经网络中,Sigmoid 函数和 Tanh 函数的梯度在输入值较大或较小时会趋近于,导致反向传播过程中梯度消失,使得网络训练变得困难。ReLU 函数在输入大于时梯度为,一定程度上缓解了梯度消失的问题,有助于更有效地训练深层网络。
- 稀疏激活性:ReLU 函数会使一部分神经元的输出为,使得网络具有稀疏性,这有助于提取数据中的重要特征,并且减少参数的相互依存关系,降低过拟合的风险。
- 为什么没有relu函数,就无法拟合除一次函数的更多函数?
如果没有像 ReLU 这样的非线性激活函数,仅依靠线性组合(如全连接层的线性变换),神经网络就只能表示线性函数。
这是因为多个线性变换的组合仍然是一个线性变换。假设我们有两个线性层,第一层的输出为:
y
1
=
W
1
x
+
b
1
y_1=W_1x+b_1
y1=W1x+b1
第二层以第一层的输出作为输入,输出为:
y
2
=
W
2
y
1
+
b
2
=
W
2
(
W
1
x
+
b
1
)
+
b
2
y_2=W_2y_1+b_2=W_2(W_1x+b_1)+b_2
y2=W2y1+b2=W2(W1x+b1)+b2
仍然是输入x的线性函数。而现实世界中的问题和数据往往具有非线性的特征和模式。例如图像识别中,不同的对象和特征之间的关系是非线性的;在预测股票价格时,影响因素与价格之间的关系也是复杂的非线性关系。
引入像 ReLU 这样的非线性激活函数后,使得神经元的输出不再是输入的简单线性组合。例如 ReLU 函数将输入小于 0 的部分截断为 0,大于 0 的部分保持不变,这种非线性的变换使得神经网络能够学习和拟合输入数据中的非线性模式和特征,从而能够表示和拟合各种各样的复杂函数,而不仅仅局限于线性函数。
nn.Linear
是用于实现全连接层(fully connected layer)的模块。
全连接层的作用是对输入数据进行线性变换。假设输入数据的维度为 in_features
,输出数据的维度为 out_features
,那么 nn.Linear 层的作用可以用数学公式表示为:
y
=
x
W
T
+
b
y=xW^T+b
y=xWT+b
其中,x xx 是输入数据,大小为 (batch_size, in_features);W WW是权重矩阵,大小为 (out_features, in_features);b bb 是偏置向量,大小为 (out_features);y yy 是输出数据,大小为 (batch_size, out_features)。
例如,如果有一个输入数据x xx,形状为 (10, 20)(即批量大小为 10,特征维度为 20),定义一个全连接层 fc = nn.Linear(20, 30),那么经过该全连接层后的输出 y 的形状将为 (10, 30)
全连接层在神经网络中应用广泛,常用于对提取到的特征进行综合和分类等任务。
-
nn.BatchNorm1d:一维数据归一化
-
计算小批量数据的均值
η = 1 / m ∑ i = 1 n x i \eta=1/m\sum_{i=1}^{n}{x_i} η=1/mi=1∑nxi
其中m 是小批量中的样本数量。 -
计算小批量数据的方差
-
对输入进行标准化
其中ϵ是一个很小的常数(如1 e − 5 1e - 51e−5),用于防止除数为0 00。
-
进行缩放和平移操作
y i = γ x ^ i + β y_ i =γ \widehat{x} _ i +β yi=γx i+β
其中 γ(可学习的缩放参数)和 β 习的偏移参数)是网络学习得到的参数。
通过批归一化,可以使得每一层神经网络的输入分布相对稳定,有助于缓解梯度消失或爆炸问题,使训练更加稳定,提高训练速度,并且在一定程度上起到正则化的作用,减少过拟合的风险。始化权重:Xavierfor line in model.layers: if type(line) == nn.Linear: nn.init.xavier_uniform_(line.weight)
-
Xavier初始化(也称为Glorot初始化)是一种用于初始化神经网络权重的方法,由Xavier Glorot 和 Yoshua Bengio 提出。
Xavier初始化的基本思想是,对于一个具有n_in个输入神经元和n_out个输出神经元的全连接层,权重W的初始化值应从均值为0 00,方差为
2 / ( n − i n + n − o u t ) 2/(n-in+n-out) 2/(n−in+n−out)
的正态分布中采样,或者从
- 区间内的均匀分布中采样。
模拟训练
训练设置
配置超参数:
epoch为 10 次。
batch_size为 512 。
学习率learning_rate为 8e-3 。
指定使用cuda设备。
选择均方误差MSELoss作为损失函数。
使用Adam优化器,并设置学习率。
# 将数据转化为tensor,并移动到cpu或cuda上
train_features = torch.tensor(train_data.values, dtype=torch.float32, device=config['device'])
train_num = train_features.shape[0]
train_labels = torch.tensor(train_target.values, dtype=torch.float32, device=config['device'])
validation_features = torch.tensor(validation_data.values, dtype=torch.float32, device=config['device'])
validation_num = validation_features.shape[0]
validation_labels = torch.tensor(validation_target.values, dtype=torch.float32, device=config['device'])
# 特征长度
train_features[1].shape
# 定义损失函数和优化器
criterion = nn.MSELoss()
criterion.to(config['device'])
optimizer = optim.Adam(model.parameters(), lr=config['learning_rate'])
损失函数
均方误差
平均绝对误差
交叉熵损失函数
L = − [ y l o g ( p ) + ( 1 − y ) l o g ( 1 − p ) ) ] L=-[ylog(p)+(1-y)log(1-p))] L=−[ylog(p)+(1−y)log(1−p))]Hinge损失函数
L = m a x ( 0 , 1 − y f ( x ) ) L=max(0,1-yf(x)) L=max(0,1−yf(x))
训练循环
在每个epoch中:
模型设置为训练模式:model.train()。
按批次获取数据进行训练。
计算预测值pred。
计算损失loss。
处理NaN损失值,若出现则中断训练。
计算平均绝对误差mae并记录。
梯度清零:optimizer.zero_grad()。
反向传播:loss.backward()。
参数更新:optimizer.step()。
每个epoch结束后,在验证集上评估模型:
模型设置为评估模式:model.eval()。
计算验证集上的平均绝对误差validation_mae。
保存模型:torch.save(model, ‘odel.pth’)。
记录每个epoch的训练平均绝对误差和验证平均绝对误差。
# 开始训练
mae_list = []
for epoch in range(config['epoch']):
losses = []
model.train()
for i in range(0, train_num, config['batch_size']):
end = i + config['batch_size']
if i + config['batch_size'] > train_num-1:
end = train_num-1
mini_batch = train_features[i: end]
mini_batch_label = train_labels[i: end]
pred = model(mini_batch)
pred = pred.squeeze()
loss = criterion(pred, mini_batch_label)
if torch.isnan(loss):
break
mae = torch.abs(mini_batch_label-pred).sum()/(end-i)
losses.append(mae.item())
optimizer.zero_grad()
loss.backward()
optimizer.step()
model.eval()
pred = model(validation_features)
validation_mae = torch.abs(validation_labels-pred.squeeze()).sum().item()/validation_num
mae_list.append((sum(losses)/len(losses), validation_mae))
print(f"epoch:{epoch + 1} MAE: {sum(losses)/len(losses)}, Validation_MAE: {validation_mae}")
torch.save(model, 'model.pth')
-
训练集(Training Set)
训练集是用于模型学习和参数调整的数据集。模型通过在训练集上进行学习,不断调整内部的参数,以拟合数据中的模式和规律。
例如,假设有一个图像分类任务,有 1000 张带有标签的图像,我们可以选择其中 700 张作为训练集。模型在这 700 张图像及其对应的标签上进行训练,学习如何根据图像的特征来预测其类别。 -
训练集(Training Set)
训练集是用于模型学习和参数调整的数据集。模型通过在训练集上进行学习,不断调整内部的参数,以拟合数据中的模式和规律。
例如,假设有一个图像分类任务,有 1000 张带有标签的图像,我们可以选择其中 700 张作为训练集。模型在这 700 张图像及其对应的标签上进行训练,学习如何根据图像的特征来预测其类别。 -
测试集(Test Set)
测试集用于评估模型最终的泛化能力,即模型在新的、未见过的数据上的表现。测试集在整个模型训练和调优过程中是完全独立的,只有在模型训练和基于验证集调优完成后,才将模型应用于测试集来评估其性能。
在上述例子中,剩下的 200 张图像可以作为测试集。当模型基于训练集训练好,并基于验证集完成超参数调整后,在这 200 张图像上进行测试,得到模型的最终性能指标,以评估模型的实际应用效果和泛化能力。
预测阶段
模型加载
使用torch.load('model.pth', map_location=config['device'])
加载训练好的模型。
数据准备
读取测试数据’./one_hot_testB.csv’。
进行数据预处理,去除特定列。
将数据转换为张量格式。
进行预测
将测试数据输入模型得到预测结果pred。
将预测结果转换为DataFrame格式。
与原始数据合并并保存为output.csv。
import pandas as pd
import torch
from torch import nn
from settings import config
class Network(nn.Module):
def __init__(self, in_dim, hidden_1, hidden_2, hidden_3, hidden_4):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(in_dim, hidden_1),
nn.BatchNorm1d(hidden_1),
nn.ReLU(),
nn.Linear(hidden_1, hidden_2),
nn.BatchNorm1d(hidden_2),
nn.ReLU(),
nn.Linear(hidden_2, hidden_3),
nn.BatchNorm1d(hidden_3),
nn.ReLU(),
nn.Linear(hidden_3, hidden_4),
nn.BatchNorm1d(hidden_4),
nn.ReLU(),
nn.Linear(hidden_4, 1)
)
def forward(self, x):
y = self.layers(x)
return y
model = torch.load('model.pth', map_location=config['device'])
data = pd.read_csv('./one_hot_testB.csv')
data.shape
data.columns
data = data.drop(columns=['Unnamed: 0', 'price'])
test = data.drop(columns='SaleID')
test.shape
test = torch.tensor(test.values, dtype=torch.float32)
pred = model(test)
price = pd.DataFrame(pred.detach().cpu().numpy(), columns=['price'])
res = pd.concat([data.SaleID, price], axis=1)
res.to_csv('output.csv')