{ "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": [ "# 多分类支撑向量机练习\n", "*完成此练习并且上交本ipynb(包含输出及代码).*\n", "\n", "在这个练习中,你将会:\n", " \n", "- 为SVM构建一个完全向量化的**损失函数**\n", "- 实现**解析梯度**的向量化表达式\n", "- 使用数值梯度检查你的代码是否正确\n", "- 使用验证集**调整学习率和正则化项**\n", "- 用**SGD(随机梯度下降)** **优化**损失函数\n", "- **可视化** 最后学习到的权重\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "pdf-ignore" ] }, "outputs": [], "source": [ "# 导入包\n", "import random\n", "import numpy as np\n", "from daseCV.data_utils import load_CIFAR10\n", "import matplotlib.pyplot as plt\n", "\n", "# 下面一行是notebook的magic命令,作用是让matplotlib在notebook内绘图(而不是新建一个窗口)\n", "%matplotlib inline\n", "plt.rcParams['figure.figsize'] = (10.0, 8.0) # 设置绘图的默认大小\n", "plt.rcParams['image.interpolation'] = 'nearest'\n", "plt.rcParams['image.cmap'] = 'gray'\n", "\n", "# 该magic命令可以重载外部的python模块\n", "# 相关资料可以去看 http://stackoverflow.com/questions/1907993/autoreload-of-modules-in-ipython\n", "%load_ext autoreload\n", "%autoreload 2" ] }, { "cell_type": "markdown", "metadata": { "tags": [ "pdf-ignore" ] }, "source": [ "## 准备和预处理CIFAR-10的数据" ] }, { "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", "# 这里我们每个类别展示了7张图片\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)\n", " idxs = np.random.choice(idxs, samples_per_class, replace=False)\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", "# 我们从训练集中抽取了一小部分作为代码开发的数据,\n", "# 使用小批量的开发数据集能够快速开发代码\n", "num_training = 49000\n", "num_validation = 1000\n", "num_test = 1000\n", "num_dev = 500\n", "\n", "# 从原始训练集中抽取出num_validation个样本作为验证集\n", "mask = range(num_training, num_training + num_validation)\n", "X_val = X_train[mask]\n", "y_val = y_train[mask]\n", "\n", "# 从原始训练集中抽取出num_training个样本作为训练集\n", "mask = range(num_training)\n", "X_train = X_train[mask]\n", "y_train = y_train[mask]\n", "\n", "# 从训练集中抽取num_dev个样本作为开发数据集\n", "mask = np.random.choice(num_training, num_dev, replace=False)\n", "X_dev = X_train[mask]\n", "y_dev = y_train[mask]\n", "\n", "# 从原始测试集中抽取num_test个样本作为测试集\n", "mask = range(num_test)\n", "X_test = X_test[mask]\n", "y_test = y_test[mask]\n", "\n", "print('Train data shape: ', X_train.shape)\n", "print('Train labels shape: ', y_train.shape)\n", "print('Validation data shape: ', X_val.shape)\n", "print('Validation labels shape: ', y_val.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": [ "# 预处理:把图片数据rehspae成行向量\n", "X_train = np.reshape(X_train, (X_train.shape[0], -1))\n", "X_val = np.reshape(X_val, (X_val.shape[0], -1))\n", "X_test = np.reshape(X_test, (X_test.shape[0], -1))\n", "X_dev = np.reshape(X_dev, (X_dev.shape[0], -1))\n", "\n", "# 完整性检查,打印出数据的shape\n", "print('Training data shape: ', X_train.shape)\n", "print('Validation data shape: ', X_val.shape)\n", "print('Test data shape: ', X_test.shape)\n", "print('dev data shape: ', X_dev.shape)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "pdf-ignore-input" ] }, "outputs": [], "source": [ "# 预处理:减去image的平均值(均值规整化)\n", "# 第一步:计算训练集中的图像均值\n", "mean_image = np.mean(X_train, axis=0)\n", "print(mean_image[:10]) # print a few of the elements\n", "plt.figure(figsize=(4,4))\n", "plt.imshow(mean_image.reshape((32,32,3)).astype('uint8')) # visualize the mean image\n", "plt.show()\n", "\n", "# 第二步:所有数据集减去均值\n", "X_train -= mean_image\n", "X_val -= mean_image\n", "X_test -= mean_image\n", "X_dev -= mean_image\n", "\n", "# 第三步:拼接一个bias维,其中所有值都是1(bias trick),\n", "# SVM可以联合优化数据和bias,即只需要优化一个权值矩阵W\n", "X_train = np.hstack([X_train, np.ones((X_train.shape[0], 1))])\n", "X_val = np.hstack([X_val, np.ones((X_val.shape[0], 1))])\n", "X_test = np.hstack([X_test, np.ones((X_test.shape[0], 1))])\n", "X_dev = np.hstack([X_dev, np.ones((X_dev.shape[0], 1))])\n", "\n", "print(X_train.shape, X_val.shape, X_test.shape, X_dev.shape)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## SVM分类器\n", "\n", "你需要在**daseCV/classifiers/linear_svm.py**里面完成编码\n", "\n", "我们已经预先定义了一个函数`compute_loss_naive`,该函数使用循环来计算多分类SVM损失函数" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# 调用朴素版的损失计算函数\n", "from daseCV.classifiers.linear_svm import svm_loss_naive\n", "import time\n", "\n", "# 生成一个随机的SVM权值矩阵(矩阵值很小)\n", "W = np.random.randn(3073, 10) * 0.0001 \n", "\n", "loss, grad = svm_loss_naive(W, X_dev, y_dev, 0.000005)\n", "print('loss: %f' % (loss, ))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "从上面的函数返回的`grad`现在是零。请推导支持向量机损失函数的梯度,并在svm_loss_naive中编码实现。\n", "\n", "为了检查是否正确地实现了梯度,你可以用数值方法估计损失函数的梯度,并将数值估计与你计算出来的梯度进行比较。我们已经为你提供了检查的代码:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# 一旦你实现了梯度计算的功能,重新执行下面的代码检查梯度\n", "\n", "# 计算损失和W的梯度\n", "loss, grad = svm_loss_naive(W, X_dev, y_dev, 0.0)\n", "\n", "# 数值估计梯度的方法沿着随机几个维度进行计算,并且和解析梯度进行比较,\n", "# 这两个方法算出来的梯度应该在任何维度上完全一致(相对误差足够小)\n", "from daseCV.gradient_check import grad_check_sparse\n", "f = lambda w: svm_loss_naive(w, X_dev, y_dev, 0.0)[0]\n", "grad_numerical = grad_check_sparse(f, W, grad)\n", "\n", "# 把正则化项打开后继续再检查一遍梯度\n", "# 你没有忘记正则化项吧?(忘了的罚抄100遍(๑•́ ₃•̀๑) )\n", "loss, grad = svm_loss_naive(W, X_dev, y_dev, 5e1)\n", "f = lambda w: svm_loss_naive(w, X_dev, y_dev, 5e1)[0]\n", "grad_numerical = grad_check_sparse(f, W, grad)" ] }, { "cell_type": "markdown", "metadata": { "tags": [ "pdf-inline" ] }, "source": [ "**问题 1**\n", "\n", "有可能会出现某一个维度上的gradcheck没有完全匹配。这个问题是怎么引起的?有必要担心这个问题么?请举一个简单例子,能够导致梯度检查失败。如何改进这个问题?*提示:SVM的损失函数不是严格可微的*\n", "\n", "$\\color{blue}{ 你的回答:}$ *在这里填写* \n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# 接下来实现svm_loss_vectorized函数,目前只计算损失\n", "# 稍后再计算梯度\n", "tic = time.time()\n", "loss_naive, grad_naive = svm_loss_naive(W, X_dev, y_dev, 0.000005)\n", "toc = time.time()\n", "print('Naive loss: %e computed in %fs' % (loss_naive, toc - tic))\n", "\n", "from daseCV.classifiers.linear_svm import svm_loss_vectorized\n", "tic = time.time()\n", "loss_vectorized, _ = svm_loss_vectorized(W, X_dev, y_dev, 0.000005)\n", "toc = time.time()\n", "print('Vectorized loss: %e computed in %fs' % (loss_vectorized, toc - tic))\n", "\n", "# 两种方法算出来的损失应该是相同的,但是向量化实现的方法应该更快\n", "print('difference: %f' % (loss_naive - loss_vectorized))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# 完成svm_loss_vectorized函数,并用向量化方法计算梯度\n", "\n", "# 朴素方法和向量化实现的梯度应该相同,但是向量化方法也应该更快\n", "tic = time.time()\n", "_, grad_naive = svm_loss_naive(W, X_dev, y_dev, 0.000005)\n", "toc = time.time()\n", "print('Naive loss and gradient: computed in %fs' % (toc - tic))\n", "\n", "tic = time.time()\n", "_, grad_vectorized = svm_loss_vectorized(W, X_dev, y_dev, 0.000005)\n", "toc = time.time()\n", "print('Vectorized loss and gradient: computed in %fs' % (toc - tic))\n", "\n", "# 损失是一个标量,因此很容易比较两种方法算出的值,\n", "# 而梯度是一个矩阵,所以我们用Frobenius范数来比较梯度的值\n", "difference = np.linalg.norm(grad_naive - grad_vectorized, ord='fro')\n", "print('difference: %f' % difference)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 随机梯度下降(Stochastic Gradient Descent)\n", "\n", "我们现在有了向量化的损失函数表达式和梯度表达式,同时我们计算的梯度和数值梯度是匹配的。\n", "接下来我们要做SGD。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# 在linear_classifier.py文件中,编码实现LinearClassifier.train()中的SGD功能,\n", "# 运行下面的代码\n", "from daseCV.classifiers import LinearSVM\n", "svm = LinearSVM()\n", "tic = time.time()\n", "loss_hist = svm.train(X_train, y_train, learning_rate=1e-7, reg=2.5e4,\n", " num_iters=1500, verbose=True)\n", "toc = time.time()\n", "print('That took %fs' % (toc - tic))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# 一个有用的debugging技巧是把损失函数画出来\n", "plt.plot(loss_hist)\n", "plt.xlabel('Iteration number')\n", "plt.ylabel('Loss value')\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# 完成LinearSVM.predict函数,并且在训练集和验证集上评估其准确性\n", "y_train_pred = svm.predict(X_train)\n", "print('training accuracy: %f' % (np.mean(y_train == y_train_pred), ))\n", "y_val_pred = svm.predict(X_val)\n", "print('validation accuracy: %f' % (np.mean(y_val == y_val_pred), ))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "code" ] }, "outputs": [], "source": [ "# 使用验证集来调整超参数(正则化强度和学习率)。\n", "# 你可以尝试不同的学习速率和正则化项的值;\n", "# 如果你细心的话,您应该可以在验证集上获得大约0.39的准确率。\n", "\n", "# 注意:在搜索超参数时,您可能会看到runtime/overflow的警告。\n", "# 这是由极端超参值造成的,不是代码的bug。\n", "\n", "learning_rates = [1e-7, 5e-5]\n", "regularization_strengths = [2.5e4, 5e4]\n", "\n", "# results是一个字典,把元组(learning_rate, regularization_strength)映射到元组(training_accuracy, validation_accuracy) \n", "# accuracy是样本中正确分类的比例\n", "results = {}\n", "best_val = -1 # 我们迄今为止见过最好的验证集准确率\n", "best_svm = None # 拥有最高验证集准确率的LinearSVM对象\n", "\n", "##############################################################################\n", "# TODO:\n", "# 编写代码,通过比较验证集的准确度来选择最佳超参数。\n", "# 对于每个超参数组合,在训练集上训练一个线性SVM,在训练集和验证集上计算它的精度,\n", "# 并将精度结果存储在results字典中。此外,在best_val中存储最高验证集准确度,\n", "# 在best_svm中存储拥有此精度的SVM对象。\n", "#\n", "# 提示: \n", "# 在开发代码时,应该使用一个比较小的num_iter值,这样SVM就不会花费太多时间训练; \n", "# 一旦您确信您的代码开发完成,您就应该使用一个较大的num_iter值重新训练并验证。\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", "# 打印results\n", "for lr, reg in sorted(results):\n", " train_accuracy, val_accuracy = results[(lr, reg)]\n", " print('lr %e reg %e train accuracy: %f val accuracy: %f' % (\n", " lr, reg, train_accuracy, val_accuracy))\n", " \n", "print('best validation accuracy achieved during cross-validation: %f' % best_val)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "pdf-ignore-input" ] }, "outputs": [], "source": [ "# 可是化交叉验证结果\n", "import math\n", "x_scatter = [math.log10(x[0]) for x in results]\n", "y_scatter = [math.log10(x[1]) for x in results]\n", "\n", "# 画出训练集准确率\n", "marker_size = 100\n", "colors = [results[x][0] for x in results]\n", "plt.subplot(2, 1, 1)\n", "plt.scatter(x_scatter, y_scatter, marker_size, c=colors, cmap=plt.cm.coolwarm)\n", "plt.colorbar()\n", "plt.xlabel('log learning rate')\n", "plt.ylabel('log regularization strength')\n", "plt.title('CIFAR-10 training accuracy')\n", "\n", "# 画出验证集准确率\n", "colors = [results[x][1] for x in results] # default size of markers is 20\n", "plt.subplot(2, 1, 2)\n", "plt.scatter(x_scatter, y_scatter, marker_size, c=colors, cmap=plt.cm.coolwarm)\n", "plt.colorbar()\n", "plt.xlabel('log learning rate')\n", "plt.ylabel('log regularization strength')\n", "plt.title('CIFAR-10 validation accuracy')\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# 在测试集上测试最好的SVM分类器\n", "y_test_pred = best_svm.predict(X_test)\n", "test_accuracy = np.mean(y_test == y_test_pred)\n", "print('linear SVM on raw pixels final test set accuracy: %f' % test_accuracy)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "pdf-ignore-input" ] }, "outputs": [], "source": [ "# 画出每一类的权重\n", "# 基于您选择的学习速度和正则化强度,画出来的可能不好看\n", "w = best_svm.W[:-1,:] # 去掉bias\n", "w = w.reshape(32, 32, 3, 10)\n", "w_min, w_max = np.min(w), np.max(w)\n", "classes = ['plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']\n", "for i in range(10):\n", " plt.subplot(2, 5, i + 1)\n", " \n", " # 将权重调整为0到255之间\n", " wimg = 255.0 * (w[:, :, :, i].squeeze() - w_min) / (w_max - w_min)\n", " plt.imshow(wimg.astype('uint8'))\n", " plt.axis('off')\n", " plt.title(classes[i])" ] }, { "cell_type": "markdown", "metadata": { "tags": [ "pdf-inline" ] }, "source": [ "**问题2**\n", "\n", "描述你的可视化权值是什么样子的,并提供一个简短的解释为什么它们看起来是这样的。\n", "\n", "$\\color{blue}{ 你的回答: }$ *请在这里填写* \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/linear_svm.py', 'daseCV/classifiers/linear_classifier.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 }