支持向量机(SVC)实现乳腺癌肿瘤预测

💥 项目专栏:【机器学习项目实战案例目录】项目详解 + 完整源码
文章目录
-
一、支持向量机(SVC)实现乳腺癌肿瘤预测
-
二、数据集介绍
-
三、导包
-
四、加载数据集
-
五、数据处理
-
- 5.1 数值型特征
- 5.2 离散型特征
-
六、配置流水线
-
七、获取训练数据、测试集
-
八、定义模型
-
九、模型训练
-
十、训练集、测试集验证
-
十一、混淆矩阵
-
十二、模型AUC
-
十三、AUC曲线
-
十四、网络搜索
🌠 『精品学习专栏导航帖』
- 🐳 最适合入门的100个深度学习实战项目🐳
- 🐙 【PyTorch深度学习项目实战100例目录】项目详解 + 数据集 + 完整源码🐙
- 🐶 【机器学习入门项目10例目录】项目详解 + 数据集 + 完整源码🐶
- 🦜 【机器学习项目实战10例目录】项目详解 + 数据集 + 完整源码🦜
- 🐌 Java经典编程100例🐌
- 🦋 Python经典编程100例🦋
- 🦄 蓝桥杯历届真题题目+解析+代码+答案🦄
- 🐯 【2023王道数据结构目录】课后算法设计题C、C++代码实现完整版大全🐯
一、支持向量机(SVC)实现乳腺癌肿瘤预测
Scikit-learn(以前称为scikits.learn,也称为sklearn)是针对Python 编程语言的免费软件机器学习库。它具有各种分类,回归和聚类算法,包括支持向量机,随机森林,梯度提升,k均值和DBSCAN,并且旨在与Python数值科学库NumPy和SciPy联合使用。

本项目将使用支持向量机对乳腺癌肿瘤数据进行预测,实现二分类任务。
二、数据集介绍
scikit-learn内置的乳腺癌数据集来自加州大学欧文分校机器学习仓库中的威斯康辛州乳腺癌数据集。
乳腺癌数据集是一个共有569个样本、30个输入变量和2个分类的数据集。
30个数值型测量结果由数字化细胞核的10个不同特征的均值、标准差和最差值(即最大值)构成。这些特征包括:
- radius(半径):mean of distances from center to points on the perimeter
- texture(质地):standard deviation of gray-scale values
- perimeter(周长)
- area(面积)
- smoothness(光滑度):local variation in radius lengths
- compactness(致密性):perimeter^2 / area - 1.0
- concavity(凹度):severity of concave portions of the contour
- concave points(凹点)
- symmetry(对称性)
- fractal dimension(分形维数):“coastline approximation” - 1
三、导包
我们需要导入本项目所需要的所有库,例如绘图库、模型库、评估函数库等
import warnings
import lightgbm as lgb
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import shap
import sklearn
from sklearn import metrics
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.datasets import load_breast_cancer
from sklearn.svm import SVC
from sklearn.metrics import mean_squared_error
from sklearn.metrics import r2_score
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
warnings.filterwarnings('ignore')
四、加载数据集
本项目使用的是乳腺癌肿瘤数据,该数据集在sklearn的datasets中给出,我们可以直接导入,该函数将返回两个变量,第一个就是数据集的特征矩阵X,第二个就是数据集对应的标签y。
X, y = sklearn.datasets.load_breast_cancer(return_X_y=True)
X = pd.DataFrame(X)
y = pd.DataFrame(y)
print(X.info)
print(y.info)

五、数据处理
对于数据,一般有两种,连续型数据(数值型)、离散型数据,对于这两种不同类型的数据我们一般对其处理方式不同,为了针对不同类型的特征进行处理,我们需要将这些特征提取出来。
cate_cols = [] # 离散特征
num_cols = [] # 数值型特征
# 获取各个特征的数据类型
dtypes = X.dtypes
for col, dtype in dtypes.items():
if dtype == 'object':
cate_cols.append(col)
else:
num_cols.append(col)
这里我们会遍历该数据的所有特征,然后判别特征的数据类型,然后将对应特征的编号添加到相应的列表当中。
5.1 数值型特征
class Num_Encoder(BaseEstimator, TransformerMixin):
def __init__(self, cols=[], fillna=False, addna=False):
self.fillna = fillna
self.cols = cols
self.addna = addna
self.na_cols = []
self.imputers = {}
def fit(self, X, y=None):
for col in self.cols:
if self.fillna:
self.imputers[col] = X[col].median()
if self.addna and X[col].isnull().sum():
self.na_cols.append(col)
print(self.na_cols, self.imputers)
return self
def transform(self, X, y=None):
df = X.loc[:, self.cols]
for col in self.imputers:
df[col].fillna(self.imputers[col], inplace=True)
for col in self.na_cols:
df[col + '_na'] = pd.isnull(df[col])
return df
这里我们定义了一个连续型特征处理的一个编码类,在该类中我们可以对其连续性特征进行一系列处理,这里我们只是定义了一些简单的填充操作,可以根据自己的需求补充相关数据清洗操作。
5.2 离散型特征
class Cat_Encoder(BaseEstimator, TransformerMixin):
def __init__(self, cols, max_n_cat=7, onehot_cols=[], orders={}):
self.cols = cols
self.onehot_cols = onehot_cols
self.cats = {}
self.max_n_cat = max_n_cat
self.orders = orders
def fit(self, X, y=None):
df_cat = X.loc[:, self.cols]
for n, c in df_cat.items():
df_cat[n].fillna('NAN', inplace=True)
df_cat[n] = c.astype('category').cat.as_ordered()
if n in self.orders:
df_cat[n].cat.set_categories(self.orders[n], ordered=True, inplace=True)
cats_count = len(df_cat[n].cat.categories)
if cats_count <= 2 or cats_count > self.max_n_cat:
self.cats[n] = df_cat[n].cat.categories
if n in self.onehot_cols:
self.onehot_cols.remove(n)
elif n not in self.onehot_cols:
self.onehot_cols.append(n)
print(self.onehot_cols)
return self
def transform(self, df, y=None):
X = df.loc[:, self.cols]
for col in self.cats:
X[col].fillna('NAN', inplace=True)
X.loc[:, col] = pd.Categorical(X[col], categories=self.cats[col], ordered=True)
X.loc[:, col] = X[col].cat.codes
if len(self.onehot_cols):
df_1h = pd.get_dummies(X[self.onehot_cols], dummy_na=True)
df_drop = X.drop(self.onehot_cols, axis=1)
return pd.concat([df_drop, df_1h], axis=1)
return X
这里定义了离散型特征处理的一个类,实例化该类我们可以对数据的离散型特征进行处理,例如将其进行OneHot编码等等。
六、配置流水线
num_pipeline = Pipeline([
('num_encoder', Num_Encoder(cols=num_cols, fillna='median', addna=True)),
])
X_num = num_pipeline.fit_transform(X)
cat_pipeline = Pipeline([
('cat_encoder', Cat_Encoder(cols=cate_cols))
])
X_cate = cat_pipeline.fit_transform(X)
为了方便,这里我们将上述处理数据的操作封装到流水线里,Pipeline的作用就是会按照封装的操作步骤将原始输入数据按照流程进行处理。

七、获取训练数据、测试集
上面我们已经处理好了所有的数据,之后就是要进行训练了,为了验证模型的效果,我们这里将原始数据进行分割,将其分成训练集和测试集,这里我们使用的比例是0.2,也就是整个数据集的0.2部分作为测试集。
对于这里我们将标签y进行了log操作,只是为了将标签数值更加平滑。
X = pd.concat([X_num, X_cate], axis=1)
print(X.shape, y.shape)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=2022)
print('【训练集】', X_train.shape, y_train.shape)
print('【测试集】', X_test.shape, y_test.shape)

八、定义模型
这里我们定义了SVC支持向量机模型作为实例,来进行模型训练,之后我们会对其使用网格搜索找到最优的参数。
这里我们使用自己实现的支持向量机,也可以使用sklearn自带的SVC
class SVM:
def __init__(self, C, kernel):
self.C = C
self.kernel = kernel
self.errors = None
self.user_linear_optim = 'rbf'
self.w = None
self.b = None
self.alphas = None
self.tol = 0.001
self.eps = 0.01
self.y = None
self.X = None
def fit(self, X, y):
def gaussian_kernel(x, y, sigma=1):
if np.ndim(x) == 1 and np.ndim(y) == 1:
result = np.exp(-(np.linalg.norm(x - y, 2)) ** 2 / (2 * sigma ** 2))
elif (np.ndim(x) > 1 and np.ndim(y) == 1) or (np.ndim(x) == 1 and np.ndim(y) > 1):
result = np.exp(-(np.linalg.norm(x - y, 2, axis=1) ** 2) / (2 * sigma ** 2))
elif np.ndim(x) > 1 and np.ndim(y) > 1:
result = np.exp(
-(np.linalg.norm(x[:, np.newaxis] - y[np.newaxis, :], 2, axis=2) ** 2) / (2 * sigma ** 2))
return result
def linear_kernel(x, y, b=1):
result = x @ y.T + b
return result
def init_params():
self.w = np.random.rand(X.shape[1], 1).ravel()
self.b = np.random.rand(1)
self.errors = np.zeros(X.shape[0])
self.alphas = np.random.rand(X.shape[0])
self.X = X
self.y = y
self.user_linear_optim = self.kernel
self.kernel = gaussian_kernel if self.kernel == 'rbf' else linear_kernel
def take_step(i1, i2):
if i1 == i2:
return 0
alph1 = self.alphas[i1]
alph2 = self.alphas[i2]
y1 = y[i1]
y2 = y[i2]
E1 = get_error(i1)
E2 = get_error(i2)
s = y1 * y2
if (y1 != y2):
L = max(0, alph2 - alph1)
H = min(self.C, self.C + alph2 - alph1)
elif (y1 == y2):
L = max(0, alph1 + alph2 - self.C)
H = min(self.C, alph1 + alph2)
if (L == H):
return 0
k11 = self.kernel(X[i1], X[i1])
k12 = self.kernel(X[i1], X[i2])
k22 = self.kernel(X[i2], X[i2])
eta = k11 + k22 - 2 * k12
if (eta > 0):
a2 = alph2 + y2 * (E1 - E2) / eta
if L < a2 < H:
a2 = a2
elif (a2 <= L):
a2 = L
elif (a2 >= H):
a2 = H
else:
f1 = y1 * (E1 + self.b) - alph1 * k11 - s * alph2 * k12
f2 = y2 * (E2 + self.b) - s * alph1 * k12 - alph2 * k22
L1 = alph1 + s * (alph2 - L)
H1 = alph1 + s * (alph2 - H)
Lobj = L1 * f1 + L * f2 + 0.5 * (L1 ** 2) * k11 \
+ 0.5 * (L ** 2) * k22 + s * L * L1 * k12
Hobj = H1 * f1 + H * f2 + 0.5 * (H1 ** 2) * k11 \
+ 0.5 * (H ** 2) * k22 + s * H * H1 * k12
if Lobj < Hobj - self.eps:
a2 = L
elif Lobj > Hobj + self.eps:
a2 = H
else:
a2 = alph2
if a2 < 1e-8:
a2 = 0.0
elif a2 > (self.C - 1e-8):
a2 = self.C
if (np.abs(a2 - alph2) < self.eps * (a2 + alph2 + self.eps)):
return 0
a1 = alph1 + s * (alph2 - a2)
b1 = E1 + y1 * (a1 - alph1) * k11 + y2 * (a2 - alph2) * k12 + self.b
b2 = E2 + y1 * (a1 - alph1) * k12 + y2 * (a2 - alph2) * k22 + self.b
if 0 < a1 and a1 < self.C:
b_new = b1
elif 0 < a2 and a2 < self.C:
b_new = b2
else:
b_new = (b1 + b2) * 0.5
self.b = b_new
if self.user_linear_optim != 'rbf':
self.w = self.w + y1 * (a1 - alph1) * X[i1] + y2 * (a2 - alph2) * X[i2]
self.alphas[i1] = a1
self.alphas[i2] = a2
self.errors[i1] = 0
self.errors[i2] = 0
for i in range(X.shape[0]):
if 0 < self.alphas[i] < self.C:
self.errors[i] += y1 * (a1 - alph1) * self.kernel(X[i1], X[i]) + \
y2 * (a2 - alph2) * self.kernel(X[i2], X[i]) + self.b - b_new
return 1
def get_error(i1):
if 0 < self.alphas[i1] < self.C:
return self.errors[i1]
else:
return decision_function_output(i1) - y[i1]
def decision_function_output(i):
if self.user_linear_optim != 'rbf':
return float(self.w.T @ X[i]) - self.b
else:
return np.sum(
[self.alphas[j] * self.y[j] * self.kernel(X[j], X[i]) for j in range(X.shape[0])]) - self.b
def examine_example(i2):
y2 = y[i2]
alph2 = self.alphas[i2]
E2 = get_error(i2)
r2 = E2 * y2
if ((r2 < self.tol and alph2 < self.C) or (r2 > self.tol and alph2 > 0)):
if len(self.alphas[(self.alphas != 0) & (self.alphas != self.C)]) > 1:
if self.errors[i2] > 0:
i1 = np.argmin(self.errors)
elif self.errors[i2] <= 0:
i1 = np.argmax(self.errors)
step_result = take_step(i1, i2)
if step_result:
return 1
for i1 in np.roll(np.where((self.alphas != 0) & (self.alphas != self.C))[0],
np.random.choice(np.arange(X.shape[0]))):
step_result = take_step(i1, i2)
if step_result:
return 1
for i1 in np.roll(np.arange(X.shape[0]), np.random.choice(np.arange(X.shape[0]))):
# print("what is the first i1",i1)
step_result = take_step(i1, i2)
if step_result:
return 1
return 0
def optimize():
numChanged = 0
examineAll = 1
loopnum = 0
loopnum1 = 0
loopnum2 = 0
while (numChanged > 0) or (examineAll):
numChanged = 0
if loopnum == 2000:
break
loopnum = loopnum + 1
if examineAll:
loopnum1 = loopnum1 + 1
for i in range(self.alphas.shape[0]):
examine_result = examine_example(i)
numChanged += examine_result
else:
loopnum2 = loopnum2 + 1
for i in np.where((self.alphas != 0) & (self.alphas != self.C))[0]:
examine_result = examine_example(i)
numChanged += examine_result
if examineAll == 1:
examineAll = 0
elif numChanged == 0:
examineAll = 1
init_params()
optimize()
def predict(self, X):
if self.user_linear_optim != 'rbf':
return np.sign(X @ (self.w.reshape(-1, 1)) - self.b)
else:
result = (self.alphas * self.y) @ self.kernel(self.X, X) - self.b
return np.sign(result)
def score(self, X, y):
result = self.predict(X).ravel()
return ((result == y).sum()) / len(y)
九、模型训练
这里我们分别使用了linear、rbf两种核函数进行验证,并且对比了自己实现的SVC和sklearn中的SVC。
# 自实现线性核
svm_linear = SVM(C=1, kernel='linear')
svm_linear.fit(X, y)
print(svm_linear.score(X, y))
# 自实现rbf核
svm_rbf = SVM(C=1, kernel='rbf')
svm_rbf.fit(X, y)
print(svm_rbf.score(X, y))
# sklearn库线性核
svm_lin = SVC(kernel='linear')
svm_lin.fit(X, y)
print(svm_lin.score(X, y))
# sklearn库rbf核
svm_rb = SVC(kernel='rbf')
svm_rb.fit(X, y)
print(svm_rb.score(X, y))
效果对比如下:

十、训练集、测试集验证
模型训练结束,我们可以使用该模型进行预测,由于我们的任务是分类任务,所以我们的评估指标使用AUC和准确率这两个指标。
y_train_pred = model.predict(X_train)
y_test_pred = model.predict(X_test)
accuracy_train = metrics.accuracy_score(y_train, y_train_pred)
accuracy_test = metrics.accuracy_score(y_test, y_test_pred)
print('训练集的accuracy: ', accuracy_train)
print('测试集的accuracy: ', accuracy_test)

我们从上图可以看到测试集的指标明显差于训练集,这也就是模型的泛化性较低,在训练集上过拟合了,所以需要对其进行参数调整。
十一、混淆矩阵
confusion_matrix = metrics.confusion_matrix(y_train, y_train_pred)
df_matrix = pd.DataFrame(confusion_matrix, index=['0', '1'], columns=['0', '1'])
print(df_matrix)

十二、模型AUC
y_train_proba = model.predict_proba(X_train)
y_test_proba = model.predict_proba(X_test)
auc_train = metrics.roc_auc_score(y_train, y_train_proba[:, 1])
auc_test = metrics.roc_auc_score(y_test, y_test_proba[:, 1])
print('训练集的AUC: ', auc_train)
print('测试集的AUC: ', auc_test)

十三、AUC曲线
fpr, tpr, thres = metrics.roc_curve(y_test==1, y_test_proba[:, 1])
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.plot(fpr, tpr)
plt.title('ROC曲线')
plt.xlabel('FPR')
plt.ylabel('TPR')
plt.show()

十四、网络搜索
为了能够找到最优的参数,使得模型的性能更好,所以我们使用网络搜索对其进行处理,这里我们首先定义了SVC,然后我们会定义这个模型的搜索参数,然后使用网格搜索尽心搜索。
model = SVC()
param_grid_svc = {
"kernel": ['rbf', 'linear'],
"degree": range(1, 5, 1),
"coef0": range(1, 10, 2),
"max_iter": range(100, 500, 100)
}
svcmodel_grid = GridSearchCV(estimator=model,
param_grid=param_grid_svc,
verbose=1,
n_jobs=-1,
cv=2)
svcmodel_grid.fit(X_train, y_train)
print('【RFR】', svcmodel_grid.best_score_)

网格搜索好后,我们就可以找到最好的模型参数,然后我们可以使用这些参数初始化一个新的模型用于训练。
best_modelsvc = SVC(kernel=svcmodel_grid.best_estimator_.get_params()['kernel'],
degree=svcmodel_grid.best_estimator_.get_params()['degree'],
coef0=svcmodel_grid.best_estimator_.get_params()['coef0'],
max_iter=svcmodel_grid.best_estimator_.get_params()['max_iter'])
best_modelsvc.fit(X_train, y_train)
y_train_pred = best_modelsvc.predict(X_train)
y_test_pred = best_modelsvc.predict(X_test)
accuracy_train = metrics.accuracy_score(y_train, y_train_pred)
accuracy_test = metrics.accuracy_score(y_test, y_test_pred)
print('训练集的accuracy: ', accuracy_train)
print('测试集的accuracy: ', accuracy_test)

