内容介绍
本课题的流程为:抽取一些数据作为数据集,用knn的方式进行多分类,对分类结果进行评价并将评价结果输出
此流程中可以用其他多分类方法方式替换knn算法
ps. 凡是代码段中用 【】 标识符括起来的部分,都需要进行对应的替换
数据处理
因为在作者的数据中,有很多label下的数据只有一条,所以为了减少这些少量数据的干扰,作者将这些数据全部归成一类,类名为’unknown’
将待更改的特征出现的次数作为tmp, 并把 tmp == 1 对应的等待更改的值改为unknown
归类方法如下所示:
def UnknownClass():
df = pd.read_csv('【C:/Users/待抽取的文件路径.csv】')
df['tmp'] = df['【待更改的特征】']
tmp_dict = dict(df['【待更改的特征】'].value_counts())
df['tmp'] = df['tmp'].replace(tmp_dict)
df.【待更改的特征】[df['tmp'] == 1] = '【更改后的名字,这里为unknown】'
# print(df)
df.to_csv('【C:/Users/更改好的文件路径.csv】')def UnknownClass():
df = pd.read_csv('【C:/Users/待抽取的文件路径.csv】')
df['tmp'] = df['【待更改的特征】']
tmp_dict = dict(df['【待更改的特征】'].value_counts())
df['tmp'] = df['tmp'].replace(tmp_dict)
df.【待更改的特征】[df['tmp'] == 1] = '【更改后的名字,这里为unknown】'
# print(df)
df.to_csv('【C:/Users/更改好的文件路径.csv】')
referrence:
数据抽取
因为原始数据集很大,所以在本课题中,只抽取部分数据作为数据集,再将抽取后的部分数据分为train set和test set
以下两种方法为从数据集中抽取部分数据,并保存为.csv文件的方法
按比例抽取
def GetPartDataToCSV(PART_RATIO):
data_file_path = '【C:/Users/待抽取的文件路径.csv】'
df = pd.read_csv(data_file_path, header=0)
x = df.mac
y = df.type_vendor_model
x_train, x_test, y_train, y_test = train_test_split(x,y,test_size = PART_RATIO)
data = {"【训练特征名(feature)】":x_test,"【对应的需要分类的类名(label)】":y_test}
dfpart = pd.DataFrame(data)
print(dfpart)
dfpart.to_csv('【C:/Users/抽取好的文件路径.csv】')
按条数随机抽取
是抽取数据的条数
def SampleGetPartDataToCSV(LINES):
data_file_path = '【C:/Users/待抽取的文件路径.csv】'
df = pd.read_csv(data_file_path, header=0)
dfpart = df.sample(LINES)
print(dfpart)
dfpart.to_csv('【C:/Users/抽取好的文件路径.csv】')
训练集测试集划分
因为作者的数据集中
type为标签label,也是分类的类名。其格式为字符串类型,需要转换为数字
且mac地址为分类依据的特征feature。其格式为十六进制,需要转换为浮点型
并使用 train_test_split 函数划分训练集和测试集,并通过设置stratify参数处理数据不平衡问题
def GetData(df, TEST_RATIO):
''' 将字符串格式的 type 转换为数字类型 '''
le = preprocessing.LabelEncoder()
labels = df.type
labels_encoder = le.fit_transform(labels)
''' 将十六进制的 mac地址 转换为float '''
macs = df.mac
features = []
for mac in macs:
# print(mac)
features.append([float.fromhex(mac)])
x_train, x_test, y_train, y_test = train_test_split(features, labels_encoder, test_size = TEST_RATIO, stratify=labels_encoder)
return x_train, x_test, y_train, y_test
knn
KNN算法的核心思想:
要想确定测试样本属于哪一类,就先寻找所有训练样本中与该测试样本“距离”最近的前K个样本,将这K个样本中大部分样本的类型作为该测试样本的类型。也就是所谓的“近朱者赤近墨者黑”,根据与其最近的k个样本的类型决定其自身的类型。因此K的确定和测算距离的方式是影响样本最终分类准确率的重要因素。
实现:
调用 KNeighborsClassifier 函数实现knn算法
调用 EvaluationFN 函数对knn算法分类的结果进行评价,调用 ClassificationResultPlot 将结果可视化
参考文档:python 实现KNN算法
def KNNOneK(x_train, x_test, y_train, y_test, weightss, k):
model = KNeighborsClassifier(n_neighbors = k,weights= weightss)
model.fit(x_train, y_train)
y_pred = model.predict(x_test)
sampleClassificationReport = EvaluationFN(y_test, y_pred)
plot.ClassificationResultPlot(sampleClassificationReport)
评价函数
对于分类问题,常用confusion_matrix(混淆矩阵),用来评估分类的准确性,其定义如下:
defination of confusion_matrix
:分类模型总体判断的准确率(预测的结果和真实的结果相同的概率,即预测的正确率)
: 预测为正样本时预测正确的概率(查准率)
: 把正样本预测出来的概率。召回率维持在80%就比较好(查全率)
: 对于某个分类,综合了Precision和Recall的一个判断指标,F1-Score的值是从0到1的,1是最好,0是最差
reference:
下面是实现:
主要使用 precision_recall_fscore_support 函数来获得 precision, recall, f1score, support(预测为某个label数据的个数)
def EvaluationFN(y_pred,y_test):
print("overall evluation result: ")
accuracy = accuracy_score(y_test, y_pred)
print("accuracy: ", accuracy)
''' 如果只想获取一种数据可以采用以下对应的函数 '''
# precision = precision_recall_fscore_support(y_test, y_pred, warn_for='precision', zero_division='warn')
# print("precision: ", precision)
# recall = precision_recall_fscore_support(y_test, y_pred, warn_for='recall', labels = 'pos_label')
# print("recall: ", recall)
# f1score = precision_recall_fscore_support(y_test, y_pred, warn_for='f-score', labels = 'array-like')
# print("f1_score: ", f1score)
# support = precision_recall_fscore_support(y_test, y_pred, warn_for='f-score', labels='array-like')
# print("support: ", support)
''' 如果想获得多种数据 '''
precision, recall, f1score, support = precision_recall_fscore_support(
y_test, y_pred, pos_label=1)
# print("precision: ", precision)
# print("recall: ", recall)
# print("f1_score: ", f1score)
# print("support: ", support)
labels = GetLabels()
''' 将数据保存为一个dataframe,并将index设置为labels '''
data = {'precision': precision,
'recall': recall, 'f1-score': f1score, 'support': support}
sampleClassificationReport = pd.DataFrame(data)
sampleClassificationReport.index = labels
# print(sampleClassificationReport)
return sampleClassificationReport
作者希望展示的时候可以标注分类的类名,所以需要额外传递label。其中 GetLabels() 用于获得label。
因为划分数据集的时候已经保证train set和test set的种类都是所有类,所以test result label就是总label
而且因为由precision_recall_fscore_support函数得到的评价结果是按顺序排列的,所以只需要对label排序就能一一对应
此处作者使用set去重,用sort排序
def GetLabels():
data_file_path = '【C:/Users/待抽取的文件路径.csv】'
df = pd.read_csv(data_file_path, header=0)
alllabels = df.type_vendor_model
labels = list(set(alllabels))
labels.sort()
# print("labels: ", labels)
return labels
评价结果可视化(HeatMap)
为了将最终的结果更清晰的展示出来,使用heatmap的方式对评价结果进行可视化
其中heatmap模板函数为:
def ShowValues(pc, fmt="%.2f", **kw):
pc.update_scalarmappable()
ax = pc.axes
for p, color, value in zip(pc.get_paths(), pc.get_facecolors(), pc.get_array()):
x, y = p.vertices[:-2, :].mean(0)
if np.all(color[:3] > 0.5):
color = (0.0, 0.0, 0.0)
else:
color = (1.0, 1.0, 1.0)
ax.text(x, y, fmt % value, ha="center", va="center", color=color, **kw)
def cm2inch(*tupl):
inch = 2.54
if type(tupl[0]) == tuple:
return tuple(i / inch for i in tupl[0])
else:
return tuple(i / inch for i in tupl)
def HeatMap(AUC, title, xlabel, ylabel, xticklabels, yticklabels, figure_width=40, figure_height=20,
correct_orientation=False, cmap='Reds'):
fig, ax = plt.subplots()
# c = ax.pcolor(AUC, edgecolors='k', linestyle= 'dashed', linewidths=0.2, cmap='RdBu', vmin=0.0, vmax=1.0)
c = ax.pcolor(AUC, edgecolors='k', linestyle='dashed', linewidths=0.2, cmap=cmap)
# put the major ticks at the middle of each cell
ax.set_yticks(np.arange(AUC.shape[0]) + 0.5, minor=False)
ax.set_xticks(np.arange(AUC.shape[1]) + 0.5, minor=False)
# set tick labels
# ax.set_xticklabels(np.arange(1,AUC.shape[1]+1), minor=False)
ax.set_xticklabels(xticklabels, minor=False)
ax.set_yticklabels(yticklabels, minor=False)
# set title and x/y labels
plt.title(title)
plt.xlabel(xlabel)
plt.ylabel(ylabel)
# Remove last blank column
plt.xlim((0, AUC.shape[1]))
# Turn off all the ticks
ax = plt.gca()
for t in ax.xaxis.get_major_ticks():
t.tick1On = False
t.tick2On = False
for t in ax.yaxis.get_major_ticks():
t.tick1On = False
t.tick2On = False
# Add color bar
plt.colorbar(c)
# Add text in each cell
ShowValues(c)
# Proper orientation (origin at the top left instead of bottom left)
if correct_orientation:
ax.invert_yaxis()
ax.xaxis.tick_top()
# resize
fig = plt.gcf()
# fig.set_size_inches(cm2inch(40, 20))
# fig.set_size_inches(cm2inch(40*4, 20*4))
fig.set_size_inches(cm2inch(figure_width, figure_height))
将得到的分类评价结果数据画成 heatmap:
使用 列表生成式 生成 yticklabels 格式为 【label】(【support】)
def PlotClassificationReport(classification_report, title='Classification report ', cmap='RdBu'):
df = classification_report
# print(df)
xlabel = 'Metrics'
ylabel = 'Classes'
xticklabels = ['Precision', 'Recall', 'F1-score']
yticklabels = ['{0} ({1})'.format(list(df.index)[index], sup)
for index, sup in enumerate(df.support)]
figure_width = 25
figure_height = len(df.support) + 7
correct_orientation = False
HeatMap(df[['precision', 'recall', 'f1-score']], title, xlabel,
ylabel, xticklabels, yticklabels, figure_width, figure_height,
correct_orientation, cmap=cmap)
封装整个模块,并输出结果热力图:
在本例中,类较多,所以使用 iloc 函数对原 df 进行切分,只保留前10行
def ClassificationResultPlot(sampleClassificationReport):
PlotClassificationReport(sampleClassificationReport.iloc[:10, :])
plt.savefig('【结果图名称】.png', dpi=100, format='png', bbox_inches='tight')
plt.close()
结果如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DmycrlU0-1623721820229)(http://image.huawei.com/tiny-lts/v1/images/4ce5fb8039f0a42187da8f1f040fd2da_995x598.png@900-0-90-f.png)]
reference:
matplotlib颜色 推荐:GnBu(蓝绿) 、RdBu(同上)
main函数
data_file_path = '【C:/Users/待抽取的文件路径.csv】'
df = pd.read_csv(data_file_path, header=0)
x_train, x_test, y_train, y_test = GetData(df,TEST_RATIO, 0)
knn.KNNOneK(x_train, x_test, y_train, y_test, '【加权方式,distance/uniform】', 【k值】)
未来改进
- 目前 evaluate 和 heatmap 的部分都是嵌套在 KNNOneK() 里,未来可以拆出来。可以将此模块重构成两个部分,一个部分是算法,可以添加 knn 以外的多分类算法;另一部分是整个分析流程。
- 目前的 label 采取了一个偷懒的方式,另还可以创建对应的字典,在计算完评估结果后查字典找到对应的 label
- 对于类别不均衡的分类模型,采用 macro 方式会有较大的偏差,采用 weighted 方式则可较好反映模型的优劣、