目录
1. 概要
OCSVM(One-Class SVM), 很多人翻译成一类SVM,不过我觉得单类SVM可能更好一些。那什么是OC-SVM呢,与经典的SVM的有什么区别?。。。。。。--别问,问就是现在我也不知道也说不清楚,留作homework^-^(敬请等待后文,如果我没有走上从入门到放弃的路的话)。
本文只是想介绍一个第一天接触OCSVM的小白的自创(就是各处抄了一些东西凑齐了而且还看上去运行起来了)的一个toy-example。看到这里你可以确定你是不是还要看下去了(判断依据是你是不是一个同等level的小白^-^)。
经典的分类问题是将待识别对象分为两类或者多类,识别结果要么为这一类,要么为那一类。但是,在有些问题场景中,只需要确定待检测样本是否属于已知的某一特定分类,针对某个观测样本,其判断结果为是或者否。一个中文网络世界提及OCSVM的解释所经常给出的例子是:假设这样一个特殊的场景,比方说,有一个小和尚,他从小在寺庙长大,从来都只见过男生,没见过女生。那么对于一个男性,他能很快地基于这个男性与他之前接触的男性有类似的特征,给出这个人是男性的答案。但如果是一个女性,他会发现,这个女性与他之前所认知的男性的特征差异很大,进而会得出这个“新物种”不是男性的判断。但注意这里的“不是男性”并不意味着小和尚做出了这个“新物种”是女性的判断,他只是判断这个“新物种”跟他日常世界所接触过的人不是一类人,他甚至不一定有“男性”“女性”这个概念。写到这里,我也可以举一个“原创”的例子,就是(在对应于大航海时代以后的若干个世纪)一个与世隔绝的原始部落的人,突然有一天碰到一个入侵的金发碧眼的西欧强盗的时候,他没有什么外国人这个概念,甚至也没有物种的概念,但是他隐隐约约知道这个眼前的生物非我族类非我同种。。。
好了,概念就只说这么多(事实上只能扯得出这么多了),下面介绍本小白基于scikit-learn的OneClassSVM所构建的一个toy-example。本渣觉得跟概念和论文硬刚太费劲,最适合于小白入门学习的方法就是手撕代码从例子中学习。先学会调包调参,然后再自己写核心函数 ,然后。。。看造化了。
以下代码是在Jupyter notebook中调试运行的,所以感兴趣的伙伴们可以把代码copy到Jupyter notebook中去运行。如果是作为普通的python脚本运行的话,可能需要一些修改。
这个例子的思路是这样的:训练集是一组全部落在单位院内的二维数据,而测试集为x和y坐标均落在[-1,1]和[-1,1]的矩形内的随机数据。用训练集对svm.OneLcassSVM进行训练后,训练的目标是希望能够分辨出待测数据是否落在单位圆内。
2. 导入需要的包
导入scikit-learn中的svm.OneLcassSVM以及其它一些需要的包。
import numpy as np
import random
import sys
from sklearn.svm import OneClassSVM
import matplotlib.pyplot as plt
%matplotlib inline
3. 构造数据
实验数据生成如下(借鉴自[2])
isInUnitCircle()是一个helper function,用于实验数据构造中判断二维数据是否在单位圆内。
def isInUnitCircle(x,y):
'''
Judge whether (x,y) is within the range of unit circle
'''
if x**2 + y**2 > 1:
return False
return True
n_train = 1000
n_test = 100
X = np.zeros((n_train,2))
Y = np.zeros((n_test,2))
for i in range(n_train):
x1 = random.uniform(-1.,1.)
x2 = random.uniform(-1.,1.)
if isInUnitCircle(x1,x2):
X[i,:] = [x1,x2]
for i in range(100):
y1 = random.uniform(-1.,1.)
y2 = random.uniform(-1.,1.)
Y[i,:] = [y1,y2]
X是训练数据,全部落在单位圆内。Y是测试数据,一部分落在单位圆内,一部分落在单位圆外。以下看看数据长得啥样。
fig, ax = plt.subplots(1,2,figsize = (12,6))
ax[0].scatter(X[:,0],X[:,1])
ax[1].scatter(Y[:,0],Y[:,1])
ax[0].set_title('Train set')
ax[1].set_title('Test set')
fig.suptitle('Left: train set; Right: test set')
4. 训练和预测
clf = OneClassSVM(gamma=0.01, nu=0.01).fit(X)
X_pred = clf.predict(X)
Y_pred = clf.predict(Y)
因为是调包嘛,所以就这么简单。只不过参数还是需要设置一下。完全使用缺省参数的话在本例中是不行的,有兴趣伙伴可以试一试将gamma和nu的设定去掉看看是什么效果。至于参数应该如何选取。。。呃。。。尴尬了,以后再商量^-^.
先看看训练集本身的预测准确度(Accuracy)如何。
X_accuracy = np.sum(X_pred[X_pred > 0])/len(X_pred)
print('X_accuracy = {0:6.3f}'.format(X_accuracy))
输出结果:X_accuracy = 0.991
接下来,看看测试集的预测准确度(Accuracy)如何。
Y_pred_gt = 2 * ((Y[:,0]**2 + Y[:,1]**2) < 1) - 1
Y_diff = Y_pred_gt * Y_pred
Y_accuracy = np.sum(Y_diff[Y_diff > 0])/len(Y_diff)
print('Y_accuracy = {0:6.3f}'.format(Y_accuracy))
输出结果:Y_accuracy = 0.980
5. Summary
一个最简单的例子,能运行而且基本上还预测正确说明对OneLcassSVM的调用方法的理解大致没错。可以继续前进了。。。
在以上训练中,训练数据全部是处于单位圆内的。经过训练,分类器模型也的确搞明白了把单位圆内作为正常区域,在此之外的区域当作是异常数据。在对测试数据的判别中有98%的准确率,还是觉得挺神奇的。因为并没有给标签啊,分类器是怎么学习到要区分单位圆内和单位圆外的呢?仅仅是因为训练数据全部位于单位圆内这样一个规则的区域?
[Reference]