如何为k近邻分类创建置信度估计的色彩图

0 投票
1 回答
1701 浏览
提问于 2025-04-18 11:12

我想要的:

我想在Python中把我的简单分类算法的结果显示成一个颜色图(数据是二维的),每个类别都有一个对应的颜色,而在二维图上任何地方的预测信心与该类别颜色的饱和度成正比。下面的图片大致展示了我想要的效果,适用于二分类问题,其中红色部分可能表示对类别1的强信心,而蓝色部分则代表类别2。中间的颜色则表示对这两者的不确定性。显然,我希望这个颜色方案能够扩展到多个类别,所以我需要很多颜色,颜色的变化范围从白色(表示不确定)到与某个类别相关的非常丰富的颜色。

示例图片 http://www.nicolacarlon.it/out.png

一些示例代码:

我的示例代码使用了一个简单的kNN算法,允许最近的k个数据点对地图上一个新点的类别进行“投票”。预测的信心简单地通过获胜类别的相对频率来表示,这个频率是从投票的k个数据点中得出的。我还没有处理平局的情况,我知道这个方法还有更好的概率版本,但我只想可视化我的数据,以便向观众展示某个类别在二维平面特定区域的可能性。

import numpy as np
import matplotlib.pyplot as plt


# Generate some training data from three classes
n = 100 # Number of covariates (sample points) for each class in training set. 
mean1, mean2, mean3 = [-1.5,0], [1.5, 0], [0,1.5]
cov1, cov2, cov3 = [[1,0],[0,1]], [[1,0],[0,1]], [[1,0],[0,1]]
X1 = np.asarray(np.random.multivariate_normal(mean1,cov1,n))
X2 = np.asarray(np.random.multivariate_normal(mean2,cov2,n))
X3 = np.asarray(np.random.multivariate_normal(mean3,cov3,n))


plt.plot(X1[:,0], X1[:,1], 'ro', X2[:,0], X2[:,1], 'bo', X3[:,0], X3[:,1], 'go' )

plt.axis('equal'); plt.show() #Display training data


# Prepare the data set as a 3n*3 array where each row is a data point and its associated class
D = np.zeros((3*n,3))
D[0:n,0:2] = X1; D[0:n,2] = 1
D[n:2*n,0:2] = X2; D[n:2*n,2] = 2
D[2*n:3*n,0:2] = X3; D[2*n:3*n,2] = 3

def kNN(x, D, k=3):
    x = np.asarray(x)
    dist = np.linalg.norm(x-D[:,0:2], axis=1)
    i = dist.argsort()[:k] #Return k indices of smallest to highest entries
    counts = np.bincount(D[i,2].astype(int))
    predicted_class = np.argmax(counts) 
    confidence = float(np.max(counts))/k
    return predicted_class, confidence 

print(kNN([-2,0], D, 20))

1 个回答

1

你可以为二维平面上的每个点计算两个数字:

  • 置信度(范围从0到1)
  • 类别(一个整数)

一种方法是自己计算RGB图,并用imshow来显示。像这样:

import numpy as np
import matplotlib.pyplot as plt

# color vector with N x 3 colors, where N is the maximum number of classes and the colors are in RGB
mycolors = np.array([
  [ 0, 0, 1],
  [ 0, 1, 0],
  [ 1, 0, 1],
  [ 1, 1, 0],
  [ 0, 1, 1],
  [ 0, 0, 0],
  [ 0, .5, 1]])

# negate the colors
mycolors = 1 - mycolors 

# extents of the area
x0 = -2
x1 = 2
y0 = -2
y1 = 2

# grid over the area
X, Y = np.meshgrid(np.linspace(x0, x1, 1000), np.linspace(y0, y1, 1000))

# calculate the classification and probabilities
classes = classify_func(X, Y)
probabilities = prob_func(X, Y)

# create the basic color map by the class
img = mycolors[classes]

# fade the color by the probability (black for zero prob)
img *= probabilities[:,:,None]

# reverse the negative image back
img = 1 - img

# draw it
plt.imshow(img, extent=[x0,x1,y0,y1], origin='lower')
plt.axis('equal')

# save it
plt.savefig("mymap.png")

这里使用负颜色的技巧只是为了让数学计算更容易理解。当然,代码可以写得更紧凑。

我创建了两个非常简单的函数来模拟分类和概率:

def classify_func(X, Y):
    return np.round(abs(X+Y)).astype('int')

def prob_func(X,Y):
    return 1 - 2*abs(abs(X+Y)-classify_func(X,Y))

第一个函数给定区域的整数值范围是0到4,第二个函数则给出平滑变化的概率。

结果是:

enter image description here

如果你不喜欢颜色渐变到零概率的方式,你可以创建一些非线性效果,在与概率相乘时应用。


这里的函数classify_funcprob_func接收两个数组作为参数,第一个是要计算的X坐标,第二个是Y坐标。如果底层计算是完全向量化的,这样做效果很好。但在问题中的代码里并不是这样,因为它只计算单个值。

在这种情况下,代码稍微有些变化:

x = np.linspace(x0, x1, 1000)
y = np.linspace(y0, y1, 1000)
classes = np.empty((len(y), len(x)), dtype='int')
probabilities = np.empty((len(y), len(x)))
for yi, yv in enumerate(y):
    for xi, xv in enumerate(x):
    classes[yi, xi], probabilities[yi, xi] = kNN((xv, yv), D)

另外,由于你的置信度估计不是在0到1之间,需要进行缩放:

probabilities -= np.amin(probabilities)
probabilities /= np.amax(probabilities)

完成这些后,你的地图应该看起来像这样,范围是-4到4(根据颜色映射:绿色=1,洋红色=2,黄色=3):

kNN map


要不要向量化 - 这是个问题

这个问题时不时会出现。网上有很多关于向量化的信息,但快速搜索没有找到简短的总结,所以我在这里分享一些想法。这是一个相当主观的事情,所以以下只是我个人的看法,其他人可能有不同的看法。

需要考虑三个因素:

  • 性能
  • 可读性
  • 内存使用

通常(但并不总是)向量化会让代码运行得更快,但理解起来更困难,并且会消耗更多内存。内存使用通常不是大问题,但对于大数组来说需要考虑一下(几百兆通常没问题,几千兆就麻烦了)。

抛开简单的情况(逐元素简单操作,简单矩阵操作),我的做法是:

  • 先写没有向量化的代码,确保它能工作
  • 对代码进行性能分析
  • 如果需要且可能的话,对内部循环进行向量化(1D向量化)
  • 如果简单的话,创建2D向量化

例如,逐像素的图像处理操作可能会导致我最终得到一维向量化(针对每一行)。这样内部循环(针对每个像素)会很快,而外部循环(针对每一行)就不那么重要了。如果代码不试图兼容所有可能的输入维度,可能看起来会简单很多。

我在复杂情况下是个糟糕的算法师,所以我喜欢将我的向量化代码与非向量化版本进行验证。因此,我几乎总是先创建非向量化代码,然后再进行优化。

有时向量化并不会带来性能提升。例如,方便的numpy.vectorize函数可以用来几乎对任何函数进行向量化,但它的文档中指出:

向量化函数主要是为了方便,而不是为了性能。它的实现本质上是一个for循环。

(这个函数在上面的代码中也可以使用。我选择了循环版本是为了让不太熟悉numpy的人更容易理解。)

只有当底层的向量化函数更快时,向量化才会带来更好的性能。有时是这样,有时不是。只有通过性能分析和经验才能知道。此外,并不总是需要对所有内容进行向量化。你可能有一个图像处理算法,其中既有向量化操作,也有逐像素操作。在这种情况下,numpy.vectorize非常有用。

我会尝试至少对上面的kNN搜索算法进行一维向量化。没有条件代码(这不会成为障碍,但会让事情复杂化),而且算法相当简单。内存消耗会增加,但一维向量化时这并不重要。

在这个过程中,你可能会发现n维的推广并没有复杂多少。如果内存允许的话,就去做吧。

撰写回答