sklearn 随机森林的应用 鼠标轨迹风控

使用随机森林算法 对鼠标轨迹进行人机风控

简介

随机森林 (Random Forest) 是一种基于决策树的集成学习方法, 由多个独立训练的决策树组成, 能够显著提升模型的性能和稳定性. 它通过引入随机性, 增强了模型的泛化能力. 随机森林通常用于分类和回归问题.

本文使用随机森林对鼠标轨迹的特征进行分类并预测.

输入鼠标轨迹格式为 (TS精确到毫秒, 整数):

[[X1, Y1, TS1], [X2, Y2, TS2], [X3, Y3, TS3] ......]

正文

轨迹特征

从鼠标轨迹中, 我们一般可以提取出如下有效的特征:

  1. 总用时

  2. 总距离

  3. 速度 (平均速度, 最大速度, 最小速度, 速度方差, 波动特征)

  4. 加速度 (平均加速度, 最大加速度, 最小加速度, 加速度方差)

  5. 方向变化 (平均方向变化, 方向变化方差)

  6. 轨迹的直线性, 曲率平均值

  7. 停顿 (时间, 次数)

  8. 轨迹平滑度

代码如下:

import numpy as np

# 提取特征
def ExtractFeatures(MT: list[list[int]]):
    X = np.array([i[0] for i in MT])
    Y = np.array([i[1] for i in MT])
    Time = np.array([i[2] for i in MT])

    # 时间差
    TimeDiff = np.diff(Time)
    TimeDiff[TimeDiff == 0] = 1

    # 距离
    Distances = np.sqrt(np.diff(X)**2 + np.diff(Y)**2)
    
    # 速度 (距离除以时间差)
    Speed = Distances / TimeDiff
    
    # 加速度 (速度的变化除以时间差)
    Acceleration = np.diff(Speed) / TimeDiff[1:] if len(Speed) > 1 else np.array([0])
    
    # 方向变化
    DirectionChanges = np.diff(np.arctan2(np.diff(Y), np.diff(X)))

    # 特征集合
    return {
        'total_time': Time[-1] - Time[0],  # 总时间
        'total_distance': np.sum(Distances),  # 总距离
        'average_speed': np.mean(Speed),  # 平均速度
        'max_speed': np.max(Speed),  # 最大速度
        'min_speed': np.min(Speed),  # 最小速度
        'speed_variance': np.var(Speed),  # 速度方差
        'average_acceleration': np.mean(Acceleration),  # 平均加速度
        'max_acceleration': np.max(Acceleration),  # 最大加速度
        'min_acceleration': np.min(Acceleration),  # 最小加速度
        'acceleration_variance': np.var(Acceleration),  # 加速度方差
        'direction_change_mean': np.mean(DirectionChanges),  # 平均方向变化
        'direction_change_variance': np.var(DirectionChanges),  # 方向变化方差
        'linearity': np.sqrt((X[-1] - X[0]) ** 2 + (Y[-1] - Y[0]) ** 2) / np.sum(Distances),  # 轨迹的直线性
        'curvature_mean': np.mean(np.abs(DirectionChanges)),  # 曲率平均值
        'pause_time': np.sum(TimeDiff[Speed < 1e-5]), # 停顿时间
        'pause_count': np.sum(Speed < 1e-5), # 停顿次数
        'speed_fluctuation': np.sum(np.abs(np.diff(Speed))),  # 速度波动特征
        'smoothness': np.var(np.abs(DirectionChanges)), # 轨迹平滑度
    }

数据集

可使用 2017大数据挑战赛 中提供的鼠标轨迹训练数据, 也可自行制作数据集

完整代码

from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, accuracy_score
import numpy as np
import pandas as pd
import joblib
import json

# 提取特征
def ExtractFeatures(MT: list[list[int]]):
    X = np.array([i[0] for i in MT])
    Y = np.array([i[1] for i in MT])
    Time = np.array([i[2] for i in MT])

    # 时间差
    TimeDiff = np.diff(Time)
    TimeDiff[TimeDiff == 0] = 1

    # 距离
    Distances = np.sqrt(np.diff(X)**2 + np.diff(Y)**2)
    
    # 速度 (距离除以时间差)
    Speed = Distances / TimeDiff
    
    # 加速度 (速度的变化除以时间差)
    Acceleration = np.diff(Speed) / TimeDiff[1:] if len(Speed) > 1 else np.array([0])
    
    # 方向变化
    DirectionChanges = np.diff(np.arctan2(np.diff(Y), np.diff(X)))

    # 特征集合
    return {
        'total_time': Time[-1] - Time[0],  # 总时间
        'total_distance': np.sum(Distances),  # 总距离
        'average_speed': np.mean(Speed),  # 平均速度
        'max_speed': np.max(Speed),  # 最大速度
        'min_speed': np.min(Speed),  # 最小速度
        'speed_variance': np.var(Speed),  # 速度方差
        'average_acceleration': np.mean(Acceleration),  # 平均加速度
        'max_acceleration': np.max(Acceleration),  # 最大加速度
        'min_acceleration': np.min(Acceleration),  # 最小加速度
        'acceleration_variance': np.var(Acceleration),  # 加速度方差
        'direction_change_mean': np.mean(DirectionChanges),  # 平均方向变化
        'direction_change_variance': np.var(DirectionChanges),  # 方向变化方差
        'linearity': np.sqrt((X[-1] - X[0]) ** 2 + (Y[-1] - Y[0]) ** 2) / np.sum(Distances),  # 轨迹的直线性
        'curvature_mean': np.mean(np.abs(DirectionChanges)),  # 曲率平均值
        'pause_time': np.sum(TimeDiff[Speed < 1e-5]), # 停顿时间
        'pause_count': np.sum(Speed < 1e-5), # 停顿次数
        'speed_fluctuation': np.sum(np.abs(np.diff(Speed))),  # 速度波动特征
        'smoothness': np.var(np.abs(DirectionChanges)), # 轨迹平滑度
    }

Trajectories = []
Tags = []

# 加载的是2017大数据挑战赛的数据
for i in open('dataset\\Mouse1.txt', 'rb').read().decode().splitlines():
    if ';' in i:
        Tmp = [[int(a.split(',')[0]), int(a.split(',')[1]), int(a.split(',')[2])] for a in i.split()[1].split(';') if ',' in a]
        if len(Tmp) > 5:
            Trajectories.append(Tmp)
            Tags.append(int(i.split()[3]))

# 加载的是自己的数据
for i in json.loads(open('dataset\\Mouse2.json', 'rb').read()):
    if len(i) > 40:
        Trajectories.append(i)
        Tags.append(1)

FeaturesFrame = pd.DataFrame([ExtractFeatures(i) for i in Trajectories])

print(FeaturesFrame.head())

X_train, X_test, y_train, y_test = train_test_split(FeaturesFrame, np.array(Tags), test_size=0.3, random_state=42)

# 训练模型
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)

# 评估模型
y_pred = model.predict(X_test)

print(y_pred)
print("Accuracy:", accuracy_score(y_test, y_pred))
print("Classification Report:\n", classification_report(y_test, y_pred))

# 保存模型
joblib.dump(model, 'trained/MouseModel.pkl')

注: len(Tags) == len(Trajectories) 且 Trajectories[i]的标签应为Tags[i]

这惊人的ACC和Precision

源码&模型下载

Model.zip

Comment