写在前面
其实在大规模数据集下(数据在百万级以上且特征在百维以上)进行聚类,最好是使用分布式进行计算,本人也没有太多经验,仅此稍稍提下。
对于中等规模数据集(数据在十万级左右且特征在百维以上),优先推荐的还是使用sklearn的MiniBatchKMeans,但是有时候类别个数参数调整远比最大距离参数调整来的困难时,自然而然会想到使用基于密度聚类的DBSCAN。
但是在sklearn.cluster.DBSCAN实际的使用过程中,有时会面临因为数据集规模扩大,重新聚类时占用内存过高,导致memory error,从而使程序被kill。本文就是说一说一些可选取的内存优化方案。
PS:仅针对内存相关问题,速度上的比如多进程相关内容不涵盖。
内存占用过高原因
其实这个问题,sklearn官方文档中也给出了对应说明。
This implementation bulk-computes all neighborhood queries, which increases the memory complexity to O(n.d) where d is the average number of neighbors, while original DBSCAN had memory complexity O(n). It may attract a higher memory complexity when querying these nearest neighborhoods, depending on the algorithm.
谷歌翻译:该实现对所有邻居查询进行批量计算,从而将内存复杂度增加到O(n*d),其中d是邻居的平均数量,而原始DBSCAN的内存复杂度为O(n)。根据选取算法的不同,在查询这些最近的邻域时,它可能会吸引更高的内存复杂度。
大概意思是,为了支持批量计算,在算法初期,就需要构建各点间的距离矩阵,此时会引入额外内存。注意这里的文档应该是在0.16版本之后进行优化的,距离矩阵使用稀疏矩阵表示,之前版本的内存复杂度应该是O(n^2)。(对于具体什么版本优化存疑,我只是从gayhub项目下issue中进行推断的,并非查阅提交记录。)
优化方案
方案一
One way to avoid the query complexity is to pre-compute sparse neighborhoods in chunks using NearestNeighbors.radius_neighbors_graph with mode=‘distance’, then using metric=‘precomputed’ here.
谷歌翻译:避免查询复杂性的一种方法是使用NearestNeighbors.radius_neighbors_graph方法并且设置参数mode='distance’预先计算的稀疏矩阵,然后传入并使用参数metric=‘precomputed’。
代码示例(使用余弦距离):
def dbscan(train_data):
"""
聚类
:param train_data: 训练数据np.array[[]]
:return:
"""
neigh = NearestNeighbors(radius=0.5, metric='cosine').fit(X=train_data)
train_x = neigh.radius_neighbors_graph(mode='distance')
cluster = DBSCAN(eps=0.5, min_samples=3, metric='precomputed', n_jobs=1).fit_predict(X=train_x)
个人理解上,这样做最大的好处可以节约原输入矩阵所需内存,尤其是在n_jobs>=2或者n_jobs=-1情况下可以直观感受到内存消耗的减少。
PS:其实也可以直接传入n*n的距离矩阵进行计算,同样metric=‘precomputed’。
方案二
Another way to reduce memory and computation time is to remove (near-)duplicate points and use sample_weight instead.
谷歌翻译:另一种减少内存和计算时间的方法是删除(接近)重复的点并使用sample_weight取代,有几个点就把权重设为几,默认为1。
代码示例:
train_x = np.array([[1,