【2021CCF基线系统】系统认证风险预测-异常检测

本项目基于百度飞桨PaddlePaddle实现系统认证风险预测-异常检测

一、赛题背景

随着国家、企业对安全和效率越来越重视,作为安全基础设施之一的统一身份管理(IAM,Identity and Access Management)也得到越来越多的关注。 在IAM领域中,其主要安全防护手段是身份鉴别,既我们常见的口令验证、扫码验证、指纹验证等。它们一般分为三类,既用户所知(如口令)、所有(如身份证)、特征(如人脸)。但这些身份鉴别方式都有其各自的优缺点,比如口令,强度高了记不住,强度低了容易丢,又比如人脸,摇头晃脑做活体检测体验不好,静默检测如果算法不够,又很容易被照片、视频、人脸模型绕过。也因此,在等保2.0中对于三级以上系统要求必须使用两种以上的身份鉴别方式进行身份验证,来提高身份鉴别的可信度,这也被成为双因素认证。

但这对用户来说虽然在一定程度提高了安全性,但极大的降低了用户体验。也因此,IAM厂商开始参考UEBA、用户画像等行为分析技术,来探索一种既能确保用户体验,又能提高身份鉴别强度的方法。而在当前IAM的探索进程当中,目前最为可具落地的方法,是基于规则的行为分析技术,因为它的可理解性很高,且很容易与身份鉴别技术进行联动。

但基于规则但局限性也很明显,它是基于经验的,有宁错杀一千,不放过一个的特点,缺少从数据层面来证明是否有人正在尝试窃取或验证非法获取的身份信息,又或者正在使用窃取的身份信息,以此来提前进行风险预警和处置。

更多内容请前往比赛官网查看:系统认证风险预测-异常检测

比赛任务

本赛题中,参赛团队需要基于用户认证行为数据及风险异常标记结构,构建用户认证行为特征模型和风险异常评估模型,利用风险评估模型去判断当前用户认证行为是否存在风险。

  • 利用用户认证数据构建行为基线
  • 采用监督学习模型,基于用户认证行为特征,构建风险异常评估模型,判断当前用户认证行为是否存在风险

二、数据分析及处理

比赛数据是从竹云的风险分析产品日志库中摘录而来,主要涉及认证日志与风险日志数据。比赛数据经过数据脱敏和数据筛选等安全处理操作,供大家使用。其中认证日志是用户在访问应用系统产生的行为数据,包括登录、单点登录、退出等行为。

该比赛使用的数据已上传至AI Studio:https://aistudio.baidu.com/aistudio/datasetdetail/112151

数据概况

import csv
import numpy as np

train_dataset = r'data/data112151/train_dataset.csv'
test_dataset = r'data/data112151/test_dataset.csv'

with open(train_dataset, encoding = 'utf-8') as trainset:
    traindatas = np.loadtxt(trainset, str, delimiter = "\t", skiprows = 1)

with open(test_dataset, encoding = 'utf-8') as testset:
    testdatas = np.loadtxt(testset, str, delimiter = "\t", skiprows = 1)

print("在训练集中,共有{}条数据,其中每条数据有{}个特征".format(traindatas.shape[0], traindatas.shape[1]))
print("在测试集中,共有{}条数据,其中每条数据有{}个特征".format(testdatas.shape[0], testdatas.shape[1]))
在训练集中,共有15016条数据,其中每条数据有19个特征
在测试集中,共有10000条数据,其中每条数据有18个特征

查看某一条数据:

index = 0
print("第{}条训练数据是\n{}".format(index+1, traindatas[index]))
print("第{}条测试数据是\n{}".format(index+1, testdatas[index]))
第1条训练数据是
['access:test_d:20180101111639:bBp1' '2018/1/1 11:16' 'test_d' 'login'
 'otp' '192.168.100.101' '内网' '1级'
 '{"first_lvl":"成都分公司","sec_lvl":"9楼","third_lvl":"销售部"}' 'web' 'desktop'
 'think_pad_e460' 'windows' 'windows 10' 'chrome' 'chrome 90' 'coremail'
 'management' '0']
第1条测试数据是
['access:test_c:20191023212545:H2in' '2019/10/23 21:25' 'test_c' 'sso' ''
 '27.10.135.254' '代理IP' '3级'
 '{"first_lvl":"重庆","sec_lvl":"重庆市","third_lvl":"江北区"}' 'web' 'desktop'
 'macbook' 'macOS' 'macOS Big Sur 11' 'safari' 'safari 13' 'order-mgnt'
 'sales']

在训练集中,官方提供的数据共有19个特征,其中最后一个特征是风险标识即标签。

数据预处理

比赛提供的部分数据结构如下所示:

序号字段名_中文字段名_英文含义示例
3操作类型actionlogin还是sso1 : login 2 : sso
4首次认证方式auth_type账密、短信、otp、二维码1 : pwd 2 : sms 3 : otp 4 : qr
6IP类型ip_location_type_keyword内网、家庭宽带、公共宽带、代理ip1 : 家庭宽带 2 : 代理ip 3 : 内网 4 : 公共宽带
7IP威胁级别ip_risk_level1级、2级、3级1 : 1级 2 : 2级 3 : 3级
9客户端类型client_typeapp、web1 : app 2 : web
10浏览器来源browser_source桌面端、移动端1 : desktop 2 : mobile
17应用系统类目op_target系统所属的系统类别1 : sales 2 : finance 3 : management 4 : hr
18风险标识risk_label有、无1 : 有;0 : 无

这些字段不需要编码,因此把这些字段的数据单独取出来,组合成新的数据,如下所示:

traindata = traindatas[:, [3, 4, 6, 7, 9, 10, 17]]
trainlabel = traindatas[:, [18]]
testdata = testdatas[:, [3, 4, 6, 7, 9, 10, 17]]
# print(traindata, trainlabel)
# 将以上字段映射成数字
encoder_map = { 'login': 1, 'sso': 2, 
                'pwd': 1, 'sms': 2, 'otp': 3, 'qr': 4,
                '家庭宽带': 1, '代理IP': 2, '内网': 3, '公共宽带': 4,
                '1级': 1, '2级': 2, '3级': 3,
                'app': 1, 'web': 2, 
                'desktop': 1, 'mobile': 2,
                'sales': 1, 'finance': 2, 'management': 3, 'hr': 4,
                '': 0 }
# 将字段编码成数字
for iter in range(len(traindata)):
    for item in range(len(traindata[iter])):
        # print(traindata[iter])
        traindata[iter][item] = encoder_map[traindata[iter][item]]
        # print(traindata[iter])

for iter in range(len(testdata)):
    for item in range(len(testdata[iter])):
        testdata[iter][item] = encoder_map[testdata[iter][item]]
# 转换为nparray
data_X = np.array(traindata, dtype='float32')
data_Y = np.array(trainlabel, dtype='float32')
# 检查大小
print('data shape', data_X.shape, data_Y.shape)
print('data_x shape[1]', data_X.shape[1])
data shape (15016, 7) (15016, 1)
data_x shape[1] 7

三、模型组网

使用飞桨PaddlePaddle进行组网,在本基线系统中,只使用两层全连接层完成分类任务。

import paddle
import paddle.nn as nn

# 定义动态图
class Classification(paddle.nn.Layer):
    def __init__(self):
        super(Classification, self).__init__()
        self.drop = paddle.nn.Dropout(p=0.5)
        # 定义一层全连接层,输出维度是2,激活函数为None,即不使用激活函数
        self.fc1 = paddle.nn.Linear(7, 8)
        self.fc2 = paddle.nn.Linear(8, 2)
    
    # 网络的前向计算函数
    def forward(self, inputs):
        x = self.fc1(inputs)
        x = self.drop(x)
        pred = self.fc2(x)
        return pred

四、配置参数及训练

记录日志

# 定义绘制训练过程的损失值变化趋势的方法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()

定义损失函数

损失函数使用【R-Drop:摘下SOTA的Dropout正则化策略】里的kl_loss:

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

模型训练

import paddle.nn.functional as F
y_preds = []
labels_list = []
BATCH_SIZE = 8
train_data = data_X
test_data = data_Y
compute_kl_loss = kl_loss()

def train(model):
    print('start training ... ')
    # 开启模型训练模式
    model.train()
    EPOCH_NUM = 5
    train_num = 0
    scheduler = paddle.optimizer.lr.CosineAnnealingDecay(learning_rate=0.0025, T_max=int(traindatas.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包含50条数据
        mini_batches = [train_data[k: k+BATCH_SIZE] for k in range(0, len(train_data), BATCH_SIZE)]
        for batch_id, data in enumerate(mini_batches):
            features_np = np.array(data[:, :8], np.float32)
            labels_np = np.array(data[:, -1:], np.float32)

            features = paddle.to_tensor(features_np)
            labels = paddle.to_tensor(labels_np)

            #前向计算
            y_pred1 = model(features)
            y_pred2 = model(features)
            cost = compute_kl_loss(y_pred1, y_pred2, label=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)
start training ... 
Pass:0,Cost:21.61302
Pass:0,Cost:1.52660
Pass:0,Cost:0.92551
Pass:0,Cost:0.15430
Pass:1,Cost:0.23122
Pass:1,Cost:0.51800
Pass:1,Cost:0.49038
Pass:1,Cost:0.15809
Pass:2,Cost:0.20701
Pass:2,Cost:0.77729
Pass:2,Cost:0.12236
Pass:2,Cost:0.30246
Pass:3,Cost:0.33689
Pass:3,Cost:0.14041
Pass:3,Cost:0.10311
Pass:3,Cost:0.40102
Pass:4,Cost:0.10742
Pass:4,Cost:0.14190
Pass:4,Cost:0.38251
Pass:4,Cost:0.18503

绘制趋势曲线

import matplotlib
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline

draw_train_process(train_nums, train_costs)

在这里插入图片描述

五、保存预测结果

模型预测:

predict_result = []
for infer_feature in testdata:
    # print(infer_feature)
    infer_feature = paddle.to_tensor(np.array(infer_feature, dtype='float32'))
    result = model(infer_feature)
    # print(result)
    predict_result.append(result)

将结果写入.CSV文件中:

import os
import pandas as pd

id_list = [item for item in range(1, 10001)]
label_list = []
csv_file = 'submission.csv'

for item in range(len(id_list)):
    label = np.argmax(predict_result[item])
    label_list.append(label)

data = {'id':id_list, 'ret':label_list}
df = pd.DataFrame(data)
in range(len(id_list)):
    label = np.argmax(predict_result[item])
    label_list.append(label)

data = {'id':id_list, 'ret':label_list}
df = pd.DataFrame(data)
df.to_csv(csv_file, index=False, encoding='utf8')

提交结果文件:

六、改进思路

  1. 可以考虑设计更复杂的模型来提高模型准确度
  2. 目前只使用了数据集中的7个特征,可以尝试对还未利用的特征进行编码
  3. 目前进行训练的数据较少,可以尝试进行数据增强

作者简介

北京联合大学 机器人学院 自动化专业 2018级 本科生 郑博培

中国科学院自动化研究所复杂系统管理与控制国家重点实验室实习生

百度飞桨开发者技术专家 PPDE

百度飞桨北京领航团团长

百度飞桨官方帮帮团、答疑团成员

深圳柴火创客空间 认证会员

百度大脑 智能对话训练师

阿里云人工智能、DevOps助理工程师

我在AI Studio上获得至尊等级,点亮10个徽章,来互关呀!!!

https://aistudio.baidu.com/aistudio/personalcenter/thirdview/147378

Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐