为什么这两个视觉上相等的稀疏数组 np.array_equal return False?

Why does np.array_equal return False for these two sparse arrays that are visually equal?

我有两种计算数组单位向量的方法,这两种方法都处理稀疏数组。其中之一是 'manual' 计算,而另一个更多是 'formal'(来自 gensim.matutils 源代码)。

这是手动方法:

def manual_unitvec(vec):
        vec = vec.tocsr()
        if sparse.issparse(vec):
            vec_sum_of_squares = vec.multiply(vec)
            unit = 1. / np.sqrt(vec_sum_of_squares.sum())
            return vec.multiply(unit)
        elif not sparse.issparse(vec):
            sum_vec_squared = np.sum(vec ** 2)
            vec /= np.sqrt(sum_vec_squared)
            return vec

这是修改后的gensim方法,显式计算单位向量的方法是unitvec:

import numpy as np
from scipy import sparse
from gensim.matutils import ret_normalized_vec, blas
import scipy.sparse

blas_nrm2 = blas('nrm2', np.array([], dtype=float))
blas_scal = blas('scal', np.array([], dtype=float))


def unitvec(vec, norm='l2'):
    """Scale a vector to unit length.
    Parameters
    ----------
    vec : {numpy.ndarray, scipy.sparse, list of (int, float)}
        Input vector in any format
    norm : {'l1', 'l2'}, optional
        Normalization that will be used.
    Returns
    -------
    {numpy.ndarray, scipy.sparse, list of (int, float)}
        Normalized vector in same format as `vec`.
    Notes
    -----
    Zero-vector will be unchanged.
    """
    if norm not in ('l1', 'l2'):
        raise ValueError("'%s' is not a supported norm. Currently supported norms are 'l1' and 'l2'." % norm)

    if scipy.sparse.issparse(vec):
        print("INSIDE SPARSE HANDLING")
        vec = vec.tocsr()
        if norm == 'l1':
            veclen = np.sum(np.abs(vec.data))
        if norm == 'l2':
            veclen = np.sqrt(np.sum(vec.data ** 2))
        if veclen > 0.0:
            if np.issubdtype(vec.dtype, np.int) == True:
                vec = vec.astype(np.float)
                return vec / veclen
            else:
                vec /= veclen
                return vec.astype(vec.dtype)
        else:
            return vec

    if isinstance(vec, np.ndarray):
        print("INSIDE NORMAL VEC HANDLING")
        vec = np.asarray(vec, dtype=vec.dtype)
        if norm == 'l1':
            veclen = np.sum(np.abs(vec))
        if norm == 'l2':
            veclen = blas_nrm2(vec)
        if veclen > 0.0:
            if np.issubdtype(vec.dtype, np.int) == True:
                vec = vec.astype(np.float)
                return blas_scal(1.0 / veclen, vec).astype(vec.dtype)
            else:
                return blas_scal(1.0 / veclen, vec).astype(vec.dtype)
        else:
            return vec

    try:
        first = next(iter(vec))  # is there at least one element?
    except StopIteration:
        return vec

    if isinstance(first, (tuple, list)) and len(first) == 2:  # gensim sparse format
        print("INSIDE GENSIM SPARSE FORMAT HANDLING")
        if norm == 'l1':
            length = float(sum(abs(val) for _, val in vec))
        if norm == 'l2':
            length = 1.0 * math.sqrt(sum(val ** 2 for _, val in vec))
        assert length > 0.0, "sparse documents must not contain any explicit zero entries"
        return ret_normalized_vec(vec, length)
    else:
        raise ValueError("unknown input type")

当 运行 测试时,我想检查这些方法中的每一个的输出是否相同。下面是一段示例代码:

vec = sparse.csr_matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]).astype(np.float32)
output1 = manual_unitvec(vec)
output2 = unitvec(vec)
print(output1)
print(' ')
print(output2) 
print(np.array_equal(output1, output2))
print(type(output1) == type(output2))

所以我要检查的是assertTrue(output1, output2)。你不能这样做,因为数组的真值是不明确的,所以我使用 assertTrue(np.array_equal(output1, output2))

现在的问题是 array_equal 并不认为 output1 和 output2 是相同的,尽管我可以从打印出来的结果中看出它们是相同的。

运行 上面的所有代码给出了以下输出:

MacBook-Air:matutils.unitvec Olly$ python try.py
INSIDE SPARSE HANDLING
try.py:80: FutureWarning: Conversion of the second argument of issubdtype from `int` to `np.signedinteger` is deprecated. In future, it will be treated as `np.int64 == np.dtype(int).type`.
  if np.issubdtype(vec.dtype, np.int) == True:
  (0, 0)    0.059234887
  (0, 1)    0.118469775
  (0, 2)    0.17770466
  (1, 0)    0.23693955
  (1, 1)    0.29617444
  (1, 2)    0.35540932
  (2, 0)    0.4146442
  (2, 1)    0.4738791
  (2, 2)    0.53311396

  (0, 0)    0.059234887
  (0, 1)    0.118469775
  (0, 2)    0.17770466
  (1, 0)    0.23693955
  (1, 1)    0.29617444
  (1, 2)    0.35540932
  (2, 0)    0.4146442
  (2, 1)    0.4738791
  (2, 2)    0.53311396
/Users/Olly/anaconda2/lib/python2.7/site-packages/scipy/sparse/compressed.py:226: SparseEfficiencyWarning: Comparing sparse matrices using == is inefficient, try using != instead.
  " != instead.", SparseEfficiencyWarning)
False
True

我认为问题可能出在稀疏数组类型上,但如您所见,它们是相等的。也可以直观的看到元素完全一样

那么为什么 array_equal 返回 false?我该如何更改它?

在你的第一个函数中你做了:

    vec = vec.tocsr()
    if sparse.issparse(vec):

我认为 issparse 测试对您没有任何帮助。如果输入参数是稀疏矩阵,它有一个 tocsr 方法,结果是稀疏矩阵。如果 vecndarray 则没有 tocsr 方法,第一行将抛出错误。

在该函数的其余部分,稀疏矩阵有一个 multiply 方法和一个 sum 方法。 sum 的结果是密集的,因此 np.sqrt 可以正常工作。实际上 np.sqrt(M) 也适用于稀疏矩阵,因为 M.sqrt 存在。

在第二个函数中,您使用 data 属性,它是一维 ndarray.

np.sum(np.abs(vec.data))

没关系。但是请注意,稀疏矩阵的 M.__abs__

self._with_data(abs(self._deduped_data()))

稍微绕一圈,funtions/methods 和 abs 一样,sqrt 也可以使用 .data 属性。只有他们return一个新的稀疏矩阵。

至于测试看np.array_equal

return bool(asarray(a1 == a2).all())

如果我尝试在 output1 上使用它(我不会尝试您的 gensim 解决方案

In [106]: np.array_equal(output1, output1)
/usr/local/lib/python3.5/dist-packages/scipy/sparse/compressed.py:226: SparseEfficiencyWarning: Comparing sparse matrices using == is inefficient, try using != instead.
  " != instead.", SparseEfficiencyWarning)
Out[106]: False

它不喜欢在稀疏矩阵上取 ==。通常这些很大,有很多 0。这意味着所有这些结果将是 True,因此不再稀疏。

你的 output1 是一个稀疏矩阵,但是,至少对于这些输入,不是稀疏的:

In [107]: output1.A
Out[107]: 
array([[0.05923489, 0.11846977, 0.17770466],
       [0.23693955, 0.29617444, 0.35540932],
       [0.4146442 , 0.4738791 , 0.53311396]], dtype=float32)

但是即使你绕过了稀疏位,np.array_equal(output1.A, output2.A) 也可能会因为比较浮点数而失败

allclose 密集版本可能是最简单的测试:

In [113]: np.allclose(output1.A, output1.A)
Out[113]: True

您还可以比较 data(假设稀疏度相同):

In [114]: np.allclose(output1.data, output1.data)
Out[114]: True

更全面的稀疏测试需要检查 shapennzindices 属性。

实际上我不确定 np.array_equal 哪里出了问题。请注意,它以 a1=asarray(a1) 开头,它生成一个 0d 对象 dtype 数组。这是坚持将其输入视为数组的 numpy 函数之一。它不是稀疏的。