在过去几周中,我们开始对机器学习有了更多的了解,也认识到机器学习在机器视觉、图像分类和深度学习领域的重要作用。
我们已经看到卷积神经网络,如LetNet,可以用于对MNIST数据集的手写字迹进行分类。我们使用了k-NN算法来识别一张图片中是否含有猫或狗,并且我们也已经学习了如何调参来优化模型,进而提高分类精度。
然而,还有一个重要的机器学习的算法我们尚未涉及:这个算法非常容易构建,并能很自然地扩展到神经网络和卷积神经网络中。
是什么算法呢?
它是一个简单的线性分类器,并且由于其算法很直观,被认为是更多高级的机器学习和深度学习算法的基石。
继续阅读来加深你对线性分类器的认识,以及如何使用它们进行图像分类。
Python线性分类模型简介
本教程的前半部分主要关注线性分类有关的基本原理和数学知识。总的来说,线性分类指的是那些真正从训练数据中“学习”的有参分类算法。
在这里,我提供了一个真正的线性分类实现代码,以及一个用scikit-learn对一张图片中的内容分类的例子。
4大参数化学习和线性分类的组件
我已经多次使用“参数化”,但它到底是什么意思?
简而言之:参数化是确定模型必要参数的过程。
在机器学习的任务中,参数化根据以下几个方面来确定面对的问题:
- 数据:这是我们将要学习的输入数据。这些数据包括了数据点(例如,特征向量,颜色矩阵,原始像素特征等)和它们对应的标签。
- 评分函数:一个函数接收输入数据,然后将数据匹配到类标签上。例如,我们输入特征向量后,评分函数就开始处理这些数据,调用某个函数f(比如我们的评分函数),最后返回预测的分类标签。
- 损失函数:损失函数可以量化预测的类标签与真实的类标签之间的匹配程度。这两个标签集合之间的相似度越高,损失就越小(即分类精度越高)。我们的目标是最小化损失函数,相应就能提高分类精度。
- 权重矩阵:权重矩阵,通常标记为W,它是分类器实际优化的权重或参数。我们根据评分函数的输出以及损失函数,调整并优化权重矩阵,提高分类精度。
注意:取决于你使用的模型的种类,参数可能会多的多。但是在最底层,你会经常遇到4个参数化学习的基本模块。
一旦确定了这4个关键组件,我们就可以使用优化方法来找到评分函数的一组参数W,使得损失函数达到最小值(同时提升对数据的分类准确度)
接下来,我们就将看到如何利用这些组件,搭建一个将输入数据转化为真实预测值的分类器。
线性分类器:从图片到标签
在这部分中,我们将更多的从数学角度研究参数模型到机器学习。
首先,我们需要数据。假设训练数据集(图片或者压缩后的特征向量)标记为
,每个图或特征向量都有对应的类标签
。我们用
和
表示有N个D维(特征向量的长度)的数据点,划分到K个唯一的类别中。
为了这些表达式更具体点,参考我们以前的教程:基于提取后的颜色矩阵,使用knn分类器识别图片中的猫和狗。
这份数据集中,总共有N=25,000张图片,每张图片的特征都是一个3D颜色直方图,其中每个管道有8个桶。这样产出的特征向量有D=8 x 8 x 8 = 512个元素。最后,我们有k=2个类别标签,一个是“狗”,另一个是“猫”。

有了这些变量后,我们必须定义一个评分函数f,将特征向量映射到类标签的打分上。正如本篇博客标题所说,我们将使用一个简单的线性映射:

我们假设每个
都由一个形状为[D x 1]的单列向量所表示。我们在本例中要再次使用颜色直方图,不过如果我们使用的是原始像素粒度,那可以直接把图片中的像素压扁到一个单列向量中。
我们的权重矩阵W形状为[K x D](类别标签数乘以特征向量的维数)
最后,偏置矩阵b,大小为[K x 1]。实质上,偏置矩阵可以让我们的评分函数向着一个或另一个方向“提升”,而不会真正影响权重矩阵W,这点往往对学习的成功与否非常关键。
回到Kaggle的猫和狗例子中, 每个都表示为512维颜色直方图,因此的形状是[512 x 1]。权重矩阵W的形状为[2 x 512],而偏置矩阵b为[2 x 1]。
下面展示的是线性分类的评分函数f

在上图的左侧是原始输入图片,我们将从中提取特征。在本例中,我们计算的是一个512维的颜色直方图,也可以用其他一些特征表示方式(包括原始像素密度),但是对于这个例子,我们就只用颜色分布,即直方图来表示xi。
然后我们有了权重矩阵W,有2行(每个类标签一行)和512列(每一列都是特征向量中的条目)
将W和xi点乘后,再加上大小为[2 x 1]的偏置矩阵bi。
最后就得到了右边的两个值:猫和狗标签各自的分数。
看着上面的公式,你可以确信输入xi和yi都是固定的,没法修改。我们当然可以通过不同的特征提取技术来得到不同的xi,但是一旦特征抽取后,这些值就不会再改变了。
实际上,我们唯一能控制的参数就是权重矩阵W以及偏置向量b。因此,我们的目标是利用评分函数和损失函数去优化权重和偏置向量,来提升分类的准确度。
如何优化权重矩阵则取决于我们的损失函数,但通常会涉及梯度下降的某种形式。我们会在以后的博客中重温优化和损失函数的概念,不过现在只要简单理解为给定了一个评分函数后,我们还需要定义一个损失函数,来告诉我们对于输入数据的预测有多“好”。
参数学习和线性分类的优点
利用参数学习有两个主要的优点,正如我上面详述的方法:
- 一旦我们训练完了模型,就可以丢掉输入数据而只保留权重矩阵W和偏置向量b。这大大减少了模型的大小,因为我们只需要存储两个向量集合(而非整个训练集)。
- 对新的测试数据分类很快。为了执行分类,我们要做的只是点乘W和xi,然后再加上偏置b。这样做远比将每个测试点和整个训练集比较(比如像knn算法那样)快的多。
既然我们理解了线性分类的原理,就一起看下如何在python,opencv和scikit-learn中实现。
使用python,opencv和scikit-learn对图片线性分类
就像在之前的例子Kaggle 猫vs狗数据集和knn算法中,我们将从数据集中提取颜色直方图,不过和前面例子不同的是,我们将用一个线性分类器,而非knn。
准确地说,我们将使用线性支持向量机(SVM),即在n维空间中的数据之间构造一个最大间隔分离超平面。这个分离超平面的目标是将类别为i的所有(或者在给定的容忍度下,尽可能多)的样本分到超平面的一边,而所有类别非i的样本分到另一边。
关于支持向量机的具体描述已经超出本博客的范围。(不过在PyImageSearch Gurus course有详细描述)
同时,只需知道我们的线性SVM使用了和本博客“线性分类器:从图片到标签”部分中相似的评分函数,然后使用损失函数,用于确定最大分离超平面来对数据点分类(同样,我们将在以后的博客中讲述损失函数)。
我们从打开一个新文件开始,命名为
linear_classifier.py,然后插入以下代码:
# import the necessary packages
from sklearn.preprocessing import LabelEncoder
from sklearn.svm import LinearSVC
from sklearn.metrics import classification_report
from sklearn.cross_validation import train_test_split
from imutils import paths
import numpy as np
import argparse
import imutils
import cv2
import os
从第2行至11行导入了必须的python包。我们要使用scikit-learn库,因此如果你还没安装的话,跟着这些步骤,确保将其安装到你机器上。
我们还将使用我的imutils包,用于方便处理图像的一系列函数。如果你还没有安装imutils,那就让pip安装。
$ pip install imutils
现在我们定义
extract_color_histogram 函数,用于提取和量化输入图片的内容:
def extract_color_histogram(image, bins=(8, 8, 8)):
# extract a 3D color histogram from the HSV color space using
# the supplied number of `bins` per channel
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
hist = cv2.calcHist([hsv], [0, 1, 2], None, bins,
[0, 180, 0, 256, 0, 256])
# handle normalizing the histogram if we are using OpenCV 2.4.X
if imutils.is_cv2():
hist = cv2.normalize(hist)
# otherwise, perform "in place" normalization in OpenCV 3 (I
# personally hate the way this is done
else:
cv2.normalize(hist, hist)
# return the flattened histogram as the feature vector
return hist.flatten()
这个函数接收一个输入
image ,将其转化为HSV颜色空间,然后利用为每个通道提供的bins,计算3D颜色直方图。
利用cv2.calcHist函数计算出颜色直方图后,将其归一化后返回给调用函数。
有关extract_color_histogram方法更详细的描述,参阅这篇博客。
接着,我们从命令行解析参数,并初始化几个变量:
# import the necessary packages
from sklearn.preprocessing import LabelEncoder
from sklearn.svm import LinearSVC
from sklearn.metrics import classification_report
from sklearn.cross_validation import train_test_split
from imutils import paths
import numpy as np
import argparse
import imutils
import cv2
import os
def extract_color_histogram(image, bins=(8, 8, 8)):
# extract a 3D color histogram from the HSV color space using
# the supplied number of `bins` per channel
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
hist = cv2.calcHist([hsv], [0, 1, 2], None, bins,
[0, 180, 0, 256, 0, 256])
# handle normalizing the histogram if we are using OpenCV 2.4.X
if imutils.is_cv2():
hist = cv2.normalize(hist)
# otherwise, perform "in place" normalization in OpenCV 3 (I
# personally hate the way this is done
else:
cv2.normalize(hist, hist)
# return the flattened histogram as the feature vector
return hist.flatten()
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-d", "--dataset", required=True,
help="path to input dataset")
args = vars(ap.parse_args())
# grab the list of images that we'll be describing
print("[INFO] describing images...")
imagePaths = list(paths.list_images(args["dataset"]))
# initialize the data matrix and labels list
data = []
labels = []
33行到36行解析命令行参数。我们这里只需要一个简单的开关,
—dataset是kaggle 猫vs狗数据集的路径。
然后我们将25000张图片保存的磁盘位置赋值给
imagePaths,跟着初始化一个data矩阵,存储提取后的特征向量和类别labels。
说到提取特征,我们接着这样做:
# loop over the input images
for (i, imagePath) in enumerate(imagePaths):
# load the image and extract the class label (assuming that our
# path as the format: /path/to/dataset/{class}.{image_num}.jpg
image = cv2.imread(imagePath)
label = imagePath.split(os.path.sep)[-1].split(".")[0]
# extract a color histogram from the image, then update the
# data matrix and labels list
hist = extract_color_histogram(image)
data.append(hist)
labels.append(label)
# show an update every 1,000 images
if i > 0 and i % 1000 == 0:
print("[INFO] processed {}/{}".format(i, len(imagePaths)))
imagePaths进行遍历,对于每个imagePath,我们从磁盘中加载image,提取类别label,然后通过计算颜色直方图来量化图片。然后我们更新data和labels各自的列表。
1 赞 1 收藏 评论打赏支持我翻译更多好文章,谢谢!
打赏译者