写在前面
跟着李宏毅老师从头开始学习深度学习,因此这里用CSDN记录深度学习的课后例题,需要注意的是以下代码来源于网上,应该是李老师的代码,版权不属于我,写这篇博客主要为了巩固自己关于机器(深度)学习的知识。
问题描述
hw1是Kaggle原题(https://www.kaggle.com/competitions/ml2021spring-hw1/overview),简单来说是预测Covid19的阳性人数。提供的文件有covid.train.csv,covid.test.csv 和sampleSubmission.csv。显然任务是根据训练集的数据训练一个模型,并实现测试集的预测。
代码部分
导入库
import pandas as pd
from torch import nn
import numpy as np
from torch.utils.data import Dataset, DataLoader, random_split
from torch.utils.tensorboard import SummaryWriter
import math
from tqdm import tqdm #tqdm库用于生成训练时的进度条展示
import os
import csv
这里的库只有tqdm库不太了解,注释说是用于进度条展示,所以应该是类似于用户友好的图形处理界面。
建立数据集函数
class COVID19Dataset(Dataset):
def __init__(self,x,y=None):
if y is None:
self.y=y
else:
self.y=torch.FloatTensor(y)
self.x=torch.FloatTensor(x)
def __getitem__(self,idx):
if self.y is None:
return self.x[idx]
else:
return self.x[idx],self.y[idx]
def __len__(self):
return len(self.x)
上述函数建立了covid19dataset的类,继承于torch库里的dataset类并覆写了其中的内嵌函数__getitem,添加了函数__len__。(在此之前我一般做法都是直接读取csv文件获取数据集,这里学习到了)
特征提取
def select_feature(train_data,valid_data,test_data,select_all=True):
y_train,y_valid=train_data[:,-1],valid_data[:,-1]
raw_x_train,raw_x_valid,raw_x_test=train_data[:,:-1],valid_data[:,:-1],test_data
if select_all:
feature_idx=list(range(raw_x_train.shape[1]))
else:
feature_idx=[0,1,2,3,4]
#print(raw_x_train)
return raw_x_train[:,feature_idx],raw_x_valid[:,feature_idx],raw_x_test[:,feature_idx],y_train,y_valid
感觉这里写的简单了很多,对于特征提取应该可以更精细一点。虽然这个代码块可以实现特征选择,但是选择哪些特征并没有给出,应该对这些特征做一个比较细致的分析例如相关系数分析。
设置随机种子
def same_seed(seed):
torch.backends.cudnn.deterministic=True
torch.backends.cudnn.benchmark=False
np.random.seed(seed)
torch.manual_seed(seed)
if torch.cuda.is_avaliable():
torch.cuda.manual_seed_all(seed)
随机划分训练集和验证集
def train_valid_split(dataset,ratio,seed):
valid_set_size=int(ratio*len(dataset))
train_set_size=len(dataset)-valid_set_size
train_set,valid_set=random_split(dataset,[train_set_size,valid_set_size],generator=torch.Generator().manual_seed(seed))
return np.array(train_set.dataset.iloc[train_set.indices,:]), np.array(valid_set.dataset.iloc[valid_set.indices,:])
这里我写的与原始代码不同,主要是最后的返回值,按常理来看这里应该是返回训练集和验证集的矩阵形式,但按照原代码的结果返回的却是两个torch.utils.data.dataset.Subset对象。
定义模型
class My_Model(nn.Module):
def __init__(self,input_dim):
super(My_Model,self).__init__()
self.layers=nn.Sequential(
nn.Linear(input_dim,16),
nn.ReLU(),
nn.Linear(16,8),
nn.ReLU(),
nn.Linear(8,1)
)
def forward(self,x):
x=self.layers(x)
x=x.squeeze(1)
return x
显然这部分代码是定义了一个神经网络,有2个隐藏层,神经元个数分别为16和8,ReLU为激活函数。
定义训练函数
def trainer(train_loader,valid_loader,model,config,device):
criterion=nn.MSELoss(reduction='mean')
optimizer=torch.optim.SGD(model.parameters(),lr=config['learning_rate'],momentum=0.9)
writer=SummaryWriter()
if not os.path.isdir('./models'):
os.mkdir('./models')
n_epochs,best_loss,step,early_stop_count=config['n_epochs'],math.inf,0,0
for epoch in range(n_epochs):
model.train()
loss_record=[]
train_pbar=tqdm(train_loader,position=0,leave=True)
for x,y in train_pbar:
optimizer.zero_grad()
x,y=x.to(device),y.to(device)
pred=model(x)
loss=criterion(pred,y)
loss.backward()
optimizer.step()
step+=1
loss_record.append(loss.detach().item())
train_pbar.set_description(f'Epoch [{epoch+1}/{n_epochs}]')
train_pbar.set_postfix({'loss':loss.detach().item()})
mean_train_loss=sum(loss_record)/len(loss_record)
writer.add_scalar('Loss/train', mean_train_loss, step) #tensoboard画出loss曲线
model.eval()
loss_record=[]
for x,y in valid_loader:
x,y=x.to(device),y.to(device)
with torch.no_grad():
pred=model(x)
loss=criterion(pred,y)
loss_record.append(loss.item())
mean_valid_loss=sum(loss_record)/len(loss_record)
print(f'Epoch [{epoch+1}/{n_epochs}]: Train loss: {mean_train_loss:.4f}, Valid loss: {mean_valid_loss:.4f}')
writer.add_scalar('Loss/valid', mean_valid_loss, step)
if mean_valid_loss<best_loss:
best_loss=mean_valid_loss
torch.save(model.state_dict(),config['save_path'])
print(f'Saving model with loss {best_loss:.3f}...')
early_stop_count=0
else:
early_stop_count+=1
if early_stop_count>=config['early_stop_count']:
print('\nModel is mot improving, so we halt the training session.')
return
该部分十分重要,可以说是核心代码了,所以有必要进行详细的说明。这部分代码即是为了完成模型的训练。函数的参数有训练集、验证集、自定义配置和设备(cpu or gpu)。首先定义了损失函数和优化器,算是常见的操作了,SummaryWriter是我第一次见到,与tensorboard相关,后续会仔细再看看。之后定义了存储位置和epoch,best_loss以及early_stop_count。early_stop_count在我用XGBoost时经常遇到,主要是为了停止损失基本不下降模型的训练。下面正式进入epoch,在每一个epoch中:首先model.train(),表明模型开启训练模式,打开batch normalization和drop out,与之对应的是model.eval(),即将对应的设置关闭,在验证集上使用。train_pbar用到了tqdm类,可视化了遍历训练集的进度,position设置了打印进度条的位置,leave是表示执行完成后是否保留进度条。随后开始遍历训练集,将输入与输出分别转移到相应的device上,之后定义损失函数,同时将得到的损失记录下来,遍历完之后,计算平均损失。之后进入模型验证,这里就要关闭模型的训练模式,因为模型验证是为了观察模型在训练之后在验证集的表现,所以这里torch.no_grad()表明无需对模型计算梯度,只需要计算损失即可。最后比较验证集上的平均损失与最低损失,如果小于则更新最低损失为当前平均损失并将early_stop_count设为0,否则early_stop_count+=1。
开始:
框架已经搭好,后面就是正式开始训练模型,这部分内容比较简单易懂,就不多赘述。
读取并划分数据
device = 'cuda' if torch.cuda.is_available() else 'cpu'
config={'seed':0,'select_all':True,'valid_ratio':0.2,'n_epochs':3000,'batch_size':256,'learning_rate':1e-5,'early_stop_count':400,
'save_path': './models/model.ckpt' }
train_data=pd.read_csv(r'D:\Work Studio\Python 3.8.6\My secret base\hw\hw1\ml2021spring-hw1\covid.train.csv').iloc[:,1:]
test_data=np.array(pd.read_csv(r'D:\Work Studio\Python 3.8.6\My secret base\hw\hw1\ml2021spring-hw1\covid.test.csv').iloc[:,1:])
train_data, valid_data = train_valid_split(train_data, config['valid_ratio'], config['seed'])
x_train, x_valid, x_test, y_train, y_valid = select_feature(train_data, valid_data, test_data, config['select_all'])
train_dataset, valid_dataset, test_dataset = COVID19Dataset(x_train, y_train), \
COVID19Dataset(x_valid, y_valid), \
COVID19Dataset(x_test)
train_loader = DataLoader(train_dataset, batch_size=config['batch_size'], shuffle=True, pin_memory=True)
#pin_memory默认false,打开后更快
valid_loader = DataLoader(valid_dataset, batch_size=config['batch_size'], shuffle=True, pin_memory=True)
test_loader = DataLoader(test_dataset, batch_size=config['batch_size'], shuffle=False, pin_memory=True)
训练模型
model = My_Model(input_dim=x_train.shape[1]).to(device) # put your model and data on the same computation device.
trainer(train_loader, valid_loader, model, config, device)
保存模型并预测数据
def save_pred(preds, file):
''' Save predictions to specified file '''
with open(file, 'w') as fp:
writer = csv.writer(fp)
writer.writerow(['id', 'tested_positive'])
for i, p in enumerate(preds):
writer.writerow([i, p])
model = My_Model(input_dim=x_train.shape[1]).to(device)
model.load_state_dict(torch.load(config['save_path']))
preds = predict(test_loader, model, device)
save_pred(preds, 'pred.csv')
总结
以上即是hw1例题的基本内容,经过这一次的学习和探讨,我对机器学习框架的建立有了更深刻的了解。后续如果有新的感悟和思考,会继续更新补充,也欢迎大家批评指正。