重复试验
在本节中,我们将在𝑘= 2到𝑘= 20之间重复此步骤:
- 执行k-means以获取每个像素的聚类中心和聚类标签
- 将每个像素替换为其聚类中心。
- 保存指标值以进行进一步优化:WCSS,BCSS,解释方差和图像大小
- 用越来越多的颜色绘制压缩图像
range_k_clusters = (2, 21) kmeans_result = [] for k in range(*range_k_clusters): # CLUSTERING kmeans = KMeans(n_clusters = k, n_jobs = -1, random_state = 123).fit(X) # REPLACE PIXELS WITH ITS CENTROID new_pixels = replaceWithCentroid(kmeans) # EVALUATE WCSS = kmeans.inertia_ BCSS = calculateBCSS(X, kmeans) exp_var = 100*BCSS/(WCSS + BCSS) metric = { "No. of Colors": k, "Centroids": list(map(get_colour_name, np.uint8(kmeans.cluster_centers_))), "Pixels": new_pixels, "WCSS": WCSS, "BCSS": BCSS, "Explained Variance": exp_var, "Image Size (KB)": imageByteSize(new_pixels) } kmeans_result.append(metric) kmeans_result = pd.DataFrame(kmeans_result).set_index("No. of Colors") kmeans_result
聚类指标:最佳的颜色种类数
在本节中,我们将尝试搜索最佳的颜色数(聚类中心)k,以便在保持较高的解释方差百分比的同时将内存大小减小到尽可能小。
如何确定最佳颜色数k?以下是算法:
- 用直线连接曲线的第一个和最后一个点
- 计算每个点到该线的垂直距离
- 将距离最长的点视为拐点
下一个问题,如何在步骤2中计算垂直距离?很简单,我们可以使用从点(x0,y0)到线ax + by + c = 0的距离公式,如下所示:
def locateOptimalElbow(x, y): # START AND FINAL POINTS p1 = (x[0], y[0]) p2 = (x[-1], y[-1]) # EQUATION OF LINE: y = mx + c m = (p2[1] - p1[1]) / (p2[0] - p1[0]) c = (p2[1] - (m * p2[0])) # DISTANCE FROM EACH POINTS TO LINE mx - y + c = 0 a, b = m, -1 dist = np.array([abs(a*x0+b*y0+c)/math.sqrt(a**2+b**2) for x0, y0 in zip(x,y)]) return np.argmax(dist) + x[0]
但是,如果图形不是增加或减少的曲线函数,该怎么办?我们可以使用有限差分法使用二阶导数来定位梯度中变化最剧烈的地方。
什么是有限差分法?这是一种数值方法,可以近似离散值的导数。共有三种类型:
forward差异:
backward差异:
中心差异:
其中:
f’(x)是函数f(x)的一阶导数h是步长,在这种情况下,h = 1(颜色数的步长)O(h)是一级误差项O(h²)是二次误差项
由于中心差异具有较高的度数误差项,因此预期它会比其他两个差异产生更好的结果。我们仅对第一个点使用前向差异,对最后一个点使用后向差异。
def calculateDerivative(data): derivative = [] for i in range(len(data)): if i == 0: # FORWARD DIFFERENCE d = data[i+1] - data[i] elif i == len(data) - 1: # BACKWARD DIFFERENCE d = data[i] - data[i-1] else: # CENTER DIFFERENCE d = (data[i+1] - data[i-1])/2 derivative.append(d) return np.array(derivative) def locateDrasticChange(x, y): # CALCULATE GRADIENT BY FIRST DERIVATIVE first_derivative = calculateDerivative(np.array(y)) # CALCULATE CHANGE OF GRADIENT BY SECOND DERIVATIVE second_derivative = calculateDerivative(first_derivative) return np.argmax(np.abs(second_derivative)) + x[0]
让我们搜索每个指标的最佳k值:
optimal_k = [] for col in kmeans_result.columns[2:]: optimal_k_dict = {} optimal_k_dict["Metric"] = col if col == "Image Size (KB)": optimal_k_dict["Method"] = "Derivative" optimal_k_dict["Optimal k"] = locateDrasticChange(kmeans_result.index, kmeans_result[col].values) else: optimal_k_dict["Method"] = "Elbow" optimal_k_dict["Optimal k"] = locateOptimalElbow(kmeans_result.index, kmeans_result[col].values) optimal_k.append(optimal_k_dict) optimal_k = pd.DataFrame(optimal_k) optimal_k
我们选择最大的最优k作为所有最优k的代表,即k = 12。