在经历了MATLAB的困扰后,我决定尝试使用Python:
我编写了一个函数,用于计算当样本属于我自己的类时,使用我自己的距离函数进行kNN计算:
def closestK(sample, otherSamples, distFunc, k):"返回基于distFunc的样本最接近的k个样本" n = len(otherSamples) d = [distFunc(sample, otherSamples[i]) for i in range(0,n)] idx = sorted(range(0,len(d)), key=lambda k: d[k]) return idx[1:(k+1)]def kNN(samples, distFunc, k): return [[closestK(samples[i], samples, distFunc, k)] for i in range(len(samples))]
这是距离函数:
@staticmethod def distanceRepr(c1, c2): r1 = c1.repr r2 = c2.repr # 因为cdist需要二维数组 if r1.ndim == 1: r1 = np.vstack([r1,r1]) if r2.ndim == 1: r2 = np.vstack([r2,r2]) return scipy.spatial.distance.cdist(r1, r2, 'euclidean').min()
但与“普通”的kNN函数相比,它仍然运行得非常慢,即使使用了“brute”算法。我做错了什么吗?
更新
我添加了类的构造函数。属性repr包含一组向量(从1到任意数量),距离计算为两个repr集合之间的最小欧几里得距离。
class myCluster: def __init__(self, index = -1, P = np.array([])): if index ==-1 : self.repr = np.array([]) self.IDs = np.array([]) self.n = 0 self.center = np.array([]) else: self.repr = np.array(P) self.IDs = np.array(index) self.n = 1 self.center = np.array(P)
以及其他相关代码(X是一个矩阵,其行是样本,列是变量):
level = [myCluster(i, X[i,:]) for i in range(0,n)]kNN(level, myCluster.distanceRepr, 3)
更新2
我进行了一些测量,发现耗时最多的行是
d = [distFunc(sample, otherSamples[i]) for i in range(0,n)]
所以问题出在distFunc
上。当我将其更改为返回
np.linalg.norm(c1.repr-c2.repr)
即“普通”的向量计算,没有排序时,运行时间保持不变。所以问题在于调用这个函数。使用类会使运行时间增加60倍,这合理吗?
回答:
你只是遇到了Python(或者更准确地说是CPython解释器)的慢速问题。根据维基百科的说法:
NumPy针对的是Python的CPython参考实现,这是一个非优化的字节码编译器/解释器。为此版本的Python编写的数学算法通常比编译的等价物运行得慢得多。NumPy试图通过提供多维数组以及高效操作数组的函数和运算符来解决这个问题。因此,任何可以主要表示为数组和矩阵操作的算法都可以运行得几乎和等价的C代码一样快。
根据Scipy常见问题解答:
Python的列表是高效的通用容器。它们支持(相当)高效的插入、删除、追加和连接操作,Python的列表推导式使它们易于构建和操作。然而,它们有一些限制:它们不支持像逐元素加法和乘法这样的“向量化”操作,并且它们可以包含不同类型的对象,这意味着Python必须为每个元素存储类型信息,并且在操作每个元素时必须执行类型调度代码。这也意味着很少有列表操作可以由高效的C循环执行——每次迭代都需要类型检查和其他Python API的簿记工作。
请注意,这不仅仅是Python的问题;有关更多背景,请参见例如这个和这个在Stack Overflow上的问题。
由于动态类型系统和解释器的开销,如果Python无法利用各种编译的C和Fortran库(例如NumPy),它在高性能数字计算方面的用处将大大减少。此外,还有像Numba和PyPy这样的JIT编译器,它们试图使Python代码的执行速度接近静态类型、编译代码的速度。
结论:你在普通Python中做的工作太多,而卸载到快速C代码的工作太少。我认为你需要采用更像“数组导向”的编码风格,而不是面向对象的风格,以实现使用NumPy的高性能(MATLAB在这方面的情况非常相似)。另一方面,如果你使用更高效的算法(参见Ara的回答),那么Python的慢速可能就不是那么大的问题了。