写在开头:
此篇博客主要是记录李宏毅老师2021年春季的深度学习作业1的笔记过程,以时间的角度进行记录。
作业网址:https://www.kaggle.com/competitions/ml2021spring-hw1
用rf进行初步测试数据集:结果不太行,大约在1350/2032
提交主要的代码:
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error
clf = RandomForestRegressor()
clf.fit(train,target)
test_pred = clf.predict(test)
test_pred
#Kaggle需要提交最终的csv文件,所以输出一个csv文件:
sample['tested_positive']=test_pred
sample.to_csv('submission.csv', index=False)
下面的代码是李宏毅老师助教的代码,目前只是跑通了并且弄懂了一部分,由于用的是俩层的全连接层,而且梯度下降用的也是SGD,最后的效果还不如没有调参的rf的效果。
1、Github:代码链接
无脑不用看,直接跑代码:
提交的可运行代码:https://www.kaggle.com/code/bessielee1/ml2021spring-homework1-gpu
下面进行代码解释:
首先,助教的代码风格是将很多的东西放在函数里面,这样对代码进行修改就会极其的方便,只需要改模型部分就行。
【建议】
建议是先看代码的中间部分,而不是去看函数体,函数什么时候调用,就什么时候开始仔细看函数部分。
# 获取当前设备的cpu或gpu
device = get_device() # get the current available device ('cpu' or 'cuda')
# 创建多层目录
os.makedirs('models', exist_ok=True) # The trained model will be saved to ./models/
target_only = False # TODO: Using 40 states & 2 tested_positive features
# TODO: How to tune these hyper-parameters to improve your model's performance?
config = {
'n_epochs': 3000, # maximum number of epochs
'batch_size': 270, # mini-batch size for dataloader
'optimizer': 'SGD', # optimization algorithm (optimizer in torch.optim) - 梯度下降优化算法
'optim_hparas': { # hyper-parameters for the optimizer (depends on which optimizer you are using)
'lr': 0.001, # learning rate of SGD
'momentum': 0.9 # momentum for SGD 加动量
},
'early_stop': 200, # early stopping epochs (the number epochs since your model's last improvement)早停法
'save_path': './.pth' # your model will be saved here
}
1.1、涉及到
get_device()
函数
def get_device():
''' Get device (if GPU is available, use GPU) '''
return 'cuda' if torch.cuda.is_available() else 'cpu'
1.2、创建一个多层的目录(如果不想创建可以不写)
1.3、设置超参数在config里面
'n_epochs': 3000,
'batch_size': 270,
'optimizer': 'SGD',
'optim_hparas': {
'lr': 0.001,
'momentum': 0.9
},
'early_stop': 200,
'save_path': './.pth'
n_epochs:最大的epoch数
batch_size:
optimizer:SGD是一种梯度下降优化算法
optim_hparas
early_stop:早停法(当epochs的值加到200的时候,就停止了,这个epochs和刚刚的n_epochs不是一个东西)
save_path:保存最后存储文件的地址
tr_set = prep_dataloader(tr_path, 'train', config['batch_size'], target_only=target_only)
dv_set = prep_dataloader(tr_path, 'dev', config['batch_size'], target_only=target_only)
tt_set = prep_dataloader(tt_path, 'test', config['batch_size'], target_only=target_only)
用的都是prep_dataloader()
这个方法,那接下来对这个方法进行详细的解释:
def prep_dataloader(path, mode, batch_size, n_jobs=0, target_only=False):
''' Generates a dataset, then is put into a dataloader. '''
dataset = COVID19Dataset(path, mode=mode, target_only=target_only) # Construct dataset
# 可以通过继承和重写这个抽象类实现自己的数据类,只需要定义__len__和__getitem__这个两个函数
dataloader = DataLoader(
dataset, batch_size,
shuffle=(mode == 'train'), drop_last=False,
num_workers=n_jobs, pin_memory=True) # Construct dataloader
return dataloader
在函数prep_dataloader()
里面,又调用了COVID19Dataset()
这个函数,所以说先看COVID19Dataset()
这个函数
class COVID19Dataset(Dataset):
''' Dataset for loading and preprocessing the COVID19 dataset '''
# 定义构造函数:后续可以用COVID19Dataset()调用,需要输入path,mode,target_only三个参数
def __init__(self,
path,
mode='train',
target_only=False):
self.mode = mode
# 读取path(有可能是train或者test),将数据变成二维列表,此时原来一行的信息被放入一个二维列表中的其中一个里面。
# Read data into numpy arrays
with open(path, 'r') as fp:
data = list(csv.reader(fp))
data = np.array(data[1:])[:, 1:].astype(float)
# 如果target_only为False,则feats为[0,1,2,3,....,92]
if not target_only:
feats = list(range(93))
# 否则:不做处理
else:
# TODO: Using 40 states & 2 tested_positive features (indices = 57 & 75)
pass
# 如果mode是test(测试集)
if mode == 'test':
# Testing data
# data: 893 x 93 (40 states + day 1 (18) + day 2 (18) + day 3 (17))
data = data[:, feats] # 其实没有变化,因为feats本身的值就是test的,就是93
self.data = torch.FloatTensor(data) # 把numpy转换为tensor的类型
else:
# Training data (train/dev sets)
# data: 2700 x 94 (40 states + day 1 (18) + day 2 (18) + day 3 (18))
target = data[:, -1] # target位于最后一列
data = data[:, feats] # 除去最后一列的数据
# Splitting training data into train & dev sets
if mode == 'train':
# 在2700列中除去可以被十整除的部分
indices = [i for i in range(len(data)) if i % 10 != 0]
elif mode == 'dev':
indices = [i for i in range(len(data)) if i % 10 == 0]
# Convert data into PyTorch tensors
self.data = torch.FloatTensor(data[indices]) # 不能被10整除的数据转换为tensor作为测试集
self.target = torch.FloatTensor(target[indices]) # 可以被10整除的数据转换为tensor作为交叉验证集
# 归一化
# Normalize features (you may remove this part to see what will happen)
self.data[:, 40:] = \
(self.data[:, 40:] - self.data[:, 40:].mean(dim=0, keepdim=True)) \
/ self.data[:, 40:].std(dim=0, keepdim=True)
self.dim = self.data.shape[1]
print('Finished reading the {} set of COVID19 Dataset ({} samples found, each dim = {})'
.format(mode, len(self.data), self.dim))
def __getitem__(self, index):
# Returns one sample at a time
if self.mode in ['train', 'dev']:
# For training
return self.data[index], self.target[index]
else:
# For testing (no target)
return self.data[index]
def __len__(self):
# Returns the size of the dataset
return len(self.data)
这是定义的一个COVID19Dataset类,但是我们调用的是她的构造方法,也就是def __init__
这个部分。
对于这个部分,代码中已经进行详细的注释,再此进行简单的阐述即可。
feats = list(range(93))
feats = list(range(93))
这句话的意思是:feats=[0,1,2,3,4,…,92],但是为什么要如此设计呢?是因为test.csv的数据是893×93,故而以93作为feats的值。 data = data[:, feats]
没有什么操作变化(目的是为了和后面的代码形成一致)对于prep_dataloader()
函数的后一部分: DataLoader()
这是一个系统类,可以通过继承和重写这个抽象类实现自己的数据类,只需要定义__len__和__getitem__这个两个函数。
model = NeuralNet(tr_set.dataset.dim).to(device)
对于这个代码,要涉及NeuralNet()
这个函数,同时.to(device)
的意思是:放入GPU进行运算
NeuralNet()
进行详细分析:class NeuralNet(nn.Module):
''' A simple fully-connected deep neural network '''
# 全连接层
def __init__(self, input_dim):
super(NeuralNet, self).__init__()
# Define your neural network here
# TODO: How to modify this model to achieve better performance?
self.net = nn.Sequential(
nn.Linear(input_dim, 64),
nn.ReLU(),
nn.Linear(64, 1)
)
# Mean squared error loss
# 新版本中的MSELoss只有一个参数,意思就是要不要压缩
# 'mean': the sum of the output will be divided by the number of elements in the output.
# 如果不设置reduction参数,默认是'mean'
# https://blog.csdn.net/zfhsfdhdfajhsr/article/details/115637954
self.criterion = nn.MSELoss(reduction='mean')
def forward(self, x):
''' Given input of size (batch_size x input_dim), compute output of the network '''
# squeeze(1) 是压缩,dim=1
return self.net(x).squeeze(1)
def cal_loss(self, pred, target):
''' Calculate loss '''
# TODO: you may implement L2 regularization here
# criterion是损失函数
return self.criterion(pred, target)
这里是模型选取的代码部分
reduction='mean'
,也就是’mean’: 输出的总和将除以输出中的元素数model_loss, model_loss_record = train(tr_set, dv_set, model, config, device)
训练用的train()
函数,下方贴出:
def train(tr_set, dv_set, model, config, device):
''' DNN training '''
# 传入:训练集,交叉验证集,模型,config
n_epochs = config['n_epochs'] # Maximum number of epochs - 3000
# Setup optimizer
# getattr(a,'qq') :在对象a中是否存在qq,有则输出这个数,没有则返回异常
optimizer = getattr(torch.optim, config['optimizer'])(
model.parameters(), **config['optim_hparas'])
min_mse = 1000.
loss_record = {'train': [], 'dev': []} # for recording training loss
early_stop_cnt = 0
epoch = 0
# 训练一个epoches
while epoch < n_epochs:
model.train() # set model to training mode
for x, y in tr_set: # iterate through the dataloader
optimizer.zero_grad() # set gradient to zero
x, y = x.to(device), y.to(device) # move data to device (cpu/cuda)
pred = model(x) # forward pass (compute output)
mse_loss = model.cal_loss(pred, y) # compute loss
mse_loss.backward() # compute gradient (backpropagation)
optimizer.step() # update model with optimizer
loss_record['train'].append(mse_loss.detach().cpu().item())
# After each epoch, test your model on the validation (development) set.
dev_mse = dev(dv_set, model, device)
if dev_mse < min_mse:
# Save model if your model improved
min_mse = dev_mse
print('Saving model (epoch = {:4d}, loss = {:.4f})'
.format(epoch + 1, min_mse))
torch.save(model.state_dict(), config['save_path']) # Save model to specified path
early_stop_cnt = 0
else:
early_stop_cnt += 1
epoch += 1
loss_record['dev'].append(dev_mse)
if early_stop_cnt > config['early_stop']:
# Stop training if your model stops improving for "config['early_stop']" epochs.
break
print('Finished training after {} epochs'.format(epoch))
return min_mse, loss_record
preds = test(tt_set, model, device)
调用的是test()
方法:
def test(tt_set, model, device):
model.eval() # set model to evalutation mode
preds = []
for x in tt_set: # iterate through the dataloader
x = x.to(device) # move data to device (cpu/cuda)
with torch.no_grad(): # disable gradient calculation
pred = model(x) # forward pass (compute output)
preds.append(pred.detach().cpu()) # collect prediction
preds = torch.cat(preds, dim=0).numpy() # concatenate all predictions and convert to a numpy array
return preds
将测试集的数据进行一一预测的值,转换为numpy格式进行输出
import pandas as pd
sample = pd.read_csv('../input/ml2021spring-hw1/sampleSubmission.csv')
sample['tested_positive'] = preds
sample.to_csv('submission.csv', index=False)
最后提交结果
这个结果很差,还不如用rf进行预测的结果好,原因有好几个:
1、有可能是因为数据量太少,只有2700行
2、模型选取的是双层的全连接层,维度是64,所以说模型可能选取有问题
3、可能这个问题用机器学习就是比深度学习要好,原因可能还是因为1(数据量不够的问题)
后续会进行俩种测试,一种是继续把深度学习的这个模型进行狂改,另一种就是先对数据进行处理以下,再进行魔改模型,第三种就是试试用其他机器学习模型看看效果怎么样(这个最后采用,因为这个作业的目的就是为了学习深度学习而不是学习机器学习算法)
29号第一天上学,很多事情都没搞完,晚上又做了2个多小时的核酸是,故而没有跑代码。
30号下午,再次进行代码改进,叠层已经不起效果了,但是从0开始改框架,我还没掌握,故而此篇博客暂时记录到此,先去kaggle看一些pytorch实例的代码,学一下怎么用,不然直接大改代码容易把代码改死机,冲冲冲冲冲冲继续干!!