下载数据集# 导入所需的库 import hashlib # 用于计算文件的SHA-1哈希值验证文件完整性 import os # 用于操作系统路径、目录等操作 import tarfile # 用于处理.tar或.gz压缩文件 import zipfile # 用于处理.zip压缩文件 import requests # 用于发送HTTP请求下载文件 # save 是d2l库的标记表示该函数会被保存或复用 DATA_HUB dict() # 全局字典存储数据集名称和对应的(URL, SHA-1哈希值) DATA_URL http://d2l-data.s3-accelerate.amazonaws.com/ # 数据集基础URL def download(name, cache_diros.path.join(.., data)): #save 下载一个DATA_HUB中的文件返回本地文件名 # 断言确保要下载的文件名在DATA_HUB中已定义 assert name in DATA_HUB, f{name} 不存在于 {DATA_HUB} # 从DATA_HUB获取URL和期望的SHA-1哈希值 url, sha1_hash DATA_HUB[name] # 创建缓存目录如果不存在 os.makedirs(cache_dir, exist_okTrue) # 生成本地文件路径缓存目录 URL中的文件名 fname os.path.join(cache_dir, url.split(/)[-1]) # 如果文件已存在检查其哈希值是否匹配 if os.path.exists(fname): sha1 hashlib.sha1() # 以二进制模式打开文件分块读取每次1MB with open(fname, rb) as f: while True: data f.read(1048576) # 1MB 1024 * 1024 if not data: break sha1.update(data) # 如果哈希值匹配直接返回缓存的文件路径 if sha1.hexdigest() sha1_hash: return fname # 命中缓存 # 如果文件不存在或哈希不匹配则重新下载 print(f正在从{url}下载{fname}...) r requests.get(url, streamTrue, verifyTrue) # 发送GET请求下载文件 with open(fname, wb) as f: f.write(r.content) # 将下载的内容写入文件 return fname def download_extract(name, folderNone): #save 下载并解压zip/tar文件 fname download(name) # 调用download函数获取文件路径 base_dir os.path.dirname(fname) # 获取文件所在目录 data_dir, ext os.path.splitext(fname) # 分离文件名和扩展名 # 根据扩展名选择解压方式 if ext .zip: fp zipfile.ZipFile(fname, r) # 打开ZIP文件 elif ext in (.tar, .gz): fp tarfile.open(fname, r) # 打开TAR/GZ文件 else: assert False, 只有zip/tar文件可以被解压缩 # 解压到基础目录 fp.extractall(base_dir) # 如果指定了子文件夹返回该子文件夹路径否则返回解压后的目录 return os.path.join(base_dir, folder) if folder else data_dir def download_all(): #save 下载DATA_HUB中的所有文件 for name in DATA_HUB: download(name) # 如果没有安装pandas请取消下一行的注释 # !pip install pandas # 设置matplotlib为内联模式图像直接显示在Notebook中 %matplotlib inline # 导入常用库 import numpy as np # 数值计算库 import pandas as pd # 数据处理和分析库 import torch # PyTorch深度学习框架 from torch import nn # PyTorch神经网络模块 from d2l import torch as d2l # 导入d2l的PyTorch版本 # 定义Kaggle房价预测训练集的信息 DATA_HUB[kaggle_house_train] ( #save DATA_URL kaggle_house_pred_train.csv, # 文件URL 585e9cc93e70b39160e7921475f9bcd7d31219ce) # 文件的SHA-1哈希值 # 定义Kaggle房价预测测试集的信息 DATA_HUB[kaggle_house_test] ( #save DATA_URL kaggle_house_pred_test.csv, fa19780a7b011d9b009e8bff8e99922a8ee2eb90) # 下载并读取训练数据到pandas DataFrame train_data pd.read_csv(download(kaggle_house_train)) # 下载并读取测试数据到pandas DataFrame test_data pd.read_csv(download(kaggle_house_test))主要功能总结download()智能下载文件支持缓存和完整性校验SHA-1。download_extract()下载并自动解压压缩文件。download_all()批量下载所有数据集。结果:数据预处理(key 不熟)all_features pd.concat( (train_data.iloc[:, 1:-1], test_data.iloc[:, 1:]) )把训练集的特征列和测试集的特征列拼在一起形成一个新的 DataFrame(因为是pandas)。(没想到)合并训练和测试集的特征为了统一预处理结果:填充缺失值和归一化特征# 特征预处理 # 1. 识别数值特征 numeric_features all_features.dtypes[all_features.dtypes ! object].index # 2. 数值特征标准化Z-score标准化 # 注意这里使用了整个数据集训练测试计算均值和标准差 # 实际生产中应该只使用训练集统计量 all_features[numeric_features] all_features[numeric_features].apply( lambda x: (x - x.mean()) / (x.std()) ) # 3. 标准化后缺失值用0填充因为标准化后均值为0 all_features[numeric_features] all_features[numeric_features].fillna(0) # 4. 类别特征进行独热编码dummy_naTrue会将缺失值也视为一个类别 all_features pd.get_dummies(all_features, dummy_naTrue) # 获取特征数量 print(f预处理后的特征维度: {all_features.shape}) # 分离回训练和测试集 n_train train_data.shape[0] # 训练集样本数 train_features torch.tensor(all_features[:n_train].values, dtypetorch.float32) test_features torch.tensor(all_features[n_train:].values, dtypetorch.float32) # 获取训练标签 train_labels torch.tensor( train_data.SalePrice.values.reshape(-1, 1), dtypetorch.float32 ) # 打印数据形状 print(f训练特征形状: {train_features.shape}) print(f训练标签形状: {train_labels.shape}) print(f测试特征形状: {test_features.shape})1细节 处理连续数据 取出数字列指的是和object比较得到结果是布尔型序列(因为有很多个特征列) 下图所示取出索引(类似于字典)2细节 数字列归一化先标准化再填充缺失值处理离散特征独热编码:机器学习中处理离散特征分类特征的一种常用方法叫做独热编码One-Hot Encoding。简单来说它的目的是把文字类别变成计算机能读懂的数字向量。1. 为什么要这么做原始的表格里像MSZoning zoning 分类这样的列里面的值是文字比如 RL住宅低密, RM住宅中等密, 或者是空的NaN。计算机不认识字模型只能计算数字。不能随便编数字如果你把 RL 编成 1RM 编成 2模型可能会误以为 RM 比 RL 大或者它们有高低之分但这在现实中是不成立的它们是平等的类别。2. 它是怎么工作的以 RL 为例独热编码的做法是既然它们是平等的那就给每个类别发一个专属的“开关”0或1。不再有MSZoning这个列了检查结果是否正确:# 检查每一列的数据类型 print(all_features.dtypes) # 找出所有非数值类型的列object 或 string non_numeric_cols all_features.select_dtypes(include[object, string]).columns print(以下列是非数值类型导致了报错) print(non_numeric_cols)弄出训练集和测试集(分割因为最开始时候concat了)通过values属性我们可以 [从pandas格式中提取NumPy格式并将其转换为张量表示]用于训练。# 获取训练集的样本数量行数 n_train train_data.shape[0] # train_data.shape[0]返回训练数据的行数即样本数量 # 从合并的特征数据all_features中取出前n_train行作为训练特征 # all_features[:n_train] 通过切片获取前n_train行训练集部分 # .values 将DataFrame转换为NumPy数组 # torch.tensor(...) 将NumPy数组转换为PyTorch张量并指定数据类型为float32 train_features torch.tensor(all_features[:n_train].values, dtypetorch.float32) # 从合并的特征数据all_features中取出从n_train行开始到最后一行的数据作为测试特征 # all_features[n_train:] 获取从第n_train行开始到最后一行的数据测试集部分 # 同样转换为PyTorch张量 test_features torch.tensor(all_features[n_train:].values, dtypetorch.float32) # 获取训练集的标签房价 # train_data.SalePrice 是训练集中的房价列 # .values 转换为NumPy数组 # .reshape(-1, 1) 将一维数组转换为二维数组形状为[n, 1]n行1列 # 这里-1表示自动计算该维度的大小即自动根据原数组元素数量和指定的其他维度1来推断 # 例如原数组有1460个元素reshape(-1, 1)就会转换成1460行1列的二维数组 # 最后将其转换为PyTorch张量数据类型为float32 train_labels torch.tensor( train_data.SalePrice.values.reshape(-1, 1), dtypetorch.float32)训练:以简单的线性模型为baseline作为对比的# 定义均方误差损失函数Mean Squared Error Loss loss nn.MSELoss() # 获取输入特征的维度即特征的数量 # train_features.shape[1] 返回训练特征张量的列数即每个样本的特征数 in_features train_features.shape[1] # 定义一个函数用于创建神经网络模型 def get_net(): # 使用nn.Sequential定义一个简单的线性回归模型 # 该模型只包含一个全连接层Linear输入维度为in_features输出维度为1 net nn.Sequential(nn.Linear(in_features,1)) return net # 定义一个函数计算对数均方根误差Log Root Mean Squared Error # 在房价预测任务中通常使用对数均方根误差作为评估指标因为房价的分布通常是偏态的取对数可以使误差更接近正态分布 def log_rmse(net, features, labels): # 使用模型net对特征features进行预测 # 通过torch.clamp将预测值限制在1到正无穷之间避免取对数时出现负值或零因为log(0)是负无穷log(负数)是NaN clipped_preds torch.clamp(net(features), 1, float(inf)) # 计算对数均方根误差 # 1. 对预测值和真实值分别取对数 # 2. 计算两者之间的均方误差使用之前定义的MSE损失函数 # 3. 对均方误差开方得到均方根误差 rmse torch.sqrt(loss(torch.log(clipped_preds), torch.log(labels))) # 返回一个标量Python浮点数通过.item()将张量转换为浮点数 return rmse.item() # 定义训练函数 def train(net, train_features, train_labels, test_features, test_labels, num_epochs, learning_rate, weight_decay, batch_size): # 初始化两个列表分别用于记录训练集和测试集在每个epoch后的对数均方根误差 train_ls, test_ls [], [] # 使用d2l库中的load_array函数将训练特征和训练标签转换为一个可以迭代的数据加载器 # 每次迭代返回一个批次的数据包含batch_size个样本 train_iter d2l.load_array((train_features, train_labels), batch_size) # 定义优化器这里使用Adam优化算法 # net.parameters()返回网络中所有需要训练的参数 # lr学习率 # weight_decay权重衰减L2正则化系数用于防止过拟合 optimizer torch.optim.Adam(net.parameters(), lr learning_rate, weight_decay weight_decay) # 开始训练迭代num_epochs个周期 for epoch in range(num_epochs): # 每个epoch中遍历数据加载器中的每一个批次 for X, y in train_iter: # 在计算梯度之前先将优化器中之前存储的梯度清零 optimizer.zero_grad() # 前向传播计算模型在当前批次上的预测值并计算损失 l loss(net(X), y) # 反向传播计算损失函数关于模型参数的梯度 l.backward() # 更新模型参数根据梯度使用优化器更新参数 optimizer.step() # 一个epoch结束后计算整个训练集上的对数均方根误差并记录 train_ls.append(log_rmse(net, train_features, train_labels)) # 如果提供了测试标签即test_labels不为None则计算测试集上的对数均方根误差并记录 if test_labels is not None: test_ls.append(log_rmse(net, test_features, test_labels)) # 返回训练和测试过程中每个epoch的对数均方根误差列表 return train_ls, test_lsk折交叉验证:重点获取k折数据进行k折验证:# 定义一个函数用于获取K折交叉验证中第i折的训练数据和验证数据 def get_k_fold_data(k, i, X, y): # 断言k必须大于1因为至少需要2折才能进行交叉验证 assert k 1 # 计算每一折的大小样本数这里使用整数除法所以可能不能整除余数部分被忽略 fold_size X.shape[0] // k # 初始化训练数据和标签为None X_train, y_train None, None # 遍历k折 for j in range(k): # 计算当前折的索引范围slice对象用于切片 idx slice(j * fold_size, (j 1) * fold_size) # 根据索引切片获取当前折的特征和标签 X_part, y_part X[idx, :], y[idx] # 如果当前折是第i折则作为验证集 if j i: X_valid, y_valid X_part, y_part # 否则将当前折合并到训练集中 # 如果训练集为空则直接赋值 elif X_train is None: X_train, y_train X_part, y_part # 如果训练集不为空则通过torch.cat在0维度行方向进行拼接 else: X_train torch.cat([X_train, X_part], 0) y_train torch.cat([y_train, y_part], 0) # 返回训练集特征、训练集标签、验证集特征、验证集标签 return X_train, y_train, X_valid, y_valid # 定义一个函数执行K折交叉验证 def k_fold(k, X_train, y_train, num_epochs, learning_rate, weight_decay, batch_size): # 初始化训练和验证误差总和 train_l_sum, valid_l_sum 0, 0 # 遍历每一折 for i in range(k): # 获取当前折的训练和验证数据 data get_k_fold_data(k, i, X_train, y_train) # 创建一个新的网络模型 net get_net() # 训练模型并返回训练和验证的误差列表 # 注意这里使用了*data将返回的四个值作为参数传递给train函数 train_ls, valid_ls train(net, *data, num_epochs, learning_rate, weight_decay, batch_size) # 将最后一轮的训练和验证误差累加到总和中 train_l_sum train_ls[-1] valid_l_sum valid_ls[-1] # 如果是第一折绘制训练和验证误差曲线 if i 0: d2l.plot(list(range(1, num_epochs 1)), [train_ls, valid_ls], xlabelepoch, ylabelrmse, xlim[1, num_epochs], legend[train, valid], yscalelog) # 打印当前折的训练和验证误差最后一轮 print(f折{i 1}训练log rmse{float(train_ls[-1]):f}, f验证log rmse{float(valid_ls[-1]):f}) # 返回K折交叉验证的平均训练误差和平均验证误差 return train_l_sum / k, valid_l_sum / k核心思想把训练数据分成K份轮流用其中K-1份训练1份验证重复K次取平均结果。类比你有5个老师教同一个学生每个老师用4/5的内容教用剩下的1/5考试最后看5个老师的平均分数。例子:eg:# 遍历5折 for j in range(5): idx slice(j*200, (j1)*200) # 当前折的索引范围 if j 0: # 第0折 → 验证集 X_valid, y_valid X[0:200], y[0:200] elif j 1: # 第1折 → 训练集第一部分 X_train, y_train X[200:400], y[200:400] else: # 第2,3,4折 → 拼接到训练集 X_train torch.cat([X_train, X[400:600]], 0) # 第2折 # ...继续拼接划分数据代码解读:我习惯第一种拼接写法# 对比两种写法 # 写法1先创建空列表再拼接 X_train_parts [] for j in range(k): if j ! i: # 如果不是验证折 X_train_parts.append(X_part) # 最后用torch.cat拼接所有部分 # 写法2当前代码的写法 X_train None for j in range(k): if j ! i: # 如果不是验证折 if X_train is None: # 第一次遇到训练折 X_train X_part else: # 后续训练折 X_train torch.cat([X_train, X_part], 0)0 1是dim参数的值验证代码解读:----------------------------------------train_l_sum train_ls[-1] # 只取最后一个epoch来加入# 但有时最佳模型在中间epoch# 应该取验证误差最小的那个epoch 但为了初学者懂train_ls, valid_ls train(net, *data, num_epochs, learning_rate,weight_decay, batch_size)train_l_sum train_ls[-1]valid_l_sum valid_ls[-1]和return train_l_sum / k, valid_l_sum / k 关系:每个epoch都进行k折验证train_ls, valid_ls是训练了num_epochs的结果 最后 train_l_sum每次都只用最后一个epoch结果所以最后 train_l_sum就只包含了k份数据然后➗️得到平均值模型选择(调参数):先用部分数据(比如下图的某折来验证)来调参k, num_epochs, lr, weight_decay, batch_size 5, 100, 5, 0, 64 train_l, valid_l k_fold(k, train_features, train_labels, num_epochs, lr, weight_decay, batch_size) print(f{k}-折验证: 平均训练log rmse: {float(train_l):f}, f平均验证log rmse: {float(valid_l):f})上图是选择了一组未调优的超参数并将其留给读者来改进模型。 找到一组调优的超参数可能需要时间这取决于一个人优化了多少变量。 有了足够大的数据集和合理设置的超参数折交叉验证往往对多次测试具有相当的稳定性。 然而如果我们尝试了不合理的超参数我们可能会发现验证效果不再代表真正的误差。有时一组超参数的训练误差可能非常低但折交叉验证的误差要高得多 这表明模型过拟合了。 在整个训练过程中我们希望监控训练误差和验证误差这两个数字。较少的过拟合可能表明现有数据可以支撑一个更强大的模型 较大的过拟合可能意味着我们可以通过正则化技术来获益。用全部数据(已经得到合适的超参数了)来训练:def train_and_pred(train_features, test_features, train_labels, test_data, num_epochs, lr, weight_decay, batch_size): 完整的训练和预测函数 参数: train_features: 训练集特征 (Tensor) test_features: 测试集特征 (Tensor) train_labels: 训练集标签 (Tensor) test_data: 原始的测试数据集 (Pandas DataFrame包含Id列) num_epochs: 训练轮数 lr: 学习率 weight_decay: 权重衰减 (L2正则化系数) batch_size: 批次大小 # 1. 创建新的神经网络模型实例 net get_net() # 2. 使用全部训练数据训练模型 # train函数的第4、5个参数设为None表示不传入验证集/测试集 # 所以只返回训练误差列表train_ls第二个返回值用_接收表示忽略 train_ls, _ train(net, train_features, train_labels, None, None, num_epochs, lr, weight_decay, batch_size) # 3. 绘制训练误差随训练轮次变化的曲线 d2l.plot(np.arange(1, num_epochs 1), # x轴1到num_epochs [train_ls], # y轴训练误差列表 xlabelepoch, # x轴标签 ylabellog rmse, # y轴标签 xlim[1, num_epochs], # x轴范围 yscalelog) # y轴使用对数刻度误差值通常较小 # 4. 打印最终训练误差 print(f训练log rmse{float(train_ls[-1]):f}) # 5. 使用训练好的模型在测试集上进行预测 # net(test_features)模型前向传播得到预测结果 # .detach()从计算图中分离不记录梯度 # .numpy()转换为NumPy数组 preds net(test_features).detach().numpy() # 6. 将预测结果重新格式化以便导出到Kaggle # preds.reshape(1, -1)将预测结果重塑为1行多列的二维数组 # [0]取第一行因为是1行所以就是取所有预测值 # pd.Series转换为Pandas Series方便添加到DataFrame test_data[SalePrice] pd.Series(preds.reshape(1, -1)[0]) # 7. 创建提交文件 # 提取测试集的Id列和预测的SalePrice列 submission pd.concat([test_data[Id], test_data[SalePrice]], axis1) # 8. 将结果保存为CSV文件用于Kaggle提交 # indexFalse不保存行索引 submission.to_csv(submission.csv, indexFalse) train_and_pred(train_features, test_features, train_labels, test_data, num_epochs, lr, weight_decay, batch_size)输入数据↓创建新模型↓用全部训练数据训练↓绘制训练误差曲线↓打印最终训练误差↓在测试集上预测↓格式化预测结果↓保存为提交文件细节:竞赛总结:10行代码战胜90%数据科学家10行代码战胜90%数据科学家_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1rh411m7Hb/?fromsearchseid14820697425740410884spm_id_from333.788.comment.all.clickvd_source5252d3cdd5246bf9326ccfc5acb9064410行代码战胜90%数据科学家_哔哩哔哩_bilibili第三h2ohttps://www.kaggle.com/wuwawa/automl-using-h2o第四 随机森林https://www.kaggle.com/jackzh/the-4th-place-approach-random-foresthttps://www.bilibili.com/video/BV15Q4y1o7vc/?spm_id_from333.999.0.0vd_source5252d3cdd5246bf9326ccfc5acb90644MLP效果不好eg:1有些特征文本很多比如房子介绍可以直接舍弃文本信息/transform等处理 但不能onehot编码因为不会有两个文本一样的数据这样维度就很高了数据里面是10w条这样2 数据较大房子价格美金 差距很大可以先取log再归一化处理评分较高的automl:一样是集成模型【机器学习】解放双手AutoML入门从原理到Auto-sklearn实战告别繁琐调参_automl 开源框架-CSDN博客一、 文本特征地址、介绍怎么处理在传统的机器学习中直接把“我爱我家温馨两居”这种文本扔进树模型如XGBoost/LightGBM是不行的。不能直接塞进去必须先进行“结构化”或“向量化”提取。1. 地址类文本靠“正则表达式”提取标签地址往往很长但包含的关键信息学区、地铁、商圈对房价影响巨大。不要试图把整个地址字符串当作一个特征而是要用正则把关键信息抠出来变成数值或类别提取硬性配套用正则匹配是否包含“地铁”、“学区”、“三甲医院”、“万达广场”等关键词转换成has_subway(0或1)、school_district(学区等级) 等布尔或数值特征。结构化解析比如把“广州市天河区珠江新城保利心语”拆解成城市广州、行政区天河、商圈珠江新城、小区保利心语。高阶玩法聚类如果小区名称有几千个可以计算每个小区的历史均价然后用 K-Means 把这几千个小区聚类成 5-10 个档次如豪宅盘、刚需盘用聚类的标签代替原始小区名既降维又保留了地理价值。2. 介绍类文本房源描述靠“Embedding”获取语义对于“南北通透采光极佳拎包入住”这种描述心情和状态的文本传统方法TF-IDF简单粗暴把词变成数字。但如果介绍太长维度会爆炸容易过拟合。深度学习方法强烈推荐使用 NLP 领域的预训练模型如BERT、Word2Vec。把一段介绍文本输入 BERT它会输出一个固定长度比如 768 维的向量。这个向量就代表了这段描述的“整体语义氛围”。将这个向量作为额外的特征拼接到你的表格数据中能让模型理解“豪华装修”和“毛坯简装”在语义上的巨大差异。二、 公榜和私榜是什么意思这是数据科学竞赛如 Kaggle最核心的机制也是你提到的第三点“数据切分”的真正用意。1. 基本定义公榜 (Public Leaderboard)比赛期间实时更新的排行榜。你的模型在一部分隐藏的测试数据上跑出的分数会显示在这里所有人都能看到你的实时排名。私榜 (Private Leaderboard)比赛结束后才揭晓的排行榜。你的模型在另一部分更深度的隐藏数据上跑分这决定了你的最终奖金和获奖资格。2. 为什么要分公榜和私榜防作弊与防过拟合这就回到了你说的“时间切分”前6个月训练公榜是后3个月私榜是再后3个月。公榜的作用给你的模型一个“试错反馈”。让你知道模型能不能推广到稍微未来的数据。私榜的作用终极审判。用来检验你的模型是不是真的学到了普适规律还是在公榜那3个月的数据上“死记硬背”过拟合了。3. 实战中的“致命陷阱”Shake-Up排名震荡如果你为了让公榜排名更高疯狂地针对公榜那3个月的数据调参、加特征你的模型就会完美适应公榜但到了私榜再后3个月市场行情可能变了尤其这个房价预测波动太大了就会一塌糊涂。