python-3.x - 通过利用测试数据了解数据泄漏并获得满分
问题描述
我读过一篇关于数据泄露的文章。在黑客马拉松中,有两组数据,参与者训练算法的训练数据和测量性能的测试集。数据泄漏有助于在测试数据中获得满分,而无需通过利用泄漏来查看火车数据。我已经阅读了这篇文章,但我错过了如何利用泄漏的关键。如文章所示的步骤如下:
让我们加载测试数据。
请注意,我们这里没有任何训练数据,只有测试数据。此外,我们甚至不会使用测试对象的任何特征。我们需要解决这个任务是包含我们需要比较的对索引的文件。让我们用测试索引加载数据。
test = pd.read_csv('../test_pairs.csv')
test.head(10)
pairId FirstId SecondId
0 0 1427 8053
1 1 17044 7681
2 2 19237 20966
3 3 8005 20765
4 4 16837 599
5 5 3657 12504
6 6 2836 7582
7 7 6136 6111
8 8 23295 9817
9 9 6621 7672
test.shape[0]
368550
例如,我们可以认为有一个图像的测试数据集,每个图像都被分配了一个从 0 到 N−1 的唯一 Id(N -- 是图像的数量)。在上面的数据框中,FirstId 和 SecondId 指向这些 Id 并定义对,我们应该比较:例如,对中的两个图像是否属于同一类。因此,例如对于第一行:如果 Id=1427 和 Id=8053 的图像属于同一类,我们应该预测 1,否则预测 0。但在我们的例子中,我们并不真正关心图像,以及我们如何比较图像(只要比较器是二进制的)。
print(test['FirstId'].nunique())
print(test['SecondId'].nunique())
26325
26310
因此,与总对数相比,我们要分类的对数非常少。为了利用泄漏,我们需要假设(或证明)正对的总数与对的总数相比很小。例如:考虑一个包含 1000 个类的图像数据集,每个类 N 个图像。那么如果任务是判断一对图像是否属于同一类,我们将有 1000*N*(N−1)/2 个正对,而对的总数为 1000*N(1000N−1 )/2。
另一个例子:在 Quora 竞赛中,任务是分类一对 qustions 是否彼此重复。当然,问题对的总数非常庞大,而重复(正对)的数量要少得多。
最后,让我们得到 1 类对的一小部分。我们只需要提交一个常量预测“all one”并检查返回的准确性。创建一个包含 pairId 和 Prediction 列的数据框,填充它并将其导出到 .csv 文件。然后提交
test['Prediction'] = np.ones(test.shape[0])
sub=pd.DataFrame(test[['pairId','Prediction']])
sub.to_csv('sub.csv',index=False)
All ones have accuracy score is 0.500000.
因此,我们假设对的总数远高于阳性对的数量,但测试集并非如此。这意味着测试集不是通过随机抽样对构建的,而是通过特定的抽样算法构建的。第 1 类的对被过采样。现在想一想,我们如何利用这个事实?这里的泄漏是什么?如果您现在得到它,您可以尝试自己获得最终答案,否则您可以按照以下说明进行操作。
构建一个神奇的功能
在本节中,我们将构建一个神奇的功能,它将几乎完美地解决问题。这些说明将引导您找到正确的解决方案,但请尝试向您自己解释我们执行这些步骤的目的——这非常重要。
发生矩阵
首先,我们需要建立一个关联矩阵。您可以将对 (FirstId, SecondId) 视为无向图中的边。关联矩阵是一个大小为 (maxId + 1, maxId + 1) 的矩阵,其中每一行(列)i 对应于第 i 个 Id。在这个矩阵中,我们将值 1 放在位置 [i, j],当且仅当一对 (i, j) 或 (j, i) 存在于给定的一组 pais (FirstId, SecondId) 中。关联矩阵中的所有其他元素都为零。重要的!关联矩阵通常非常稀疏(少数非零值)。同时,关联矩阵的元素总数通常很大,不可能以密集格式将它们存储在内存中。但是由于它们的稀疏性,关联矩阵可以很容易地表示为稀疏矩阵。如果你不熟悉稀疏矩阵,请参阅 wiki 和 scipy.sparse 参考。请使用任何 scipy.sparseconstructors 来构建关联矩阵。例如,您可以使用此构造函数:scipy.sparse.coo_matrix((data, (i, j)))。我们强烈建议学习使用不同的 scipy.sparseconstructors 和矩阵类型,但如果您不想使用它们,您可以随时使用简单的 for 循环构建此矩阵。您需要首先使用 scipy.sparse.coo_matrix((M, N), [dtype]) 创建一个具有适当形状 (M, N) 的矩阵,然后遍历 (FirstId, SecondId) 对并填充矩阵中的相应元素与那些。sparseconstructors 和矩阵类型,但如果你不想使用它们,你总是可以用一个简单的 for 循环来构建这个矩阵。您需要首先使用 scipy.sparse.coo_matrix((M, N), [dtype]) 创建一个具有适当形状 (M, N) 的矩阵,然后遍历 (FirstId, SecondId) 对并填充矩阵中的相应元素与那些。sparseconstructors 和矩阵类型,但如果你不想使用它们,你总是可以用一个简单的 for 循环来构建这个矩阵。您需要首先使用 scipy.sparse.coo_matrix((M, N), [dtype]) 创建一个具有适当形状 (M, N) 的矩阵,然后遍历 (FirstId, SecondId) 对并填充矩阵中的相应元素与那些。
请注意,矩阵应该是对称的,并且只包含零和一。这是一种检查自己的方法。
import networkx as nx
import numpy as np
import pandas as pd
import scipy.sparse
import matplotlib.pyplot as plt
test = pd.read_csv('../test_pairs.csv')
x = test[['FirstId','SecondId']].rename(columns={'FirstId':'col1', 'SecondId':'col2'})
y = test[['SecondId','FirstId']].rename(columns={'SecondId':'col1', 'FirstId':'col2'})
comb = pd.concat([x,y],ignore_index=True).drop_duplicates(keep='first')
comb.head()
col1 col2
0 1427 8053
1 17044 7681
2 19237 20966
3 8005 20765
4 16837 599
data = np.ones(comb.col1.shape, dtype=int)
inc_mat = scipy.sparse.coo_matrix((data,(comb.col1,comb.col2)), shape=(comb.col1.max() + 1, comb.col1.max() + 1))
rows_FirstId = inc_mat[test.FirstId.values,:]
rows_SecondId = inc_mat[test.SecondId.values,:]
f = rows_FirstId.multiply(rows_SecondId)
f = np.asarray(f.sum(axis=1))
f.shape
(368550, 1)
f = f.sum(axis=1)
f = np.squeeze(np.asarray(f))
print (f.shape)
现在构建魔术功能
我们为什么要建立关联矩阵?我们可以将此矩阵中的行视为对象的表示。第 i 行是 Id = i 的对象的表示。然后,为了测量两个对象之间的相似性,我们可以测量它们表示之间的相似性。我们将看到,这样的表示非常好。
现在从关联矩阵中选择对应于 test.FirstId 和 test.SecondId 的行。
所以不要忘记转换pd.series
为np.array
这些行通常应该运行得非常快
rows_FirstId = inc_mat[test.FirstId.values,:]
rows_SecondId = inc_mat[test.SecondId.values,:]
我们的神奇功能将是一对对象表示之间的点积。点积可以看作是相似性度量——对于我们的非负表示,当表示不同时,点积接近 0,而当表示相似时,点积很大。现在计算 rows_FirstId 和 rows_SecondId 矩阵中对应行之间的点积。
从魔术特征到二元预测
但是我们如何将此特征转换为二进制预测呢?我们没有训练集来学习模型,但我们有一条关于测试集的信息:提交常量时获得的基线准确度分数。而且我们对数据生成过程也有非常强烈的考虑,所以即使没有训练集,我们也可能会很好。我们可以尝试选择一个阈值,如果特征值 f 大于阈值,则将预测设置为 1,否则设置为 0。你会选择什么门槛?我们如何找到正确的阈值?让我们首先检查这个特征:打印特征 f 中每个值的频率(或计数)。
例如使用np.unique
函数,检查标志函数来计算每个元素的频率
from scipy.stats import itemfreq
itemfreq(f)
array([[ 14, 183279],
[ 15, 852],
[ 19, 546],
[ 20, 183799],
[ 21, 6],
[ 28, 54],
[ 35, 14]])
你看到这个特征是如何聚集成对的了吗?也许您可以通过查看值来猜测一个好的阈值?事实上,在其他情况下,它可能不是那么明显,但通常要选择一个阈值,您只需要记住基线提交的分数并使用此信息。在下面选择一个阈值:
pred = f > 14 # SET THRESHOLD HERE
pred
array([ True, False, True, ..., False, False, False], dtype=bool)
submission = test.loc[:,['pairId']]
submission['Prediction'] = pred.astype(int)
submission.to_csv('submission.csv', index=False)
我想了解这背后的想法。我们如何仅利用测试数据的泄漏。
解决方案
文章中有提示。正对数应为 1000*N*(N-1)/2,而所有对数应为 1000*N(1000N-1)/2。当然,如果测试集是随机抽样的,那么所有对的数量会大得多。
正如作者所提到的,在您评估您在测试集上对 1 的恒定预测之后,您可以看出抽样不是随机进行的。您获得的准确率为 50%。如果采样正确完成,这个值应该低得多。
因此,他们构建关联矩阵并计算我们的 ID 特征表示之间的点积(相似性度量)。然后,他们重复使用通过恒定预测(50%)获得的准确度信息来获得相应的阈值(f > 14)。它设置为大于 14,因为这构成了我们测试集的大约一半,这反过来又映射回 50% 的准确度。
“魔法”值不必大于 14。它可以等于 14。你可以在一些排行榜探测后调整这个值(只要你捕获了一半的测试集)。