前几天deepseek官网上老挂着“近期受到海量网络攻击”的提示,有点感兴趣,研究了一点基于深度学习的网络入侵检测方法。找了个开源代码,用CNN检测的,学习一下当入门吧。
1. 先让代码跑起来
训它10个epoch看看(源码图轴标签就是反的,跟我没关系)
预测结果
但是有个问题,原代码生成的训练数据是包含标签的,这个操作就很搞(数据第42个特征是标签,按原代码操作训练数据会包含标签,这样就很没有意义)
2. 把代码改的面目全非
训练结果,虽然不是inf,但是调试确定爆炸了
3. 换几个模型结构搞搞
换成两个块的resnet,数据归一化,加BatchNorm层
# 模型
import torch
from torch import nn
class MyAlexNet(nn.Module):
def __init__(self):
super().__init__()
self.ReLU = nn.ReLU() # 实例化
self.Softmax = nn.Softmax(dim=1) #! CrossEntropyLoss损失函数包含Softmax层
self.norm = nn.BatchNorm2d(16) #? BatchNormal的作用为何这么大
self.conv_in = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=3, padding=1, stride=1)
self.conv = nn.Conv2d(in_channels=16, out_channels=16, kernel_size=3, padding=1, stride=1)
self.conv_trans = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=1) # 维度变换
self.pool = nn.AvgPool2d(kernel_size=2, stride=2)
self.flatten = nn.Flatten()
self.line = nn.Linear(576, 5)
def forward(self, img:torch.tensor):
x1 = self.conv_in(img)
x1 = self.norm(x1)
x1 = self.ReLU(x1)
x1 = self.conv(x1)
x1 = self.norm(x1)
y = self.conv_trans(img)
y = self.norm(y)
x1 = x1 + y
x1 = self.ReLU(x1)
x2 = self.conv(x1)
x2 = self.norm(x2)
x2 = self.ReLU(x2)
x2 = self.conv(x2)
x2 = self.norm(x2)
x2 = self.norm(x2)
x2 = x2 + x1
x2 = self.ReLU(x2)
x3 = self.pool(x2)
x3 = self.flatten(x3)
x3 = nn.functional.dropout(x3, 0.2) / 0.8
x3 = self.line(x3)
# x3 = self.Softmax(x3)
return x3
# 数据特征
PROTOCOL = ('tcp', 'udp', 'icmp')
SERVICE = ('aol','auth','bgp','courier','csnet_ns','ctf','daytime','discard',
'domain','domain_u','echo','eco_i','ecr_i','efs','exec','finger',
'ftp','ftp_data','gopher','harvest','hostnames','http','http_2784',
'http_443','http_8001','imap4','IRC','iso_tsap','klogin','kshell',
'ldap','link','login','mtp','name','netbios_dgm','netbios_ns','netbios_ssn',
'netstat','nnsp','nntp','ntp_u','other','pm_dump','pop_2','pop_3','printer',
'private','red_i','remote_job','rje','shell','smtp','sql_net','ssh',
'sunrpc','supdup','systat','telnet','tftp_u','tim_i','time','urh_i',
'urp_i','uucp','uucp_path','vmnet','whois','X11','Z39_50')
FLAG = ('OTH','REJ','RSTO','RSTOS0','RSTR','S0','S1','S2','S3','SF','SH')
CLASS = {'NORMAL':('normal'),
'PROBE':('ipsweep','mscan','nmap','portsweep','saint','satan'),
'DOS':('apache2','back','land','mailbomb','neptune','pod','processtable','smurf','teardrop','udpstorm'),
'U2R':('buffer_overflow','httptunnel','loadmodule','perl','ps','rootkit','sqlattack','xterm'),
'R2L':('ftp_write', 'guess_passwd', 'imap', 'multihop', 'named', 'phf', 'sendmail', 'snmpgetattack', 'snmpguess', 'spy', 'warezclient', 'warezmaster', 'worm', 'xlock', 'xsnoop')}
# 预处理
'''每条数据包含43个元素,其中第2、3、4、42个元素为字符串'''
PROTOCOL_IND, SERVICE_IND, FLAG_IND, CLASS_IND = 1, 2, 3, 41
def seq2img(dat:list[str]):
# 独热码编码
lal = [0 for i in range(len(CLASS))] # 数据标签
for ind, clas in enumerate(CLASS): #先分类,再编码
if dat[CLASS_IND] in CLASS[clas]:
lal[ind] = 1
break
lal = torch.Tensor(lal)
protocol_encod = [0 for i in range(len(PROTOCOL))]
protocol_encod[PROTOCOL.index(dat[PROTOCOL_IND])] = 1
service_encod = [0 for i in range(len(SERVICE))]
service_encod[SERVICE.index(dat[SERVICE_IND])] = 1
flag_encod = [0 for i in range(len(FLAG))]
flag_encod[FLAG.index(dat[FLAG_IND])] = 1
# 替换拼接
dat = list(map(float, dat[:PROTOCOL_IND])) + protocol_encod +\
list(map(float, dat[PROTOCOL_IND+1:SERVICE_IND])) + service_encod +\
list(map(float, dat[SERVICE_IND+1:FLAG_IND])) + flag_encod +\
list(map(float, dat[FLAG_IND+1:-2]))
#print(dat)
# 归一化
dat[0] = dat[0] / 54451
dat[4+74] = dat[4+74] / 1379963888
dat[5+74] = dat[5+74] / 309937401
dat[7+74] = dat[7+74] / 3
dat[8+74] = dat[8+74] / 3
dat[9+74] = dat[9+74] / 101
dat[10+74] = dat[10+74] / 4
dat[12+74] = dat[12+74] / 7479
dat[14+74] = dat[14+74] / 2
dat[15+74] = dat[15+74] / 7468
dat[16+74] = dat[16+74] / 100
dat[17+74] = dat[17+74] / 2
dat[18+74] = dat[18+74] / 9
dat[22+74] = dat[22+74] / 511
dat[23+74] = dat[23+74] / 511
dat[31+74] = dat[31+74] / 255
dat[32+74] = dat[32+74] / 255
# 格式转换
'''41 + 3 + 70 + 11 - 3 = 122 补零到12^2=144'''
dat = torch.Tensor(np.asarray(dat + [0 for i in range(144-122)]) * 255)
img = torch.reshape(dat, (1, 12, 12))
# plt.imshow(img.squeeze())
# plt.show()
return img, lal
# 数据载入
from torch.utils.data import Dataset, DataLoader
import numpy as np
class myDataset(Dataset): # 重写Dataset
def __init__(self, data_pth):
self.data = np.loadtxt(data_pth, dtype=str, delimiter=',')
def __getitem__(self, index):
return seq2img(self.data[index])
def __len__(self):
return len(self.data) # 行数
# 模型训练
if __name__ == '__main__':
BATCH_SIZE = 10
# 载入数据
pth = 'DataSet/NSL-KDD/KDDTrain+.txt'
dataset = myDataset(pth)
data = DataLoader(dataset=dataset, batch_size=BATCH_SIZE, shuffle=True)
# print(data.__len__())
# 准备
dev = 'cuda' if torch.cuda.is_available() else 'cpu'
print(dev + ' is ready!')
net = MyAlexNet().to(dev) # 模型
los = nn.CrossEntropyLoss() # 损失
opt = torch.optim.SGD(net.parameters(), lr=0.001, momentum=0.9) # 优化器
lr = torch.optim.lr_scheduler.StepLR(opt, step_size=1, gamma=0.5) # lr更新
# 训练
EPOCH_NUM = 10
for epoch in range(EPOCH_NUM):
print('#'+str(epoch))
record = 0
for n, (img, lab) in enumerate(data):
img, lab = img.to(dev), lab.to(dev)
opt.zero_grad() # 梯度归零
outp = net(img)
# print(outp)
# print(lab)
loss = los(outp, lab) # 计算损失
loss.backward() # 反向传播
opt.step() # 更新
record += loss.item()
lr.step() # 调整学习率
print('Loss:'+str(record / (n+1)))
# 保存模型
torch.save(net.state_dict(), 'Mould/net.pth')
# 载入数据
#pth = 'DataSet/NSL-KDD/KDDTrain+.txt'
pth = 'DataSet/NSL-KDD/KDDTest+.txt'
dataset = myDataset(pth)
data = DataLoader(dataset=dataset, batch_size=BATCH_SIZE, shuffle=True)
# print(data.__len__())
# 准备ceshi
dev = 'cuda' if torch.cuda.is_available() else 'cpu'
print(dev + ' is ready!')
net = MyAlexNet()
net.load_state_dict(torch.load("Mould/net.pth", weights_only=True))
net.to(dev)
net.eval()
# 训练
acc = 0
with torch.no_grad():
for n, (img, lab) in enumerate(data):
img, lab = img.to(dev), lab.to(dev)
outp = net(img)
_, pred = torch.max(outp, axis=1)
_, real = torch.max(lab, axis=1)
acc += torch.sum(pred == real) / BATCH_SIZE
print(acc / (n+1))
结果:训练10个批次,banch_size为10,然后测试训练集准确率50%;测试集准确率45.2%。