如何为k近邻分类创建置信度估计的色彩图
我想要的:
我想在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 个回答
你可以为二维平面上的每个点计算两个数字:
- 置信度(范围从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,第二个函数则给出平滑变化的概率。
结果是:
如果你不喜欢颜色渐变到零概率的方式,你可以创建一些非线性效果,在与概率相乘时应用。
这里的函数classify_func
和prob_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):
要不要向量化 - 这是个问题
这个问题时不时会出现。网上有很多关于向量化的信息,但快速搜索没有找到简短的总结,所以我在这里分享一些想法。这是一个相当主观的事情,所以以下只是我个人的看法,其他人可能有不同的看法。
需要考虑三个因素:
- 性能
- 可读性
- 内存使用
通常(但并不总是)向量化会让代码运行得更快,但理解起来更困难,并且会消耗更多内存。内存使用通常不是大问题,但对于大数组来说需要考虑一下(几百兆通常没问题,几千兆就麻烦了)。
抛开简单的情况(逐元素简单操作,简单矩阵操作),我的做法是:
- 先写没有向量化的代码,确保它能工作
- 对代码进行性能分析
- 如果需要且可能的话,对内部循环进行向量化(1D向量化)
- 如果简单的话,创建2D向量化
例如,逐像素的图像处理操作可能会导致我最终得到一维向量化(针对每一行)。这样内部循环(针对每个像素)会很快,而外部循环(针对每一行)就不那么重要了。如果代码不试图兼容所有可能的输入维度,可能看起来会简单很多。
我在复杂情况下是个糟糕的算法师,所以我喜欢将我的向量化代码与非向量化版本进行验证。因此,我几乎总是先创建非向量化代码,然后再进行优化。
有时向量化并不会带来性能提升。例如,方便的numpy.vectorize
函数可以用来几乎对任何函数进行向量化,但它的文档中指出:
向量化函数主要是为了方便,而不是为了性能。它的实现本质上是一个for循环。
(这个函数在上面的代码中也可以使用。我选择了循环版本是为了让不太熟悉numpy
的人更容易理解。)
只有当底层的向量化函数更快时,向量化才会带来更好的性能。有时是这样,有时不是。只有通过性能分析和经验才能知道。此外,并不总是需要对所有内容进行向量化。你可能有一个图像处理算法,其中既有向量化操作,也有逐像素操作。在这种情况下,numpy.vectorize
非常有用。
我会尝试至少对上面的kNN搜索算法进行一维向量化。没有条件代码(这不会成为障碍,但会让事情复杂化),而且算法相当简单。内存消耗会增加,但一维向量化时这并不重要。
在这个过程中,你可能会发现n维的推广并没有复杂多少。如果内存允许的话,就去做吧。