Otto Group Product Classification Challenge是Kaggle上目前正在进行的一个比赛,目前已1000+队伍参赛,由Otto公司赞助1W美刀,数据也是来自于该公司的产品,提供了train.csv、test.csv、samplesubmission.csv三份数据。train.csv里包含了6万多个样本,每个样本有一个id,93个特征值feat_1~feat_93,以及类别target,一共9种类别:class_1~class_9。test.csv里有14万多测试样本,只有id以及93个特征,参赛者的任务是对这些样本分类。
官网上给出的格式:
id,Class_1,Class_2,Class_3,Class_4,Class_5,Class_6,Class_7,Class_8,Class_9
1,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0
2,0.0,0.2,0.3,0.3,0.0,0.0,0.1,0.1,0.0
也就是说,可以不用给出确切的类别,给出属于各个类别的概率也行。这一点很重要,我一开始没注意看,浪费了许多精力瞎折腾。所以但凡比赛,官网给出的信息最好都仔细看一遍。
评分的公式见:这里
我稍微解释一下这条公式,i表示样本,j表示类别。Pij代表第i个样本属于类别j的概率,如果第i个样本真的属于类别j,则yij等于1,否则为0。
假如你将所有的测试样本都正确分类,所有pij都是1,那每个log(pij)都是0,最终的logloss也是0。
假如第1个样本本来是属于1类别的,但是你给它的类别概率pij=0.1,那logloss就会累加上log(0.1)这一项。我们知道这一项是负数,而且pij越小,负得越多,如果pij=0,将是无穷。这会导致这种情况:你分错了一个,logloss就是无穷。这当然不合理,为了避免这一情况,官方是这样处理的:
也就是说最小不会小于10^-15。
废话终于说完,进入主题。下面讲一个很naive的solution,基本上不用动脑,只要会简单地使用sklearn、numpy,就能打败目前一半的参赛队伍。
代码放在我的github上wepe/Kaggle-Solution,分几部分来讲:
严格来说,我并没有做多少数据预处理的工作。只是一些加载数据的函数,分别是
loadTrainSet()和loadTestSet(),加载训练数据集和测试数据集。代码如下,里面顺便做了归一化以及零均值(这里不做standardize差别也不大)。
#load train set
def loadTrainSet():
traindata = []
trainlabel = []
table = {"Class_1":1,"Class_2":2,"Class_3":3,"Class_4":4,"Class_5":5,"Class_6":6,"Class_7":7,"Class_8":8,"Class_9":9}
with open("train.csv") as f:
rows = csv.reader(f)
rows.next()
for row in rows:
l = []
for i in range(1,94):
l.append(int(row[i]))
traindata.append(l)
trainlabel.append(table.get(row[-1]))
f.close()
traindata = np.array(traindata,dtype="float")
trainlabel = np.array(trainlabel,dtype="int")
#Standardize(zero-mean,nomalization)
mean = traindata.mean(axis=0)
std = traindata.std(axis=0)
traindata = (traindata - mean)/std
#shuffle the data
randomIndex = [i for i in xrange(len(trainlabel))]
random.shuffle(randomIndex)
traindata = traindata[randomIndex]
trainlabel = trainlabel[randomIndex]
return traindata,trainlabel
#load test set
def loadTestSet():
testdata = []
with open("test.csv") as f:
rows = csv.reader(f)
rows.next()
for row in rows:
l = []
for i in range(1,94):
l.append(int(row[i]))
testdata.append(l)
f.close()
testdata = np.array(testdata,dtype="float")
#Standardize(zero-mean,nomalization)
mean = testdata.mean(axis=0)
std = testdata.std(axis=0)
testdata = (testdata - mean)/std
return testdata
代码挺长的,但其实都是些简单的读取和处理工作。(如果不想写这么繁琐的代码,可以试试数据分析包pandas,几行代码完成这些工作)。
模型评估一般都是从训练数据集中分出一部分来做validation,一般我们是用validation accuracy来评估的(如上所述),但是这个比赛既然官方给出了评估公式,我们就根据这个公式写个评估函数:
#Evaluation function
#Refer to:https://www.kaggle.com/c/otto-group-product-classification-challenge/details/evaluation
def evaluation(label,pred_label):
num = len(label)
logloss = 0.0
for i in range(num):
p = max(min(pred_label[i][label[i]-1],1-10**(-15)),10**(-15))
logloss += np.log(p)
logloss = -1*logloss/num
return logloss
以上三个函数loadTrainSet()、loadTestSet()、evaluation()我都放在代码文件preprocess.py中,此外在该文件中还定义了一个saveResult()函数,根据test set的预测类别test_label生成一个用于提交结果的submission.csv文件。
直接调用sklearn里面的随机森林,参数调节:随便调了几次决策树的数目,可以使logloss达到0.5左右,这个得分目前已经打败了一半的参赛队伍。
def rf(train_data,train_label,val_data,val_label,test_data,name="RandomForest_submission.csv"):
print "Start training Random forest..."
rfClf = RandomForestClassifier(n_estimators=400,n_jobs=-1)
rfClf.fit(train_data,train_label)
#evaluate on validation set
val_pred_label = rfClf.predict_proba(val_data)
logloss = preprocess.evaluation(val_label,val_pred_label)
print "logloss of validation set:",logloss
print "Start classify test set..."
test_label = rfClf.predict_proba(test_data)
preprocess.saveResult(test_label,filename = name)
这个函数的参数val_data就是验证集数据,从train.csv里面取出6000个构成,大约占1/10的数据。
这部分的代码放在文件RandomForest.py中。
有兴趣的去我github获取代码试试吧。Have Fun!
============
by wepon