查找列表中元素索引的最快方法

时间:2017-07-07 17:59:45

标签: python algorithm

我有一组样本(比如X)和一些特征(比如说Y),我需要运行一些机器学习算法(比如说PCA)。一种方法是生成矩阵(样本,特征)。我构建矩阵的方法包括两个步骤:

  1. 获取整个数据集的所有要素值。我们称之为游泳池。
  2. 对于样本,对于样本中的每个值,在池中查找值的索引。值存在的索引为1,缺少值为0.
  3. 例如:考虑以下示例

    sample1 = A, B, D
    sample2 = A, C, E
    sample3 = A, C, D
    
    1. 生成的池= A,B,C,D,E
    2. 生成矩阵

      • 样本1 => 1,1,0,1,0
      • 样本2 => 1,0,1,0,1
      • 样本3 => 1,0,0,1,0
    3. 生成此矩阵的挑战在于,数据非常庞大。行数 465337 ,列数 35526155

      生成池需要大约20分钟,即使这很慢,我也很好。但是当生成矩阵的向量(即行)时,我必须得到所考虑的池中该值的索引的所有值。

      这需要很长时间。有没有更好的方法来找到元素的索引?如果程序本身不是最佳的,请告诉我生成矩阵的更好方法。

      另外,我只是存储索引并从中构建CSR矩阵而不是密集矩阵。

2 个答案:

答案 0 :(得分:1)

您可以使用scikit-learn Count Vectorizer来实现此类编码。有关示例用例,请参阅here。因为你提到机器学习并且正在使用Python,所以我认为你一般都熟悉sklearn。

但是,由于CountVectorizer用于文本标记化,因此将它用于您的问题有点麻烦。

例如,如果您的输入数据采用以下格式:

samples_s = ["".join(l) for l in samples]

您应该首先将内部列表转换为字符串:

['ABD', 'ACE', 'ACD']

给出了

vec = CountVectorizer(token_pattern='.')
X = vec.fit_transform(samples_s)

现在您可以安装CountVectorizer的实例。您只需定义构成令牌的内容即可。在这里,我使用一个简单的正则表达式来定义任何单个字符(例如" A"," B"或" C")作为标记。您还可以提供静态词汇表。

X.toarray()

调用array([[1, 1, 0, 1, 0], [1, 0, 1, 0, 1], [1, 0, 1, 1, 0]], dtype=int64) 返回

{{1}}

使用sklearn的实现应该比自己编码快得多。

答案 1 :(得分:1)

正如我在评论中提到的那样,您希望使用sklearn.feature_extraction.text.CountVectorizer来使用binary=True参数,因为您实际上并不需要计数。你创建你的编码输出一个稀疏矩阵来启动!

但是,如果您对方法的根本问题感兴趣,那么从根本上说问题是您使用序列类型list,其中.index方法是线性时间操作。当您尝试使用列表时,您会感受到这种事实的痛苦。这是一个 sketch ,它是如何使用字典更有效地实现这种方式的:

In [15]: tokens = list('qwertydfgndjfkgnf')

In [16]: pool = {}

In [17]: for t in tokens:
    ...:     pool.setdefault(t, len(pool))
    ...:

In [18]: pool
Out[18]:
{'d': 6,
 'e': 2,
 'f': 7,
 'g': 8,
 'j': 10,
 'k': 11,
 'n': 9,
 'q': 0,
 'r': 3,
 't': 4,
 'w': 1,
 'y': 5}

In [19]: tokens.index('g') # ew, O(n) time complexity
Out[19]: 8

In [20]: pool['g'] # nice! O(1) time complexity
Out[20]: 8

此池现在包含从令牌到索引的编码。在这里访问索引是一个常量时间操作。这将显着提高性能。事实上,由于我们只是开始dict,而不是从set转换为list,这将大大减少您的常数因素。

注意,上面基本上是sklearn对象正在做的事情。

https://github.com/scikit-learn/scikit-learn/blob/ab93d65/sklearn/feature_extraction/text.py#L745

但请注意,他们使用defaultdict,这是针对此类事情进行优化的,采用以下令人愉快的小方法:

In [24]: from collections import defaultdict

In [25]: pool = defaultdict()

In [26]: pool.default_factory = pool.__len__

In [27]: for t in tokens:
    ...:     pool[t]
    ...:

In [28]: pool
Out[28]:
defaultdict(<method-wrapper '__len__' of collections.defaultdict object at 0x1088aa9f8>,
            {'d': 6,
             'e': 2,
             'f': 7,
             'g': 8,
             'j': 10,
             'k': 11,
             'n': 9,
             'q': 0,
             'r': 3,
             't': 4,
             'w': 1,
             'y': 5})

还会在文档上循环时构建稀疏表示,因此它们实际上只对数据集进行一次传递。所以sklearn对象就像你要获得的那样优化。 sklearn源代码实际上非常平易近人,值得查看他们使用没有Cython扩展或任何东西的纯python完成的任务。