使用8个特征绘制k最近邻图?

15

我刚开始学习机器学习,想使用Python库Scikit并采用k最近邻方法建立一个小模型样例。

转换和适配数据很顺利,但我无法弄清楚如何绘制一个显示数据点及其“邻域”的图形。

我使用的数据集看起来像这样:

Head of dataset. 因此,有8个特征,再加上一个“结果”列。

据我所知,使用Scikit的kneighbors_graph方法,可以得到所有数据点的欧几里得距离数组。因此,我的第一次尝试是“简单地”绘制从该方法得到的矩阵。就像这样:

def kneighbors_graph(self):
    self.X_train = self.X_train.values[:10,] #trimming down the data to only 10 entries
    A = neighbors.kneighbors_graph(self.X_train, 9, 'distance')
    plt.spy(A)
    plt.show()
然而,结果图并没有真正可视化数据点之间的预期关系。Result graph with kneighbors_graph - method 因此,我尝试调整每个页面上都可以找到的有关Scikit的Iris_dataset示例。不幸的是,它只使用了两个特征,所以它并不完全符合我的要求,但我仍然想至少获得第一个输出:
  def plot_classification(self):
    h = .02
    n_neighbors = 9
    self.X = self.X.values[:10, [1,4]] #trim values to 10 entries and only columns 2 and 5 (indices 1, 4)
    self.y = self.y[:10, ] #trim outcome column, too

    clf = neighbors.KNeighborsClassifier(n_neighbors, weights='distance')
    clf.fit(self.X, self.y)

    x_min, x_max = self.X[:, 0].min() - 1, self.X[:, 0].max() + 1
    y_min, y_max = self.X[:, 1].min() - 1, self.X[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()]) #no errors here, but it's  not moving on until computer crashes

    cmap_light = ListedColormap(['#FFAAAA', '#AAFFAA','#00AAFF'])
    cmap_bold = ListedColormap(['#FF0000', '#00FF00','#00AAFF'])
    Z = Z.reshape(xx.shape)
    plt.figure()
    plt.pcolormesh(xx, yy, Z, cmap=cmap_light)
    plt.scatter(self.X[:, 0], self.X[:, 1], c=self.y, cmap=cmap_bold)
    plt.xlim(xx.min(), xx.max())
    plt.ylim(yy.min(), yy.max())
    plt.title("Classification (k = %i)" % (n_neighbors))

然而,这段代码根本不起作用,我无法弄清楚原因。它永远不会终止,所以我没有任何错误可供处理。我的计算机在等待几分钟后就会崩溃。

代码正在努力处理的行是Z = clf.predict(np.c_[xx.ravel(), yy.ravel()]) 部分。

所以我的问题是:

首先,我不明白为什么需要使用 fitpredict 来绘制邻居。欧几里得距离难道不足以绘制所需的图形吗?(所需的图形看起来有点像这样:为糖尿病或非糖尿病设有两种颜色;箭头等不必要;图片来源:这个教程)。

desired graph

代码中的错误在哪里 / 为什么predict部分会崩溃?

是否有一种方法可以绘制具有所有特征的数据?我知道我不能有8个轴,但我希望欧几里得距离是使用我的全部8个特征计算的,而不仅仅是其中的两个(只用两个不太准确,对吗?)。

更新

这里是一个可工作的例子,使用了鸢尾花代码,但使用了我的糖尿病数据集:

它使用了我的数据集的前两个特征。我能看到与我的代码唯一的区别是数组被切割 --> 这里取了前两个特征,而我想要的是第2和5个特征,所以我把它切割得不同。但我不明白为什么我的代码不起作用。所以这是工作代码;复制并粘贴它,它将在我之前提供的数据集上运行:

from sklearn.model_selection import train_test_split
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn import neighbors, datasets

diabetes = pd.read_csv('data/diabetes_data.csv')
columns_to_iterate = ['glucose', 'diastolic', 'triceps', 'insulin', 'bmi', 'dpf', 'age']
for column in columns_to_iterate:
    mean_value = diabetes[column].mean(skipna=True)
    diabetes = diabetes.replace({column: {0: mean_value}})
    diabetes[column] = diabetes[column].astype(np.float64)
X = diabetes.drop(columns=['diabetes'])
y = diabetes['diabetes'].values
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,
                                                                        random_state=1, stratify=y)
n_neighbors = 6

X = X.values[:, :2]
y = y
h = .02

cmap_light = ListedColormap(['#FFAAAA', '#AAFFAA', '#00AAFF'])
cmap_bold = ListedColormap(['#FF0000', '#00FF00', '#00AAFF'])

clf = neighbors.KNeighborsClassifier(n_neighbors, weights='distance')
clf.fit(X, y)

x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                     np.arange(y_min, y_max, h))

Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])

Z = Z.reshape(xx.shape)
plt.figure()
plt.pcolormesh(xx, yy, Z, cmap=cmap_light)

plt.scatter(X[:, 0], X[:, 1], c=y, cmap=cmap_bold)
plt.xlim(xx.min(), xx.max())
plt.ylim(yy.min(), yy.max())
plt.title("3-Class classification (k = %i)" % (n_neighbors))
plt.show()

样例代码的输出结果


请阅读并遵守帮助文档中的发布指南,这是您创建此帐户时建议的。最小、完整、可验证的示例 在这里适用。在您发布MCVE代码并准确指定问题之前,我们无法有效地帮助您。我们应该能够将您发布的代码粘贴到一个文本文件中,并重现您指定的问题。 - Prune
话虽如此,但我对你的流程并不完全清楚。如果我正确理解了你的实现,那么“fit”和“predict”过程就是绘图的过程。问题在于你试图将一个8维图形压缩到2维空间中。这需要一个最佳拟合函数,找到2维距离与给定8维距离之间的最小误差。 - Prune
1
一旦这个问题得到澄清,我很乐意讨论一些增强可视化的想法。您是否考虑过使用PCA(主成分分析)来减少空间复杂度? - Prune
1
为了讨论方便,您能否提供一个更简单的例子?我们应该能够使用4D或5D集合来解决这个问题,利用10个点来保持距离矩阵在我们掌握之中。最重要的是要在问题中使用一个完全功能的示例。 - Prune
1
MCVE 的目的是提供能够重现你所遇到问题的代码。你还没有提供这样的代码。你有多个问题;通常在一个 SO 帖子中不会被接受,但是一旦你发布 MCVE,我会尝试解决它们。 - Prune
显示剩余9条评论
2个回答

25

目录:

  1. 特征之间的关系
  2. 所需图形
  3. 为什么要拟合和预测?
  4. 绘制8个特征?

特征之间的关系:

描述特征之间“关系”的科学术语是相关性。在PCA(主成分分析)期间主要研究这个领域。其思想是并不是所有的特征都是重要的,或者至少有一些特征高度相关。将其视为相似性:如果两个特征高度相关,则它们包含相同的信息,因此可以删除其中一个。使用pandas可以像这样实现:

import pandas as pd
import seaborn as sns
from pylab import rcParams
import matplotlib.pyplot as plt


def plot_correlation(data):
    '''
    plot correlation's matrix to explore dependency between features 
    '''
    # init figure size
    rcParams['figure.figsize'] = 15, 20
    fig = plt.figure()
    sns.heatmap(data.corr(), annot=True, fmt=".2f")
    plt.show()
    fig.savefig('corr.png')

# load your data 
data  = pd.read_csv('diabetes.csv')

# plot correlation & densities
plot_correlation(data)

输出如下相关矩阵: enter image description here 因此,1表示总相关性,对角线上的所有元素都是1,因为特征与自身完全相关。此外,数字越小,特征之间的相关性就越小。
在这里,我们需要考虑特征之间的相关性和结果与特征之间的相关性。对于特征之间:较高的相关性意味着我们可以删除其中一个。然而,特征与结果之间的高相关性意味着该特征很重要并且包含大量信息。在我们的图表中,最后一行表示特征与结果之间的相关性。因此,最高值/最重要的特征是“葡萄糖”(0.47)和“MBI”(0.29)。此外,这两者之间的相关性相对较低(0.22),这意味着它们不相似。
我们可以使用每个特征与结果相关性的密度图来验证这些结果。由于我们只有两个结果:0或1,所以代码看起来会像这样:
import pandas as pd
from pylab import rcParams
import matplotlib.pyplot as plt


def plot_densities(data):
    '''
    Plot features densities depending on the outcome values
    '''
    # change fig size to fit all subplots beautifully 
    rcParams['figure.figsize'] = 15, 20

    # separate data based on outcome values 
    outcome_0 = data[data['Outcome'] == 0]
    outcome_1 = data[data['Outcome'] == 1]

    # init figure
    fig, axs = plt.subplots(8, 1)
    fig.suptitle('Features densities for different outcomes 0/1')
    plt.subplots_adjust(left = 0.25, right = 0.9, bottom = 0.1, top = 0.95,
                        wspace = 0.2, hspace = 0.9)

    # plot densities for outcomes
    for column_name in names[:-1]: 
        ax = axs[names.index(column_name)]
        #plt.subplot(4, 2, names.index(column_name) + 1)
        outcome_0[column_name].plot(kind='density', ax=ax, subplots=True, 
                                    sharex=False, color="red", legend=True,
                                    label=column_name + ' for Outcome = 0')
        outcome_1[column_name].plot(kind='density', ax=ax, subplots=True, 
                                     sharex=False, color="green", legend=True,
                                     label=column_name + ' for Outcome = 1')
        ax.set_xlabel(column_name + ' values')
        ax.set_title(column_name + ' density')
        ax.grid('on')
    plt.show()
    fig.savefig('densities.png')

# load your data 
data  = pd.read_csv('diabetes.csv')
names = list(data.columns)

# plot correlation & densities
plot_densities(data)

以下是密度图的输出: enter image description here 在这些图中,当绿色和红色曲线几乎相同(重叠)时,这意味着该特征无法区分结果。在“BMI”情况下,您可以看到一些分离(两个曲线之间轻微的水平移动),而在“葡萄糖”情况下,这更加明显(与相关性值一致)。
=>结论:如果我们只能选择两个特征,则应选择“葡萄糖”和“MBI”。
所需图形
我没有太多要说的,除了该图表示k最近邻概念的基本解释。它仅仅不是分类的表示。
为什么要拟合和预测
这是一个基础而重要的机器学习(ML)概念。你有一个数据集=[输入,相关输出],希望构建一个ML算法,能够学习将输入与其相关输出联系起来。这是一个两步过程。首先,你需要训练/教你的算法如何完成任务。在此阶段,你只需像对待孩子一样提供输入和答案即可。第二步是测试;现在孩子已经学会了,你想要测试她/他。所以你给她/他类似的输入,并检查她/他的答案是否正确。现在,你不想给她/他相同的输入,因为即使她/他给出正确的答案,她/他可能只是从学习阶段记住了答案(称为过度拟合),所以她/他什么也没学到。
类似地,你可以使用算法,首先将数据集分成训练数据和测试数据。然后将训练数据适应到你的算法或分类器中。这被称为训练阶段。之后,你测试你的分类器的好坏,看它是否能正确分类��数据。这是测试阶段。根据测试结果,你可以使用不同的评价指标,比如准确度,来评估你的分类性能。经验规则是使用2/3的数据进行训练,1/3进行测试。 绘制8个特征? 简单的回答是不行的,如果可以,请告诉我如何做到。

有趣的回答:要想将8维可视化,很简单...只需想象n维,然后让n=8或者直接想象三维并大喊“8”。

逻辑回答:我们生活在物理世界中,看到的物体是三维的,这在技术上就是一种限制。但是,你可以将第四个维度视为颜色,就像这里,你也可以将时间作为第五个维度,并使你的图形成为动画。@Rohan建议使用形状,但他的代码对我无效,而且我不认为这能很好地表示算法性能。无论如何,颜色、时间、形状......过了一段时间你会发现自己陷入了困境。这是人们进行PCA的原因之一。您可以在降维下阅读有关该问题方面的内容。

那么,如果我们在PCA之后只使用2个特征,然后进行训练、测试、评估和绘图,会发生什么?

那么,你可以使用以下代码来实现:

import warnings 
import numpy as np
import pandas as pd
from pylab import rcParams
import matplotlib.pyplot as plt
from sklearn import neighbors
from matplotlib.colors import ListedColormap
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
# filter warnings
warnings.filterwarnings("ignore")

def accuracy(k, X_train, y_train, X_test, y_test):
    '''
    compute accuracy of the classification based on k values 
    '''
    # instantiate learning model and fit data
    knn = KNeighborsClassifier(n_neighbors=k)    
    knn.fit(X_train, y_train)

    # predict the response
    pred = knn.predict(X_test)

    # evaluate and return  accuracy
    return accuracy_score(y_test, pred)

def classify_and_plot(X, y):
    ''' 
    split data, fit, classify, plot and evaluate results 
    '''
    # split data into training and testing set
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.33, random_state = 41)

    # init vars
    n_neighbors = 5
    h           = .02  # step size in the mesh

    # Create color maps
    cmap_light = ListedColormap(['#FFAAAA', '#AAAAFF'])
    cmap_bold  = ListedColormap(['#FF0000', '#0000FF'])

    rcParams['figure.figsize'] = 5, 5
    for weights in ['uniform', 'distance']:
        # we create an instance of Neighbours Classifier and fit the data.
        clf = neighbors.KNeighborsClassifier(n_neighbors, weights=weights)
        clf.fit(X_train, y_train)

        # Plot the decision boundary. For that, we will assign a color to each
        # point in the mesh [x_min, x_max]x[y_min, y_max].
        x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
        y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
        xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                             np.arange(y_min, y_max, h))
        Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])

        # Put the result into a color plot
        Z = Z.reshape(xx.shape)
        fig = plt.figure()
        plt.pcolormesh(xx, yy, Z, cmap=cmap_light)

        # Plot also the training points, x-axis = 'Glucose', y-axis = "BMI"
        plt.scatter(X[:, 0], X[:, 1], c=y, cmap=cmap_bold, edgecolor='k', s=20)   
        plt.xlim(xx.min(), xx.max())
        plt.ylim(yy.min(), yy.max())
        plt.title("0/1 outcome classification (k = %i, weights = '%s')" % (n_neighbors, weights))
        plt.show()
        fig.savefig(weights +'.png')

        # evaluate
        y_expected  = y_test
        y_predicted = clf.predict(X_test)

        # print results
        print('----------------------------------------------------------------------')
        print('Classification report')
        print('----------------------------------------------------------------------')
        print('\n', classification_report(y_expected, y_predicted))
        print('----------------------------------------------------------------------')
        print('Accuracy = %5s' % round(accuracy(n_neighbors, X_train, y_train, X_test, y_test), 3))
        print('----------------------------------------------------------------------')


# load your data 
data  = pd.read_csv('diabetes.csv')
names = list(data.columns)

# we only take the best two features and prepare them for the KNN classifier
rows_nbr = 30 # data.shape[0]
X_prime  = np.array(data.iloc[:rows_nbr, [1,5]])
X        = X_prime # preprocessing.scale(X_prime)
y        = np.array(data.iloc[:rows_nbr, 8])

# classify, evaluate and plot results
classify_and_plot(X, y)

这导致使用weights ='uniform'和weights ='distance'的决策边界的以下图表(要了解两者之间的差异,请阅读此处):

enter image description here enter image description here

注意: x轴='葡萄糖',y轴='BMI'

改进:

K值应该使用什么k值?考虑多少个邻居。低的k值意味着数据之间的依赖性较小,但大的值意味着运行时间更长。因此需要权衡。您可以使用此代码找到导致最高精度的k值:

best_n_neighbours = np.argmax(np.array([accuracy(k, X_train, y_train, X_test, y_test) for k in range(1, int(rows_nbr/2))])) + 1
print('For best accuracy use k = ', best_n_neighbours)

使用更多数据 当使用所有数据时,您可能会遇到内存问题(就像我一样),除了过度拟合的问题外。您可以通过预处理数据来克服这个问题。将其视为缩放和格式化数据。在代码中只需使用:

from sklearn import preprocessing 
X = preprocessing.scale(X_prime)

完整的代码可以在这个gist中找到。


哇,谢谢您的精彩解释!我对这个话题有了更深入的理解!非常感谢!所以,您说“它只是不是一个分类的表示。”那么我应该使用什么样的图表呢?在我的研究中,我总是遇到这种类型的图表,所以我想使用它。但如果可能的话,当然我会使用更好的。再次感谢! - sonja
很高兴能帮到你。在我看来,决策边界图(最后两张图)是你的分类的良好表示。蓝色代表结果1,红色代表结果0。从图中,你可以直接读取分类器将预测的结果(离边界越远,读取的结果越正确)。例如,在最后一张图中,你可以清楚地看到,任何血糖>180的人都会得到结果=1,任何BMI在[10,30]和血糖在[140,150]之间的人都将得到结果0,依此类推... - SuperKogito
由于某些原因,我无法弄清楚,“预测”部分仍在崩溃。或者没有崩溃,但当它到达那一行时,代码就不会继续执行。我已经尝试了仅使用数据集的10行。你认为这是我的笔记本电脑的问题吗? - sonja
让我们在聊天室里继续交流:https://chat.stackoverflow.com/rooms/194100/room-for-superkogito-and-sonja - SuperKogito
3
自从我加入这个社区以来,在stackoverflow上读过的最全面、最令人愉快的回答之一。你应该多发帖。 - Gabe Verzino

6
尝试这两个简单的代码片段,都会绘制一个包含6个变量的三维图形。绘制高维数据始终很困难,但您可以尝试调整它并检查是否可以将其调整为所需的相邻图形。第一个代码比较直观,但它会给出随机的光线或盒子(取决于变量的数量)。您不能绘制超过6个变量,否则会一直抛出错误。但是,您必须足够有创意地使用其他两个变量。当您看到第二个代码片段时,这将变得有意义。
第一个代码片段:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
X, Y, Z, U, V, W = zip(*df)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.quiver(X, Y, Z, U, V, W)    
ax.set_xlim([-2, 2])
ax.set_ylim([-2, 2])
ax.set_zlim([-2, 2])
ax.legend()
plt.show()

第二段代码

在这里,我使用年龄和BMI作为数据点的颜色和形状,你可以通过调整此代码获取6个变量的邻域图,并使用其他两个变量来区分颜色或形状。

fig = plt.figure(figsize=(8, 6))
t = fig.suptitle('name_of_your_graph', fontsize=14)
ax = fig.add_subplot(111, projection='3d')

xs = list(df['pregnancies'])
ys = list(df['glucose'])
zs = list(df['bloodPressure'])
data_points = [(x, y, z) for x, y, z in zip(xs, ys, zs)]

ss = list(df['skinThickness'])
colors = ['red' if age_group in range(0,35) else 'yellow' for age_group in list(df['age'])]
markers = [',' if q > 33 else 'x' if q in range(19,32) else 'o' for q in list(df['BMI'])]

for data, color, size, mark in zip(data_points, colors, ss, markers):
    x, y, z = data
    ax.scatter(x, y, z, alpha=0.4, c=color, edgecolors='none', s=size, marker=mark)

ax.set_xlabel('pregnancies')
ax.set_ylabel('glucose')
ax.set_zlabel('bloodPressure')

请发布您的答案。我正在解决一个类似的问题,可能会有所帮助。如果您无法绘制所有8个维度,则还可以通过每次使用6个不同变量的组合来绘制多个邻域图。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接