|
|
- {
- "cells": [
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "from google.colab import drive\n",
- "\n",
- "drive.mount('/content/drive', force_remount=True)\n",
- "\n",
- "# 输入daseCV所在的路径\n",
- "# 'daseCV' 文件夹包括 '.py', 'classifiers' 和'datasets'文件夹\n",
- "# 例如 'CV/assignments/assignment1/daseCV/'\n",
- "FOLDERNAME = None\n",
- "\n",
- "assert FOLDERNAME is not None, \"[!] Enter the foldername.\"\n",
- "\n",
- "%cd drive/My\\ Drive\n",
- "%cp -r $FOLDERNAME ../../\n",
- "%cd ../../\n",
- "%cd daseCV/datasets/\n",
- "!bash get_datasets.sh\n",
- "%cd ../../"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "tags": [
- "pdf-title"
- ]
- },
- "source": [
- "# K-近邻算法 (kNN) 练习\n",
- "\n",
- "*补充并完成本练习。*\n",
- "\n",
- "kNN分类器包含两个阶段:\n",
- "\n",
- "- 训练阶段,分类器获取训练数据并简单地记住它。\n",
- "- 测试阶段, kNN将测试图像与所有训练图像进行比较,并计算出前k个最相似的训练示例的标签来对每个测试图像进行分类。\n",
- "- 对k值进行交叉验证\n",
- "\n",
- "在本练习中,您将实现这些步骤,并了解基本的图像分类、交叉验证和熟练编写高效矢量化代码的能力。"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "tags": [
- "pdf-ignore"
- ]
- },
- "outputs": [],
- "source": [
- "# 运行notebook的一些初始化代码\n",
- "\n",
- "import random\n",
- "import numpy as np\n",
- "from daseCV.data_utils import load_CIFAR10\n",
- "import matplotlib.pyplot as plt\n",
- "\n",
- "# 使得matplotlib的图像在当前页显示而不是新的窗口。\n",
- "%matplotlib inline\n",
- "plt.rcParams['figure.figsize'] = (10.0, 8.0) # set default size of plots\n",
- "plt.rcParams['image.interpolation'] = 'nearest'\n",
- "plt.rcParams['image.cmap'] = 'gray'\n",
- "\n",
- "# 一些更神奇的,使notebook重新加载外部的python模块;\n",
- "# 参见 http://stackoverflow.com/questions/1907993/autoreload-of-modules-in-ipython\n",
- "%load_ext autoreload\n",
- "%autoreload 2"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "tags": [
- "pdf-ignore"
- ]
- },
- "outputs": [],
- "source": [
- "# 加载未处理的 CIFAR-10 数据.\n",
- "cifar10_dir = 'daseCV/datasets/cifar-10-batches-py'\n",
- "\n",
- "# 清理变量以防止多次加载数据(这可能会导致内存问题)\n",
- "try:\n",
- " del X_train, y_train\n",
- " del X_test, y_test\n",
- " print('Clear previously loaded data.')\n",
- "except:\n",
- " pass\n",
- "\n",
- "X_train, y_train, X_test, y_test = load_CIFAR10(cifar10_dir)\n",
- "\n",
- "# 作为健全性检查,我们打印出训练和测试数据的形状。\n",
- "print('Training data shape: ', X_train.shape)\n",
- "print('Training labels shape: ', y_train.shape)\n",
- "print('Test data shape: ', X_test.shape)\n",
- "print('Test labels shape: ', y_test.shape)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "tags": [
- "pdf-ignore"
- ]
- },
- "outputs": [],
- "source": [
- "# 可视化数据集中的一些示例。\n",
- "# 我们展示了训练图像的所有类别的一些示例。\n",
- "classes = ['plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']\n",
- "num_classes = len(classes)\n",
- "samples_per_class = 7\n",
- "for y, cls in enumerate(classes):\n",
- " idxs = np.flatnonzero(y_train == y) # flatnonzero表示返回所给数列的非零项的索引值,这里表示返回所有属于y类的索引\n",
- " idxs = np.random.choice(idxs, samples_per_class, replace=False) # replace表示抽取的样本是否能重复\n",
- " for i, idx in enumerate(idxs):\n",
- " plt_idx = i * num_classes + y + 1\n",
- " plt.subplot(samples_per_class, num_classes, plt_idx)\n",
- " plt.imshow(X_train[idx].astype('uint8'))\n",
- " plt.axis('off')\n",
- " if i == 0:\n",
- " plt.title(cls)\n",
- "plt.show()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "tags": [
- "pdf-ignore"
- ]
- },
- "outputs": [],
- "source": [
- "# 在练习中使用更小的子样本可以提高代码的效率\n",
- "num_training = 5000\n",
- "mask = list(range(num_training))\n",
- "X_train = X_train[mask]\n",
- "y_train = y_train[mask]\n",
- "\n",
- "num_test = 500\n",
- "mask = list(range(num_test))\n",
- "X_test = X_test[mask]\n",
- "y_test = y_test[mask]\n",
- "\n",
- "# 将图像数据调整为行\n",
- "X_train = np.reshape(X_train, (X_train.shape[0], -1))\n",
- "X_test = np.reshape(X_test, (X_test.shape[0], -1))\n",
- "print(X_train.shape, X_test.shape)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "tags": [
- "pdf-ignore"
- ]
- },
- "outputs": [],
- "source": [
- "from daseCV.classifiers import KNearestNeighbor\n",
- "\n",
- "# 创建一个kNN分类器实例。\n",
- "# 请记住,kNN分类器的训练并不会做什么: \n",
- "# 分类器仅记住数据并且不做进一步处理\n",
- "classifier = KNearestNeighbor()\n",
- "classifier.train(X_train, y_train)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "现在,我们要使用kNN分类器对测试数据进行分类。回想一下,我们可以将该过程分为两个步骤: \n",
- "\n",
- "1. 首先,我们必须计算所有测试样本与所有训练样本之间的距离。 \n",
- "2. 给定这些距离,对于每个测试示例,我们找到k个最接近的示例,并让它们对标签进行投票\n",
- "\n",
- "让我们开始计算所有训练和测试示例之间的距离矩阵。 假设有 **Ntr** 的训练样本和 **Nte** 的测试样本, 该过程的结果存储在一个 **Nte x Ntr** 矩阵中,其中每个元素 (i,j) 表示的是第 i 个测试样本和第 j 个 训练样本的距离。\n",
- "\n",
- "**注意: 在完成此notebook中的三个距离的计算时请不要使用numpy提供的np.linalg.norm()函数。**\n",
- "\n",
- "首先打开 `daseCV/classifiers/k_nearest_neighbor.py` 并且补充完成函数 `compute_distances_two_loops` ,这个函数使用双重循环(效率十分低下)来计算距离矩阵。"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# 打开 daseCV/classifiers/k_nearest_neighbor.py 并且补充完成\n",
- "# compute_distances_two_loops.\n",
- "\n",
- "# 测试你的代码:\n",
- "dists = classifier.compute_distances_two_loops(X_test)\n",
- "print(dists.shape)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# 我们可视化距离矩阵:每行代表一个测试样本与训练样本的距离\n",
- "plt.imshow(dists, interpolation='none')\n",
- "plt.show()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "tags": [
- "pdf-inline"
- ]
- },
- "source": [
- "**问题 1** \n",
- "\n",
- "请注意距离矩阵中的结构化图案,其中某些行或列的可见亮度更高。(请注意,使用默认的配色方案,黑色表示低距离,而白色表示高距离。)\n",
- "\n",
- "- 数据中导致行亮度更高的原因是什么?\n",
- "- 那列方向的是什么原因呢?\n",
- "\n",
- "$\\color{blue}{\\textit 答:}$ *在这里做出回答*\n",
- "\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# 现在实现函数predict_labels并运行以下代码:\n",
- "# 我们使用k = 1(这是最近的邻居)。\n",
- "y_test_pred = classifier.predict_labels(dists, k=1)\n",
- "\n",
- "# 计算并打印出预测的精度\n",
- "num_correct = np.sum(y_test_pred == y_test)\n",
- "accuracy = float(num_correct) / num_test\n",
- "print('Got %d / %d correct => accuracy: %f' % (num_correct, num_test, accuracy))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "你预期的精度应该为 `27%` 左右。 现在让我们尝试更大的 `k`, 比如 `k = 5`:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "y_test_pred = classifier.predict_labels(dists, k=5)\n",
- "num_correct = np.sum(y_test_pred == y_test)\n",
- "accuracy = float(num_correct) / num_test\n",
- "print('Got %d / %d correct => accuracy: %f' % (num_correct, num_test, accuracy))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "你应该能看到一个比 `k = 1` 稍微好一点的结果。"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "tags": [
- "pdf-inline"
- ]
- },
- "source": [
- "**问题 2**\n",
- "\n",
- "我们还可以使用其他距离指标,例如L1距离。\n",
- "\n",
- "记图像 $I_k$ 的每个位置 $(i,j)$ 的像素值为 $p_{ij}^{(k)}$,\n",
- "\n",
- "所有图像上的所有像素的均值 $\\mu$ 为 \n",
- "\n",
- "$$\\mu=\\frac{1}{nhw}\\sum_{k=1}^n\\sum_{i=1}^{h}\\sum_{j=1}^{w}p_{ij}^{(k)}$$\n",
- "\n",
- "并且所有图像的每个像素的均值 $\\mu_{ij}$ 为\n",
- "\n",
- "$$\\mu_{ij}=\\frac{1}{n}\\sum_{k=1}^np_{ij}^{(k)}.$$\n",
- "\n",
- "标准差 $\\sigma$ 以及每个像素的标准差 $\\sigma_{ij}$ 的定义与之类似。\n",
- "\n",
- "以下哪个预处理步骤不会改变使用L1距离的最近邻分类器的效果?选择所有符合条件的答案。\n",
- "1. 减去均值 $\\mu$ ($\\tilde{p}_{ij}^{(k)}=p_{ij}^{(k)}-\\mu$.)\n",
- "2. 减去每个像素均值 $\\mu_{ij}$ ($\\tilde{p}_{ij}^{(k)}=p_{ij}^{(k)}-\\mu_{ij}$.)\n",
- "3. 减去均值 $\\mu$ 然后除以标准偏差 $\\sigma$.\n",
- "4. 减去每个像素均值 $\\mu_{ij}$ 并除以每个素标准差 $\\sigma_{ij}$.\n",
- "5. 旋转数据的坐标轴。\n",
- "\n",
- "$\\color{blue}{\\textit 你的回答:}$\n",
- "\n",
- "\n",
- "$\\color{blue}{\\textit 你的解释:}$\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "tags": [
- "pdf-ignore-input"
- ]
- },
- "outputs": [],
- "source": [
- "# 现在,通过部分矢量化并且使用单层循环的来加快距离矩阵的计算。\n",
- "# 需要实现函数compute_distances_one_loop并运行以下代码:\n",
- "\n",
- "dists_one = classifier.compute_distances_one_loop(X_test)\n",
- "\n",
- "# 为了确保我们的矢量化实现正确,我们要保证它的结果与最原始的实现方式结果一致。\n",
- "# 有很多方法可以确定两个矩阵是否相似。最简单的方法之一就是Frobenius范数。 \n",
- "# 如果您以前从未了解过Frobenius范数,它其实是两个矩阵的所有元素之差的平方和的平方根;\n",
- "# 换句话说,就是将矩阵重整为向量并计算它们之间的欧几里得距离。\n",
- "\n",
- "difference = np.linalg.norm(dists - dists_one, ord='fro')\n",
- "print('One loop difference was: %f' % (difference, ))\n",
- "if difference < 0.001:\n",
- " print('Good! The distance matrices are the same')\n",
- "else:\n",
- " print('Uh-oh! The distance matrices are different')"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "scrolled": true,
- "tags": [
- "pdf-ignore-input"
- ]
- },
- "outputs": [],
- "source": [
- "# 现在完成compute_distances_no_loops实现完全矢量化的版本并运行代码\n",
- "dists_two = classifier.compute_distances_no_loops(X_test)\n",
- "\n",
- "# 检查距离矩阵是否与我们之前计算出的矩阵一致:\n",
- "difference = np.linalg.norm(dists - dists_two, ord='fro')\n",
- "print('No loop difference was: %f' % (difference, ))\n",
- "if difference < 0.001:\n",
- " print('Good! The distance matrices are the same')\n",
- "else:\n",
- " print('Uh-oh! The distance matrices are different')"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "tags": [
- "pdf-ignore-input"
- ]
- },
- "outputs": [],
- "source": [
- "# 让我们比较一下三种实现方式的速度\n",
- "def time_function(f, *args):\n",
- " \"\"\"\n",
- " Call a function f with args and return the time (in seconds) that it took to execute.\n",
- " \"\"\"\n",
- " import time\n",
- " tic = time.time()\n",
- " f(*args)\n",
- " toc = time.time()\n",
- " return toc - tic\n",
- "\n",
- "two_loop_time = time_function(classifier.compute_distances_two_loops, X_test)\n",
- "print('Two loop version took %f seconds' % two_loop_time)\n",
- "\n",
- "one_loop_time = time_function(classifier.compute_distances_one_loop, X_test)\n",
- "print('One loop version took %f seconds' % one_loop_time)\n",
- "\n",
- "no_loop_time = time_function(classifier.compute_distances_no_loops, X_test)\n",
- "print('No loop version took %f seconds' % no_loop_time)\n",
- "\n",
- "# 你应该会看到使用完全矢量化的实现会有明显更佳的性能!\n",
- "\n",
- "# 注意:在部分计算机上,当您从两层循环转到单层循环时,\n",
- "# 您可能看不到速度的提升,甚至可能会看到速度变慢。"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### 交叉验证\n",
- "\n",
- "我们已经实现了kNN分类器,并且可以设置k = 5。现在,将通过交叉验证来确定此超参数的最佳值。"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "tags": [
- "code"
- ]
- },
- "outputs": [],
- "source": [
- "num_folds = 5\n",
- "k_choices = [1, 3, 5, 8, 10, 12, 15, 20, 50, 100]\n",
- "\n",
- "X_train_folds = []\n",
- "y_train_folds = []\n",
- "################################################################################\n",
- "# 需要完成的事情: \n",
- "# 将训练数据分成多个部分。拆分后,X_train_folds和y_train_folds均应为长度为num_folds的列表,\n",
- "# 其中y_train_folds [i]是X_train_folds [i]中各点的标签向量。\n",
- "# 提示:查阅numpy的array_split函数。 \n",
- "################################################################################\n",
- "# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****\n",
- "\n",
- "pass\n",
- "\n",
- "# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****\n",
- "\n",
- "# A dictionary holding the accuracies for different values of k that we find when running cross-validation.\n",
- "# 一个字典,存储我们进行交叉验证时不同k的值的精度。\n",
- "# 运行交叉验证后,k_to_accuracies[k]应该是长度为num_folds的列表,存储了k值下的精度值。\n",
- "k_to_accuracies = {}\n",
- "\n",
- "\n",
- "################################################################################\n",
- "# 需要完成的事情: \n",
- "# 执行k的交叉验证,以找到k的最佳值。\n",
- "# 对于每个可能的k值,运行k-最近邻算法 num_folds 次,\n",
- "# 在每次循环下,你都会用所有拆分的数据(除了其中一个需要作为验证集)作为训练数据。\n",
- "# 然后存储所有的精度结果到k_to_accuracies[k]中。 \n",
- "################################################################################\n",
- "# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****\n",
- "\n",
- "# 交叉验证。有时候,训练集数量较小(因此验证集的数量更小),人们会使用一种被称为\n",
- "# 交叉验证的方法,这种方法更加复杂些。还是用刚才的例子,如果是交叉验证集,我们就\n",
- "# 不是取1000个图像,而是将训练集平均分成5份,其中4份用来训练,1份用来验证。然后\n",
- "# 我们循环着取其中4份来训练,其中1份来验证,最后取所有5次验证结果的平均值作为算\n",
- "# 法验证结果。\n",
- "\n",
- "for k in k_choices:\n",
- " k_to_accuracies[k] = []\n",
- " for i in range(num_folds):\n",
- " # prepare training data for the current fold\n",
- " X_train_fold = np.concatenate([ fold for j, fold in enumerate(X_train_folds) if i != j ])\n",
- " y_train_fold = np.concatenate([ fold for j, fold in enumerate(y_train_folds) if i != j ])\n",
- " \n",
- " # use of k-nearest-neighbor algorithm\n",
- " classifier.train(X_train_fold, y_train_fold)\n",
- " y_pred_fold = classifier.predict(X_train_folds[i], k=k, num_loops=0)\n",
- "\n",
- " # Compute the fraction of correctly predicted examples\n",
- " num_correct = np.sum(y_pred_fold == y_train_folds[i])\n",
- " accuracy = float(num_correct) / X_train_folds[i].shape[0]\n",
- " k_to_accuracies[k].append(accuracy)\n",
- "\n",
- "# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****\n",
- "\n",
- "# 打印出计算的精度\n",
- "for k in sorted(k_to_accuracies):\n",
- " for accuracy in k_to_accuracies[k]:\n",
- " print('k = %d, accuracy = %f' % (k, accuracy))"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "tags": [
- "pdf-ignore-input"
- ]
- },
- "outputs": [],
- "source": [
- "# 绘制原始观察结果\n",
- "for k in k_choices:\n",
- " accuracies = k_to_accuracies[k]\n",
- " plt.scatter([k] * len(accuracies), accuracies)\n",
- "\n",
- "# 用与标准偏差相对应的误差线绘制趋势线\n",
- "accuracies_mean = np.array([np.mean(v) for k,v in sorted(k_to_accuracies.items())])\n",
- "accuracies_std = np.array([np.std(v) for k,v in sorted(k_to_accuracies.items())])\n",
- "plt.errorbar(k_choices, accuracies_mean, yerr=accuracies_std)\n",
- "plt.title('Cross-validation on k')\n",
- "plt.xlabel('k')\n",
- "plt.ylabel('Cross-validation accuracy')\n",
- "plt.show()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# 根据上述交叉验证结果,为k选择最佳值,使用所有训练数据重新训练分类器,\n",
- "# 并在测试中对其进行测试数据。您应该能够在测试数据上获得28%以上的准确性。\n",
- "\n",
- "best_k = k_choices[accuracies_mean.argmax()]\n",
- "\n",
- "classifier = KNearestNeighbor()\n",
- "classifier.train(X_train, y_train)\n",
- "y_test_pred = classifier.predict(X_test, k=best_k)\n",
- "\n",
- "# Compute and display the accuracy\n",
- "num_correct = np.sum(y_test_pred == y_test)\n",
- "accuracy = float(num_correct) / num_test\n",
- "print('Got %d / %d correct => accuracy: %f' % (num_correct, num_test, accuracy))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "tags": [
- "pdf-inline"
- ]
- },
- "source": [
- "**问题 3**\n",
- "\n",
- "下列关于$k$-NN的陈述中哪些是在分类器中正确的设置,并且对所有的$k$都有效?选择所有符合条件的选项。\n",
- "\n",
- "1. k-NN分类器的决策边界是线性的。\n",
- "2. 1-NN的训练误差将始终低于5-NN。\n",
- "3. 1-NN的测试误差将始终低于5-NN。\n",
- "4. 使用k-NN分类器对测试示例进行分类所需的时间随训练集的大小而增加。\n",
- "5. 以上都不是。\n",
- "\n",
- "$\\color{blue}{\\textit 你的回答:}$\n",
- "\n",
- "\n",
- "$\\color{blue}{\\textit 你的解释:}$\n",
- "\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "---\n",
- "# 重要\n",
- "\n",
- "这里是作业的结尾处,请执行以下步骤:\n",
- "\n",
- "1. 点击`File -> Save`或者用`control+s`组合键,确保你最新的的notebook的作业已经保存到谷歌云。\n",
- "2. 执行以下代码确保 `.py` 文件保存回你的谷歌云。"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import os\n",
- "\n",
- "FOLDER_TO_SAVE = os.path.join('drive/My Drive/', FOLDERNAME)\n",
- "FILES_TO_SAVE = ['daseCV/classifiers/k_nearest_neighbor.py']\n",
- "\n",
- "for files in FILES_TO_SAVE:\n",
- " with open(os.path.join(FOLDER_TO_SAVE, '/'.join(files.split('/')[1:])), 'w') as f:\n",
- " f.write(''.join(open(files).readlines()))"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.7.0"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 1
- }
|