本文将在上一篇文章的基础上(上一篇文章完成了项目的数据处理及统计学分析),继续完善数据预处理部分、模型搭建、模型训练等步骤。
本项目使用Jupyter Notebook进行操作,使用PaddlePaddle框架进行实战操作
1 项目回顾 ✅
为了有效保护企业敏感数据,践行企业安全操作行为准则,杜绝由异常操作行为导致的企业敏感数据泄露安全事件发生,用户异常行为分析与识别成为重难点技术之一。
本项目将使用Paddle搭建人工神经网络,通过机器学习的手段预测用户上网异常评分,本项目包括数据处理与分析、数据预处理、模型搭建、模型训练、模型预测、模型评估等完整流程,尝试搭建三种不同的网络解决该问题。
2. 数据预处理
2.1 数据编码
- 在数据处理过程中,我们有时需要对不连续的数字或者文本进行数字化处理。
- 其中,我们选取了
'account', 'group', 'IP', 'url', 'switchIP'这五个object类型的特征字段进行了编码。
from sklearn.preprocessing import LabelEncoder
for feat in ['account', 'group', 'IP', 'url', 'switchIP']:
labelencoder = LabelEncoder()
train_data[feat] = labelencoder.fit_transform(train_data[feat])
查看编码后的数据集
# 查看编码后的数据集
train_data.head()
编码后的结果如下图所示:
- 可以发现以前的object字段都变成了数字编码
2.2 划分训练集与测试集
- 选取了
'account', 'group', 'IP', 'url', 'port', 'vlan', 'switchIP', 'hour', 'weekday', 'year', 'month', 'day'字段作为训练的特征(features)。 'ret'字段为想要预测的目标值/标签值(label)。- 我们将数据集划分为:训练集:测试集 = 0.75:0.25
先构造整体数据的特征值与标签值。
data_X = train_data.loc[train_data.index[:], ['account', 'group', 'IP', 'url', 'port', 'vlan', 'switchIP', 'hour', 'weekday', 'year', 'month', 'day']]
data_Y = train_data['ret']
然后,使用sklearn库中的数据集切分API进行对训练集和测试集进行切分。
from sklearn.model_selection import train_test_split
# 数据集划分
x_train, x_test, y_train, y_test = train_test_split(data_X, data_Y, test_size=0.25, random_state=6)
print("训练集的特征值:\n", x_train, x_train.shape)
print("测试集的标签值:\n", y_test, y_test.shape)
print("The length of original data X is:", data_X.shape[0])
print("The length of train Data is:", x_train.shape[0])
print("The length of test Data is:", x_test.shape[0])
部分运行结果如下图所示:
2.3 标签值数组维度转换
训练集的特征数组(features)为二维数组,而训练集的标签数据(label)是一维数组,需要将其转变为二维数组再传入神经网络训练,保持维度一致,否则会报错
首先转换字段类型,然后对数组维度进行转换
- (-1,1):后一个1表示指定数组维度为1列,前一个-1表示根据指定好的数组维度1列自动调整行数
x_train = np.array(x_train, dtype='float32')
y_train = np.array(y_train, dtype='float32')
y_train = y_train.reshape(-1,1)
y_train
x_test = np.array(x_test, dtype='float32')
y_test = np.array(y_test, dtype='float32')
y_test = y_test.reshape(-1,1)
y_test
2.4 归一化(标准化)
我们对训练集和测试集的特征值进行了标准化
- 目的是将所有特征放缩在一个数量级下,这样就不会导致某个特征对模型的影响过大
# 标准化
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import StandardScaler
# 1. 实例化一个转换器类
transfer = StandardScaler()
# 2. 标准化
x_train = transfer.fit_transform(x_train)
x_test = transfer.fit_transform(x_test)
2.5 设置随机数种子
import random
import paddle
seed = 666
# 设置随机种子 固定结果
def set_seed(seed):
np.random.seed(seed)
random.seed(seed)
paddle.seed(seed)
set_seed(seed)
3. 模型组网
使用飞桨PaddlePaddle进行组网,激活函数可以选择:
隐藏层层数可以尝试使用1层,2层,3层。
3.1 搭建神经网络
尝试使用
'account', 'group', 'IP', 'url', 'port', 'vlan', 'switchIP', 'hour', 'weekday', 'year', 'month', 'day':共12个特征(features)进行输入'ret':作为回归输出的标签(label)- 激活函数使用的是Tanh
- 共4层隐藏层
paddle.nn.Dropout(p=0.5, axis=None, mode="upscale_in_train”, name=None)
- Dropout 是一种正则化手段,该算子根据给定的丢弃概率 p,在训练过程中随机将一些神经元输出设置为 0,通过阻止神经元节点间的相关性来减少过拟合。
import paddle
import paddle.nn as nn
# 定义动态图
class Classification(paddle.nn.Layer):
def __init__(self):
super(Classification, self).__init__()
nn.Tanh
nn.PReLU
self.drop = paddle.nn.Dropout(p=0.5)
self.fc1 = paddle.nn.Linear(12, 64)
self.fc2 = paddle.nn.Linear(64, 32)
self.fc3 = paddle.nn.Linear(32, 16)
self.fc4 = paddle.nn.Linear(16, 8)
self.fc5 = paddle.nn.Linear(8, 1)
self.Tanh = nn.Tanh()
self.PReLU = nn.PReLU()
# 网络的前向计算函数
def forward(self, inputs):
x = self.Tanh(self.fc1(inputs))
x = self.drop(x)
x = self.PReLU(self.fc2(x))
x = self.drop(x)
x = self.PReLU(self.fc3(x))
x = self.drop(x)
x = self.PReLU(self.fc4(x))
x = self.drop(x)
pred = self.fc5(x)
return pred
4. 模型训练
4.1 定义可视化函数
- 在训练后可以运行可视化函数,对训练损失进行可视化
- 可视化函数定义如下,重点是获得iter与cost之间的变化关系
# 定义绘制训练过程的损失值变化趋势的方法draw_train_process
train_nums = []
train_costs = []
def draw_train_process(iters,train_costs):
title="training cost"
plt.title(title, fontsize=24)
plt.xlabel("iter", fontsize=14)
plt.ylabel("cost", fontsize=14)
plt.plot(iters, train_costs,color='red',label='training cost')
plt.grid()
plt.show()
4.2 定义损失函数
- 损失函数使用的是k1损失,需要自己定义一下损失函数。
- 当然,也可以尝试使用其他的损失函数,例如均方误差损失。
- k1损失函数定义如下:
import paddle
import paddle.nn.functional as F
class kl_loss(paddle.nn.Layer):
def __init__(self):
super(kl_loss, self).__init__()
def forward(self, p, q, label):
ce_loss = 0.5 * (F.mse_loss(p, label=label)) + F.mse_loss(q, label=label)
kl_loss = self.compute_kl_loss(p, q)
# carefully choose hyper-parameters
loss = ce_loss + 0.3 * kl_loss
return loss
def compute_kl_loss(self, p, q):
p_loss = F.kl_div(F.log_softmax(p, axis=-1), F.softmax(q, axis=-1), reduction='none')
q_loss = F.kl_div(F.log_softmax(q, axis=-1), F.softmax(p, axis=-1), reduction='none')
# You can choose whether to use function "sum" and "mean" depending on your task
p_loss = p_loss.sum()
q_loss = q_loss.sum()
loss = (p_loss + q_loss) / 2
return loss
4.3 模型训练
模型参数设置如下:
- 训练轮数:5
- batch_size:64
- 损失函数:kl_loss
如果内存空间不是很大,batch_size也不适合选择太大,可以设置稍微少一些
import paddle.nn.functional as F
y_preds = []
labels_list = []
BATCH_SIZE = 64
train_data = x_train
train_data_y = y_train
test_data = x_test
test_data_y = y_test
compute_kl_loss = kl_loss()
CET_loss = paddle.nn.CrossEntropyLoss()
def train(model):
print('start training ... ')
# 开启模型训练模式
model.train()
EPOCH_NUM = 5
train_num = 0
scheduler = paddle.optimizer.lr.CosineAnnealingDecay(learning_rate=0.001, T_max=int(train_data.shape[0]/BATCH_SIZE*EPOCH_NUM), verbose=False)
optimizer = paddle.optimizer.Adam(learning_rate=scheduler, parameters=model.parameters())
for epoch_id in range(EPOCH_NUM):
# 在每轮迭代开始之前,将训练数据的顺序随机的打乱
np.random.shuffle(train_data)
# 将训练数据进行拆分,每个batch包含64条数据
mini_batches = [np.append(train_data[k: k+BATCH_SIZE], train_data_y[k: k+BATCH_SIZE], axis = 1) for k in range(0, len(train_data), BATCH_SIZE)]
for batch_id, data in enumerate(mini_batches):
features_np = np.array(data[:, :12], np.float32)
labels_np = np.array(data[:, -1:], np.float32)
features = paddle.to_tensor(features_np)
labels = paddle.to_tensor(labels_np)
#前向计算
# y_pred = model(features)
y_pred1 = model(features)
y_pred2 = model(features)
cost = compute_kl_loss(y_pred1, y_pred2, label=labels)
# cost = CET_loss(y_pred, labels)
# cost = F.mse_loss(y_pred, label=labels)
train_cost = cost.numpy()[0]
#反向传播
cost.backward()
#最小化loss,更新参数
optimizer.step()
# 清除梯度
optimizer.clear_grad()
if batch_id % 500 == 0 and epoch_id % 1 == 0:
print("Pass:%d,Cost:%0.5f"%(epoch_id, train_cost))
train_num = train_num + BATCH_SIZE
train_nums.append(train_num)
train_costs.append(train_cost)
model = Classification()
train(model)
运行训练函数,神经网络模型就可以训练起来了。部分训练结果如下图所示:
5. 模型预测
测试集样本量较大,可以选择预测一个样本,也可以预测部分样本值。
- 定义预测函数,然后使用预测函数对测试集杨嫩
train_data = x_train
train_data_y = y_train
test_data = x_test
test_data_y = y_test
def predict(model):
print('start evaluating ... ')
model.eval()
outputs = []
mini_batches = [np.append(test_data[k: k+BATCH_SIZE], test_data_y[k: k+BATCH_SIZE], axis = 1) for k in range(0, len(test_data), BATCH_SIZE)]
for data in mini_batches:
features_np = np.array(data[:, :12], np.float32)
features = paddle.to_tensor(features_np)
pred = model(features)
out = paddle.argmax(pred, axis=1)
outputs.extend(out.numpy())
return outputs
outputs = predict(model)
6. 计算指标
6.1 Mean Squared Error(MSE) 均方误差
- 对于预测的结果,我们可以进行模型评估
- 我们选择MSE指标进行评估
from sklearn.metrics import mean_squared_error
print(mean_squared_error(test_data_y,outputs))
7. 总结
本文继续上一篇文章,完成了用户上网异常分析项目的数据预处理,模型搭建,模型预测,模型评估步骤。对上网异常数据进行了预测。
-
本项目搭建神经网络实现了对UEBA用户上网异常回归预测。
-
今后可以尝试的改进方向总结如下:
- 继续挖掘特征与目标值的关系
- 由于数据集特征有限,可以考虑进一步丰富数据特征。
- 可以考虑使用其他网络模型,例如卷积网络,对该数据集进行回归预测,分析其效果。
- 另外,可以尝试使用不同的损失函数对模型进一步优化。
本文正在参加「金石计划 . 瓜分6万现金大奖」