第一次尝试在kaggle上找机器学习(ML)项目练手,Titanic问题是官方的入门项目,在此做一个记录。
kaggle官网:https://www.kaggle.com/
进入官网之后左边那一栏的compete表示ML竞赛项目,点击某项竞赛后会有项目说明(Overview),数据集(Data)以及其他人对此项目的一些讨论(Notebooks、Discussion),点击join compete即可加入此竞赛。接下来要做的事就是使用数据集完成Overview中说明的任务,并将模型在测试集上运行的结果提交上去看自己模型的排名。
在本篇文章中,以kaggle官方的入门竞赛titanic为例走一遍整个流程,实际上kaggle自己的入门指引就很详细了。
我使用的ML框架是pytorch,pytorch的教程可以参考:
此项Titanic竞赛的目标就是给定乘客的一些信息,需要预测出乘客是否在最终的船难事故中生存下来,即预测Survived为0还是1。从kagggle上下载下来的数据集有三个:test.csv是测试使用的数据集;train.csv是训练使用的数据集,里面带有标签;而gender_submission.csv是上传范例,也就是需要按照这里面的格式将你训练好的模型得到的结果上传到kaggle上以评分。
打开train.csv,查看里面的数据格式,依照官方给的说明确定每列代表的含义:
针对这一类数据,不像图像那样直接将图片做一些预处理再读取为矩阵放入神经网络就完事。这类数据需要进行特征建模,得到适合放入神经网络的数据——一般来说就是一个数组。
首先需要对特征进行一定的筛选,有一些特征我认为是对分类没有帮助或者不好处理的,比如乘客名字、船票编号、房间号,于是直接舍弃这几项特征,直接对剩下的特征进行建模。我的建模思路如下:
这样,将信息编码为15个二进制位,联合成数组方便输入网络。
实际上像这样的几百条数据是没有必要创建数据集类的,直接读就好了。但是为了规范代码并增强可扩展性,还是使用pytorch的数据集类来读取数据,也顺便练练手。
数据集类也就是Pytorch中的Dataset,创建自己的数据集类时需要继承自此类,并且必须重写其中的__getitem__(self,index)与__len__(self)。基本的模板如下:
from torch.utils.data.dataset import Dataset
class TitanicDataset(Dataset):
"""docstring for TitanicDataset"""
def __init__(self, arg):
super(TitanicDataset, self).__init__()
def __getitem__(self, index):
# 一些数据处理
# 返回将要作为输入的feature和对应的label
return feature,label
def __len__(self):
# 返回数据集大小
return dataset_num
__getitem__方法虽然不需要我们显示调用,但是是之后读取数据时的核心函数。作用是给一个数据的索引(index),返回这条数据特征与标签,因此一般来说在__init__中我们会将数据集所有数据的索引载入为一个数组(如果是比较大的数据,不需要载入数据本身,只需要载入其在硬盘中存放的位置等等),在__getitem__再依据索引真实地将数据载入内存中使用。
创建好Dataset类之后,需要调用Dataloader来使用数据集,方法如下,一般只需要指定batch_size、num_workers与shuffle这几个参数就好了,分别表示batch大小、是否打乱、读取线程数量:
train_dataset = TitanicDataset()
train_dataloader = DataLoader(train_dataset, batch_size=5, shuffle=True, num_workers=5)
for feature,label in train_dataloader :
print('网络输入:',feature)
print('标签:',label)
使用最简单的一个全连接层+激活函数的配置,从torch.nn中继承module类,构建模型如下:
class TitanicModel(nn.Module):
"""docstring for TitanicModel"""
def __init__(self, input_size, output_size):
super(TitanicModel, self).__init__()
self.mylinear = nn.Linear(input_size, output_size)
def forward(self, x):
x = self.mylinear(x)
y_pred = func.relu(x)
return y_pred
训练也很简单,先设置好优化器和loss函数,然后读取数据进行正向传播,最后进行反向梯度传播并更新权重就可以了:
#设置优化器和loss函数
optimizer = optim.SGD(model.parameters(),lr=0.01)
loss_fn = nn.MSELoss(reduction='mean')
#训练
for epoch in range(50):
# train
model.train()
loss_epoch = 0.0
for infos,label in self.train_dataloader:
y_pred = model(infos)
# 计算loss
loss = loss_fn(y_pred,label)
loss_epoch += loss
# 梯度反向传播并更新权重
optimizer.zero_grad()
loss.backward()
optimizer.step()
print('epoch[{}]Loss={}'.format(epoch,loss_epoch))
# eval
model.eval()
acc_count = 0
count = 0
for infos,label in self.eval_dataloader:
count += 1
y_pred = model(infos)
if y_pred > 0.5:
suvive = 1
else:
suvive = 0
if suvive == label:
acc_count += 1
print('epoch[{}]eval_acc:{}'.format(epoch,acc_count/count))
部分训练结果如下,因为特征构建与模型都很简单,所以没有太高的准确率,不过能有80%左右也足够说明网络有在工作了。
用训练好的模型在测试集上进行预测,这里的话注意需要在dataset中进行一个分支判断,判断当前处于训练还是测试,以在__getitem__时返回不同的值。如果是测试模式,则返回的应当是(feature,passanger_id),因为测试集上没有标签而且需要确定乘客的编号。
test_model = TitanicModel(15,1)
test_model.load_state_dict(torch.load(model_name))
test_model.eval()
test_dataset = TitanicDataset(test_data,train=False)
test_dataloader = DataLoader(test_dataset, batch_size=1, num_workers=5)
result = {}
for infos,pid in test_dataloader:
y_pred = test_model(infos)
if y_pred > 0.5:
result[pid] = 1
else:
result[pid] = 0
# save as csv
f = open('test_model_predict.csv','w',newline='')
writer = csv.writer(f)
writer.writerow(['PassengerId','Survived'])
for key,value in result.items():
writer.writerow([int(key),value])
f.close()
得到官方要求格式的预测文件后,就可以去kaggle官网上传结果了,在比赛主页点击Submit Predictions进入上传页面后,上传得到的csv文件即可。
上传完毕后会给一个评分,点击评分下面的蓝色链接会给你看你的排名数据,我这个差不多排到一万六千多名以后了,看排名靠前的都有100%的预测准确率,说明对于这个任务,不管是在特征工程还是在模型方面都还有很大的上升空间的:
整个流程就这样结束了,这也算是我在kaggle上弄的第一个项目,主要是为了弄清楚其上面的竞赛流程并做一个记录。不得不说这是一个很好的锻炼自己机器学习编码能力与算法设计能力的平台,以后也希望自己能在上面多花一些时间做做竞赛。