基于机器学习的网络入侵检测与特征选择及随机森林分类器性能评估(NSL-KDD数据集)
本文将详细介绍如何利用Python和相关机器学习库对NSL-KDD数据集进行预处理,特征选择,并通过随机森林算法构建网络入侵检测模型。同时,还将展示如何计算并可视化模型的ROC曲线以评估其性能。首先,我们导入了必要的库,如pandas、seaborn、numpy以及scikit-learn等,并加载了KDDTrain+和KDDTest+两个数据集。通过对数据集进行初步探索,我们将列名重置为实际含义
简介
本文将详细介绍如何利用Python和相关机器学习库对NSL-KDD数据集进行预处理,特征选择,并通过随机森林算法构建网络入侵检测模型。同时,还将展示如何计算并可视化模型的ROC曲线以评估其性能。
首先,我们导入了必要的库,如pandas、seaborn、numpy以及scikit-learn等,并加载了KDDTrain+和KDDTest+两个数据集。通过对数据集进行初步探索,我们将列名重置为实际含义,并将标签(attack_type)以及其他类别型特征进行了编码转换。
在特征工程阶段,我们分析了攻击类型特征的相关性,通过找出与攻击类型关联最小的10个特征以及标准差较小的5个特征,确定了一组待删除的冗余特征。通过剔除这些特征,我们得到了精简后的数据集combined_data_reduced,并进一步将其划分为特征矩阵X和目标向量y。
接下来,我们使用随机森林分类器作为模型,设置了参数n_estimators为10,criterion为’entropy’,max_features为’auto’,bootstrap设置为True,以便训练模型并对测试集进行预测。获取模型在测试集上的得分,并输出了特征重要性。
为了更深入地评估模型的性能,我们还绘制了ROC曲线。ROC曲线是一种用于二分类问题中直观展示模型性能的工具,它展示了假正率(False Positive Rate, FPR)与真正率(True Positive Rate, TPR)之间的权衡关系。这里我们生成了一个示例数据集,并使用同样的随机森林模型计算出其ROC曲线。最后,我们在图表中添加了“无技能”基准线(即随机猜测的结果),并与模型的实际ROC曲线进行了对比。
总结来说,本篇文章展示了从原始数据预处理到特征选择,再到建立随机森林模型并评估其性能的全过程,为我们提供了在网络入侵检测领域运用机器学习技术的一个实践案例。
步骤
- 数据加载与初步预处理:读取KDDTrain+和KDDTest+两个CSV文件,合并为一个数据集,并重新命名列名。
- 特征工程:对类别型特征如攻击类型、协议类型等进行标签编码,分析特征与目标变量之间的相关性,基于相关性和标准差筛选出最不相关的特征予以删除,优化特征空间。
- 数据划分:将精简后的数据集划分为特征矩阵X和目标向量y,并进一步划分为训练集和测试集。
- 模型建立与训练:使用随机森林分类器进行训练,设置参数优化模型性能。
- 模型评估:计算模型在测试集上的准确率,并提取特征重要性;此外,还通过生成二分类示例数据集,展示了模型的ROC曲线,以更全面地评估模型在区分正常流量与攻击流量时的性能。
代码实现
导入必要的库
# 配置IPython自动补全为贪心模式
%config IPCompleter.greedy=True
# 导入数据处理和可视化库
import pandas as pd
import seaborn as sns
import numpy as np
import re
import sklearn
# 忽略警告信息
import warnings
warnings.filterwarnings("ignore")
# 导入绘图库及其配置
import matplotlib.pyplot as plt
import matplotlib as matplot
%matplotlib inline
# 设置IPython交互模式为全节点
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
# 导入数据集划分工具
from sklearn.model_selection import train_test_split
数据加载及预处理
从指定路径读取NSL-KDD99数据集的训练集和测试集;
重置训练集和测试集的列名,以便后续处理;
将训练集和测试集合并为一个大的数据集,以便进行统一的分析和处理;
调整数据集的列名,以符合特征的实际情况;
删除不需要的特征列,以简化数据集。
# 导入训练集和测试集
train = pd.read_csv('/kaggle/input/nsl-kdd99-dataset/KDDTrain+.txt')
test = pd.read_csv('/kaggle/input/nsl-kdd99-dataset/KDDTest+.txt')
# 获取训练集和测试集的形状(行数和列数)
train.shape
test.shape
# 重命名训练集和测试集的列,使用列的序号作为列名
train.columns = range(train.shape[1])
test.columns = range(test.shape[1])
# 定义标签列表,对应数据集中的特征含义
labels = ['duration', 'protocol_type', 'service', 'flag', 'src_bytes',
'dst_bytes', 'land', 'wrong_fragment', 'urgent', 'hot',
'num_failed_logins', 'logged_in', 'num_compromised', 'root_shell',
'su_attempted', 'num_root', 'num_file_creations', 'num_shells',
'num_access_files', 'num_outbound_cmds', 'is_host_login',
'is_guest_login', 'count', 'srv_count', 'serror_rate',
'srv_serror_rate', 'rerror_rate', 'srv_rerror_rate', 'same_srv_rate',
'diff_srv_rate', 'srv_diff_host_rate', 'dst_host_count',
'dst_host_srv_count', 'dst_host_same_srv_rate', 'dst_host_diff_srv_rate', 'dst_host_same_src_port_rate',
'dst_host_srv_diff_host_rate', 'dst_host_serror_rate',
'dst_host_srv_serror_rate', 'dst_host_rerror_rate',
'dst_host_srv_rerror_rate', 'attack_type', 'difficulty_level']# subclass - > attack_type
# 将训练集和测试集合并为一个数据集
combined_data = pd.concat([train, test])
# 获取合并后数据集的形状
combined_data.shape
# 显示合并后数据集的前5行
combined_data.head(5)
# 将合并后数据集的列名替换为定义的标签列表
combined_data.columns = labels
# 删除合并后数据集中的'difficulty_level'列
combined_data = combined_data.drop('difficulty_level', 1)
# 显示处理后的合并数据集的前3行
combined_data.head(3)
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | udp | other | SF | 146 | 0 | 0 | 0 | 0 | 0 | ... | 0.00 | 0.60 | 0.88 | 0.00 | 0.00 | 0.00 | 0.0 | 0.00 | normal | 15 |
1 | 0 | tcp | private | S0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0.10 | 0.05 | 0.00 | 0.00 | 1.00 | 1.00 | 0.0 | 0.00 | neptune | 19 |
2 | 0 | tcp | http | SF | 232 | 8153 | 0 | 0 | 0 | 0 | ... | 1.00 | 0.00 | 0.03 | 0.04 | 0.03 | 0.01 | 0.0 | 0.01 | normal | 21 |
3 | 0 | tcp | http | SF | 199 | 420 | 0 | 0 | 0 | 0 | ... | 1.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.0 | 0.00 | normal | 21 |
4 | 0 | tcp | private | REJ | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0.07 | 0.07 | 0.00 | 0.00 | 0.00 | 0.00 | 1.0 | 1.00 | neptune | 21 |
5 rows × 43 columns
duration | protocol_type | service | flag | src_bytes | dst_bytes | land | wrong_fragment | urgent | hot | ... | dst_host_srv_count | dst_host_same_srv_rate | dst_host_diff_srv_rate | dst_host_same_src_port_rate | dst_host_srv_diff_host_rate | dst_host_serror_rate | dst_host_srv_serror_rate | dst_host_rerror_rate | dst_host_srv_rerror_rate | attack_type | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | udp | other | SF | 146 | 0 | 0 | 0 | 0 | 0 | ... | 1 | 0.0 | 0.60 | 0.88 | 0.00 | 0.00 | 0.00 | 0.0 | 0.00 | normal |
1 | 0 | tcp | private | S0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 26 | 0.1 | 0.05 | 0.00 | 0.00 | 1.00 | 1.00 | 0.0 | 0.00 | neptune |
2 | 0 | tcp | http | SF | 232 | 8153 | 0 | 0 | 0 | 0 | ... | 255 | 1.0 | 0.00 | 0.03 | 0.04 | 0.03 | 0.01 | 0.0 | 0.01 | normal |
3 rows × 42 columns
数据编码
from sklearn import preprocessing
# 导入预处理模块
le = preprocessing.LabelEncoder()
# 创建标签编码器对象
# 打印攻击类型集合,以查看原始值
print(set(list(combined_data['attack_type'])))
# 对数据集中的'attack_type'、'protocol_type'、'service'、'flag'列进行标签编码
combined_data['attack_type'] = le.fit_transform(combined_data['attack_type'])
combined_data['protocol_type'] = le.fit_transform(combined_data['protocol_type'])
combined_data['service'] = le.fit_transform(combined_data['service'])
combined_data['flag'] = le.fit_transform(combined_data['flag'])
# 描述编码后的攻击类型数据
print('\nDescribing attack_type: ')
print("min", combined_data['attack_type'].min()) # 最小值
print("max", combined_data['attack_type'].max()) # 最大值
print("mean", combined_data['attack_type'].mean()) # 平均值
print("mode", combined_data['attack_type'].mode()) # 模式,即出现频率最高的值
print("looks like 16 is 'normal' ")
# 根据编码结果推断,16可能代表'normal'攻击类型
特征消除和数据集分割
根据攻击类型与其他特征的相关性以及特征的标准差,进行特征选择和消除
# 计算特征之间的相关性矩阵,并按'attack_type'的绝对相关性值进行排序
corr_matrix = combined_data.corr().abs().sort_values('attack_type')
# 选择与'attack_type'相关性最低的前10个特征
leastCorrelated = corr_matrix['attack_type'].nsmallest(10)
leastCorrelated = list(leastCorrelated.index)
# 选择标准差最低的前5个特征
leastSTD = combined_data.std().to_frame().nsmallest(5, columns=0)
leastSTD = list(leastSTD.transpose().columns)
# 结合相关性和标准差,得到需要消除的特征集合
featureElimination = set(leastCorrelated + leastSTD)
len(featureElimination)
featureElimination
# 根据特征集合,从数据集中删除选定的特征
combined_data_reduced = combined_data.drop(featureElimination,axis=1)
# 分离特征和标签,并将数据集划分为训练集和测试集
data_x = combined_data_reduced.drop('attack_type', axis=1)
data_y = combined_data_reduced.loc[:,['attack_type']]
X_train, X_test, y_train, y_test = train_test_split(data_x, data_y, test_size=.5, random_state=42) # 待完成:说明train_test_split的具体作用和参数意义
{‘dst_bytes’,
‘is_host_login’,
‘land’,
‘logged_in’,
‘num_access_files’,
‘num_compromised’,
‘num_file_creations’,
‘num_outbound_cmds’,
‘num_root’,
‘num_shells’,
‘root_shell’,
‘srv_rerror_rate’,
‘su_attempted’,
‘urgent’}
训练随机森林分类器模型
# 导入必要的库
import numpy as np
import matplotlib.pyplot as plt
from itertools import cycle
from sklearn import svm, datasets
from sklearn.metrics import roc_curve, auc
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import label_binarize
from sklearn.multiclass import OneVsRestClassifier
from sklearn.metrics import roc_auc_score
from sklearn import metrics
from sklearn import linear_model
from sklearn.ensemble import VotingClassifier
from sklearn.ensemble import AdaBoostClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import BaggingClassifier
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.ensemble import IsolationForest
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
import gc
gc.collect()
# 初始化随机森林分类器
RF = RandomForestClassifier(n_estimators=10, criterion='entropy', max_features='auto', bootstrap=True)
# 训练模型
RF.fit(X_train, y_train)
# 获取特征重要性
RF_feature = RF.feature_importances_
# 在测试集上评估模型性能
rf_score = RF.score(X_test, y_test)
print('RandomForestClassifier processing ,,,')
# 多类别问题的二进制化处理
y = preprocessing.label_binarize(y_train, classes=[0, 1, 2, 3])
n_classes = y.shape[1]
# 示例:绘制预测模型的ROC曲线
# 生成二分类数据集
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_curve
from matplotlib import pyplot
X, y = make_classification(n_samples=1000, n_classes=2, random_state=1)
# 划分训练集和测试集
trainX, testX, trainy, testy = train_test_split(X, y, test_size=0.5, random_state=2)
# 训练模型
model = RandomForestClassifier()
model.fit(trainX, trainy)
# 预测概率
yhat = model.predict_proba(testX)
# 获取正类的概率
pos_probs = yhat[:, 1]
# 绘制无技能的ROC曲线
pyplot.plot([0, 1], [0, 1], linestyle='--', label='No Skill')
# 计算模型的ROC曲线
fpr, tpr, _ = roc_curve(testy, pos_probs)
# 绘制模型的ROC曲线
pyplot.plot(fpr, tpr, marker='_', label='RFC_Performance')
# 添加轴标签
pyplot.xlabel('False Positive Rate')
pyplot.ylabel('True Positive Rate')
# 显示图例
pyplot.legend()
# 显示图形
pyplot.show()
优化建议
- 特征选择方面,可以尝试更多的特征选择方法,如递归特征消除(RFE)、基于树的特征重要性排序等,寻找最优特征子集。
- 超参数调优:针对随机森林分类器,可以通过网格搜索或随机搜索等方式调整n_estimators、max_depth、min_samples_split等超参数,寻找最佳模型配置。
- 类别不平衡问题处理:由于实际场景中正常流量可能远大于异常流量,可考虑引入过采样、欠采样或SMOTE等技术平衡各类别样本。
- 模型集成:结合AdaBoostClassifier、GradientBoostingClassifier等多种分类器进行集成学习,提高整体预测性能。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)