DaSE-Computer-Vision-2021
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 

611 рядки
21 KiB

{
"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
}