1摘要
玻璃碎片是犯罪现场的常见物之一。若能根据碎片准确识别出玻璃类别,对于案件侦查将是极大的帮助。 对于玻璃的分类,可以使用玻璃的物理指标(例如钠含量、镁含量、铝含量、铁含量、钾含量、钡含量等)作为特征建立分类模型,对玻璃的类别进行判定。 本案例需要分析抽取的214个玻璃样本的各项数据,并根据每个样本包含的9 个特征和1个类别特征等信息运用任意分类算法算法对玻璃进行自动鉴定分类。 在本案例中我们将使用公开的玻璃种类数据集,利用支持向量机来构建玻璃种类识别模型。
关键字:玻璃种类、支持向量机、多分类、分类方法
2 数据源
我们将使用UCI的玻璃种类数据集。
(http://archive.ics.uci.edu/ml/datasets/Glass+Identification) 该数据集包含了7种玻璃类型的214个样本。每一个样本包含9 个特征变量和1个类别特征变量。 每一个特征变量的具体含义如下:
RI refractive index 折射率
Na Sodium 钠含量(unit measurement: weight percent in corresponding oxide,as 4-10)
Mg Magnesium 镁含量
Al Aluminum 铝含量
Si Silicon 硅含量
K Potassium 钾含量
Ca Calcium 钙含量
Ba Barium 钡含量
Fe Iron 铁含量
每一个类别特征变量的具体含义如下:
1 building\_windows\_float\_processed
2 building\_windows\_non\_float\_processed
3 vehicle\_windows\_float\_processed
4 vehicle\_windows\_non\_float\_processed (none in this database)
5 containers
6 tableware
7 headlamps
3 数据探索与预处理
本案例中数据集中包含9个特征变量,这些变量用硅、镁、铁、钙等各种化学元素含量来度量一种玻璃。首先,我们使用pandas中的read_csv() 函数将数据导入:
import pandas as pd
glass= pd.read_csv("glass.csv") glass.head(10)
其次,使用pandas中Series的value_counts()函数,我们观察数据集中每一种玻璃的数量分布。 sort_index()函数可以让结果按照既定的排序顺序展示结果:
In [3]:
glass["Type"].value_counts().sort_index()
Out[3]:
1 70 2 76 3 17 5 13 6 9 7 29 Name: Type, dtype: int64
可见,每种玻璃的样本数量分布相对十分不均衡。现在,我们进一步观察每一个特征变量的取值分布:
In [4]:
glass.iloc[:,1:10].describe()
Out[4]:
RI | Na | Mg | AI | Si | K | Ca | Ba | Fe | |
---|---|---|---|---|---|---|---|---|---|
count | 214.000000 | 214.000000 | 214.000000 | 214.000000 | 214.000000 | 214.000000 | 214.000000 | 214.000000 | 214.000000 |
mean | 1.518365 | 13.407850 | 2.684533 | 1.444907 | 72.650935 | 0.497056 | 8.956963 | 0.175047 | 0.057009 |
std | 0.003037 | 0.816604 | 1.442408 | 0.499270 | 0.774546 | 0.652192 | 1.423153 | 0.497219 | 0.097439 |
min | 1.511150 | 10.730000 | 0.000000 | 0.290000 | 69.810000 | 0.000000 | 5.430000 | 0.000000 | 0.000000 |
25% | 1.516523 | 12.907500 | 2.115000 | 1.190000 | 72.280000 | 0.122500 | 8.240000 | 0.000000 | 0.000000 |
50% | 1.517680 | 13.300000 | 3.480000 | 1.360000 | 72.790000 | 0.555000 | 8.600000 | 0.000000 | 0.000000 |
75% | 1.519157 | 13.825000 | 3.600000 | 1.630000 | 73.087500 | 0.610000 | 9.172500 | 0.000000 | 0.100000 |
max | 1.533930 | 17.380000 | 4.490000 | 3.500000 | 75.410000 | 6.210000 | 16.190000 | 3.150000 | 0.510000 |
观察发现9个特征变量的取值范围不一样,因此对于该数据集我们需要对特征变量进行标准化操作。
3.1 数据标准化
为了将自变量进行min-max标准化,我们需要实现一个min_max_normalize()函数。该函数输入为数值型向量x,对于x中的每一个取值,减去x的最小值,再除以x中数值的取值范围。具体实现代码如下:
In [5]:
def min_max_normalize(x): return (x - x.min())/(x.max() - x.min())
现在,我们可以将实现的min_max_normalize()函数对数据集进行标准化。
In [6]:
for col in glass.columns[1:10]: glass[col] = min_max_normalize(glass[col])
In [7]:
glass.iloc[:,1:10].describe()
Out[7]:
RI | Na | Mg | AI | Si | K | Ca | Ba | Fe | |
---|---|---|---|---|---|---|---|---|---|
count | 214.000000 | 214.000000 | 214.000000 | 214.000000 | 214.000000 | 214.000000 | 214.000000 | 214.000000 | 214.000000 |
mean | 0.316744 | 0.402684 | 0.597891 | 0.359784 | 0.507310 | 0.080041 | 0.327785 | 0.055570 | 0.111783 |
std | 0.133313 | 0.122798 | 0.321249 | 0.155536 | 0.138312 | 0.105023 | 0.132263 | 0.157847 | 0.191056 |
min | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
25% | 0.235843 | 0.327444 | 0.471047 | 0.280374 | 0.441071 | 0.019726 | 0.261152 | 0.000000 | 0.000000 |
50% | 0.286655 | 0.386466 | 0.775056 | 0.333333 | 0.532143 | 0.089372 | 0.294610 | 0.000000 | 0.000000 |
75% | 0.351514 | 0.465414 | 0.801782 | 0.417445 | 0.585268 | 0.098229 | 0.347816 | 0.000000 | 0.196078 |
max | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 |
明显可见,9个特征变量都已经正确地标准化到0和1之间。
3.2 划分训练集和测试集
在实际应用中,检验模型最好的方法是将模型应用到未来的数据中。 由于缺少这样的未来数据,我们采用一种模拟的方法:将我们的数据集划分为训练集和测试集两部分。 训练集用来构建模型,而测试集在模型构建中不能使用,只用来评估模型的性能。 在本案例中,由于数据集作者并没有将样本随机排列,所以需要我们对数据进行随机打散。我们将数据的70%(149个样本)用来训练模型30%(65个样本)则用来测试模型。输入如下代码:
In [8]:
from sklearn import cross_validation data=pd.read_csv("glass.csv") y = data['Type'] del data['Type'] X = data X_train, X_test, y_train, y_test = cross_validation.train_test_split(X, y, test_size=0.3, random_state=0)
D:\Anaconda3\lib\site-packages\sklearn\cross_validation.py:41: DeprecationWarning: This module was deprecated in version 0.18 in favor of the model_selection module into which all the refactored classes and functions are moved. Also note that the interface of the new CV iterators are different from that of this module. This module will be removed in 0.20. "This module will be removed in 0.20.", DeprecationWarning)
通过value_counts()函数,我们可以对比在训练集和测试集中,各个种类的玻璃的样本分布是否接近:
In [9]:
print (y_train.value_counts().sort_index()) print (y_test.value_counts().sort_index()) print (y.value_counts().sort_index())
1 49 2 50 3 10 5 11 6 7 7 22 Name: Type, dtype: int64 1 21 2 26 3 7 5 2 6 2 7 7 Name: Type, dtype: int64 1 70 2 76 3 17 5 13 6 9 7 29 Name: Type, dtype: int64
可见,训练集和测试集比例为7:3,并且各种玻璃的类别所占比例基本一致。
4 模型训练
本案例中,我们将使用sklearn.svm包中的相关类来实现来构建基于玻璃类型识别模型。 在sklearn.svm包中,有三个类均实现了支持向量机算法:SVC, NuSVC 和 LinearSVC。 SVC 和 NuSVC接受的参数有细微差别,且底层的数学形式不一样。 而 LinearSVC 则是使用简单的线性核函数,其实现基于liblinear (LIBLINEAR -- A Library for Large Linear Classification), 对于大规模的样本训练速度会更快。 这三个支持向量机的具体介绍参考sklearn官方文档http://scikit-learn.org/stable/modules/svm.html。
本案例中,我们选用 SVC 来进行模型构建。 SVC 有两个主要的参数可以设置:核函数参数 kernel 和约束惩罚参数C。 核函数参数 kernel的常用取值及其对应含义如下:
"linear": 线性核函数 u'v
"poly": 多项式核函数 (gamma u' v + coef0)^degree
"rbf": 径向基核函数 exp(-gamma|u-v|^2)
"sigmoid": sigmoid核函数
·C:C-SVC的惩罚参数C,默认值是1.0(C越大,相当于惩罚松弛变量,希望松弛变量接近0,即对误分类的惩罚增大,趋向于对训练集全分对的情况,这样对训练集测试时准确率很高,但泛化能力弱。C值小,对误分类的惩罚减小,允许容错,将他们当成噪声点,泛化能力较强。)
·degree :多项式poly函数的维度,默认是3,选择其他核函数时会被忽略。
·gamma : ‘rbf’,‘poly’ 和‘sigmoid’的核函数参数。默认是’auto’,则会选择1/n_features
·coef0 :核函数的常数项。对于‘poly’和 ‘sigmoid’有用。
·probability :是否采用概率估计?.默认为False
·shrinking :是否采用shrinking heuristic方法,默认为true
·tol :停止训练的误差值大小,默认为1e-3
·cache_size :核函数cache缓存大小,默认为200
·class_weight :类别的权重,字典形式传递。设置第几类的参数C为weight* C(C-SVC中的C)
·verbose :允许冗余输出?
·max_iter :最大迭代次数。-1为无限制。
·decision_function_shape :‘ovo’, ‘ovr’ or None, default=None3
现在,我们选用最简单的线性核函数并利用训练集构建分类模型,C采用默认值1。
In [10]:
from sklearn.svm import SVC glass_recognition_model = SVC(C = 1, kernel = "linear")
In [11]:
glass_recognition_model.fit(X_train.iloc[:,1:],y_train)
Out[11]:
SVC(C=1, cache_size=200, class_weight=None, coef0=0.0, decision_function_shape='ovr', degree=3, gamma='auto', kernel='linear', max_iter=-1, probability=False, random_state=None, shrinking=True, tol=0.001, verbose=False)
5 模型性能评估
首先,使用predict()函数得到上一节训练的支持向量机模型在测试集合上的预测结果,然后使用 sklearn.metrics中的相关函数对模型的性能进行评估。
In [12]:
from sklearn import metrics type_pred = glass_recognition_model.predict(X_test.iloc[:,1:]) print (metrics.classification_report(y_test,type_pred)) import warnings import sklearn.exceptions warnings.filterwarnings("ignore", category=sklearn.exceptions.UndefinedMetricWarning)
precision recall f1-score support 1 0.48 0.71 0.58 21 2 0.57 0.50 0.53 26 3 0.00 0.00 0.00 7 5 0.67 1.00 0.80 2 6 0.00 0.00 0.00 2 7 1.00 1.00 1.00 7 avg / total 0.51 0.57 0.53 65
D:\Anaconda3\lib\site-packages\sklearn\metrics\classification.py:1135: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. 'precision', 'predicted', average, warn_for)
In [13]:
print (pd.DataFrame(metrics.confusion_matrix(y_test, type_pred),\ columns = y.value_counts().sort_index().index,\ index = y.value_counts().sort_index().index))
1 2 3 5 6 7 1 15 6 0 0 0 0 2 11 13 0 1 1 0 3 5 2 0 0 0 0 5 0 0 0 2 0 0 6 0 2 0 0 0 0 7 0 0 0 0 0 7
上述混淆矩阵中对角线的元素表示模型正确预测数,对角元素之和表示模型整体预测正确的样本数。 而非对角线元素上的值则可以反映模型在哪些类的预测上容易犯错,例如第2行第1列的取值为11,说明模型有11次将第二类玻璃(building_windows_non_float_processed)错误地识别为第一类玻璃(building_windows_float_processed )。 直观来看,第一类玻璃(building_windows_float_processed )和第二类玻璃(building_windows_non_float_processed)相似度比较高,对它们的区分也更具有挑战性。 现在,让我们来通过这个来计算模型在测试集中的预测正确率。
In [14]:
agreement = y_test == type_pred print (agreement.value_counts()) print ("Accuracy:", metrics.accuracy_score(y_test, type_pred))
True 37 False 28 Name: Type, dtype: int64 Accuracy: 0.5692307692307692
可见,我们的初步模型在65个测试样本中,正确预测37个,整体正确率(Accuaray)为56.92%。
6 模型性能提升
对于支持向量机,有两个主要的参数能够影响模型的性能:一是核函数的选取,二是惩罚参数C的选择。 下面,我们期望通过分别尝试这两个参数来进一步改善模型的预测性能。
6.1 核函数的选取
在 SVC 中,核函数参数kernel可选值为"rbf"(径向基核函数)、“poly”(多项式核函数)、"sigmoid"(sigmoid核函数)和"linear"(线性核函数)。我们的初始模型选取的是线性核函数,下面我们观察在其他三种核函数下模型正确率的改变。
In [15]:
kernels = ["rbf","poly","sigmoid"] for kernel in kernels: glass_model = SVC(C = 1, kernel = kernel) glass_model.fit(X_train.iloc[:,1:],y_train) type_pred = glass_model.predict(X_test.iloc[:,1:]) print ("kernel = ", kernel , ", Accuracy:",\ metrics.accuracy_score(y_test, type_pred))
kernel = rbf , Accuracy: 0.6307692307692307 kernel = poly , Accuracy: 0.6153846153846154 kernel = sigmoid , Accuracy: 0.4
从结果可以看到,当选取RBF核函数时,模型正确率由56.92%提高到63.08%。 多项式核函数下模型正确率为51.54%。 sigmoid核函数下模型的正确率只有40%。
6.2 惩罚参数C的选取
我们将分别测试C=0.01,0.1,1,10,100 时字符识别模型正确率的变化。核函数选取径向基核函数(即"rbf")。
In [20]:
c_list = [0.01, 0.1, 1, 10, 100] for C in c_list: glass_model = SVC(C = C, kernel = "rbf") glass_model.fit(X_train.iloc[:,1:],y_train) type_pred = glass_model.predict(X_test.iloc[:,1:]) print ("C = ", C , ", Accuracy:",\ metrics.accuracy_score(y_test, type_pred))
C = 0.01 , Accuracy: 0.4 C = 0.1 , Accuracy: 0.5076923076923077 C = 1 , Accuracy: 0.6307692307692307 C = 10 , Accuracy: 0.6615384615384615 C = 100 , Accuracy: 0.6923076923076923
可见,当惩罚参数C设置为10和100时,模型正确率进一步提升,分别达到66.15%和69.23%。