PowerLZY's Blog

本博客主要用于记录个人学习笔记(测试阶段)

[PyTorch 学习笔记] DataLoader 与 DataSet

人民币 二分类

实现 1 元人民币和 100 元人民币的图片二分类。前面讲过 PyTorch 的五大模块:数据、模型、损失函数、优化器和迭代训练。

数据模块又可以细分为 4 个部分:

  • 数据收集:样本和标签。
  • 数据划分:训练集、验证集和测试集
  • 数据读取:对应于 PyTorch 的 DataLoader。其中 DataLoader 包括 Sampler 和 DataSet。Sampler 的功能是生成索引, DataSet 是根据生成的索引读取样本以及标签。
  • 数据预处理:对应于 PyTorch 的 transforms

img

一、DataLoader 与 DataSet

1.1 torch.utils.data.DataLoader()

1
torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, batch_sampler=None, num_workers=0, collate_fn=None, pin_memory=False, drop_last=False, timeout=0, worker_init_fn=None, multiprocessing_context=None)

功能:构建可迭代的数据装载器

  • dataset: Dataset 类,决定数据从哪里读取以及如何读取
  • batchsize: 批大小
  • num_works:num_works: 是否多进程读取数据
  • sheuffle: 每个 epoch 是否乱序
  • drop_last: 当样本数不能被 batchsize 整除时,是否舍弃最后一批数据

1.2 Epoch, Iteration, Batchsize

  • Epoch: 所有训练样本都已经输入到模型中,称为一个 Epoch
  • Iteration: 一批样本输入到模型中,称为一个 Iteration
  • Batchsize: 批大小,决定一个 iteration 有多少样本,也决定了一个 Epoch 有多少个 Iteration

1.3 torch.utils.data.Dataset

功能:Dataset 是抽象类,所有自定义的 Dataset 都需要继承该类,并且重写__getitem()__方法和__len__()方法__getitem()__方法的作用是接收一个索引,返回索引对应的样本和标签,这是我们自己需要实现的逻辑。__len__()方法是返回所有样本的数量。

数据读取包含 3 个方面:

  • 读取哪些数据:每个 Iteration 读取一个 Batchsize 大小的数据,每个 Iteration 应该读取哪些数据。
  • 从哪里读取数据:如何找到硬盘中的数据,应该在哪里设置文件路径参数
  • 如何读取数据:不同的文件需要使用不同的读取方法和库。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class RMBDataset(Dataset):  
def __init__(self, data_dir, transform=None):
"""
rmb面额分类任务的Dataset
:param data_dir: str, 数据集所在路径
:param transform: torch.transform,数据预处理
"""
# data_info存储所有图片路径和标签,在DataLoader中通过index读取样本
self.data_info = self.get_img_info(data_dir)
self.transform = transform

@staticmethod
def get_img_info(data_dir):
data_info = list()
# data_dir 是训练集、验证集或者测试集的路径
for root, dirs, _ in os.walk(data_dir):
# 遍历类别
# dirs ['1', '100']
for sub_dir in dirs:
# 文件列表
img_names = os.listdir(os.path.join(root, sub_dir))
# 取出 jpg 结尾的文件
img_names = list(filter(lambda x: x.endswith('.jpg'), img_names))
# 遍历图片
for i in range(len(img_names)):
img_name = img_names[i]
# 图片的绝对路径
path_img = os.path.join(root, sub_dir, img_name)
# 标签,这里需要映射为 0、1 两个类别
label = rmb_label[sub_dir]
# 保存在 data_info 变量中
data_info.append((path_img, int(label)))
return data_info

def __getitem__(self, index):
# 通过 index 读取样本
path_img, label = self.data_info[index]
# 注意这里需要 convert('RGB')
img = Image.open(path_img).convert('RGB') # 0~255
if self.transform is not None:
img = self.transform(img) # 在这里做transform,转为tensor等等
# 返回是样本和标签
return img, label

# 返回所有样本的数量
def __len__(self):
return len(self.data_info)
  • 图片的路径和对应的标签:实现读取数据的 Dataset,编写一个get_img_info()方法,读取每一个图片的路径和对应的标签,组成一个元组,再把所有的元组作为 list 存放到self.data_info变量中,这里需要注意的是标签需要映射到 0 开始的整数: rmb_label = {"1": 0, "100": 1}
  • 然后在Dataset 的初始化函数中调用get_img_info()方法。
  • 索引:然后在__getitem__()方法中根据index 读取self.data_info中路径对应的数据,并在这里做 transform 操作,返回的是样本和标签。
  • 长度:在__len__()方法中返回self.data_info的长度,即为所有样本的数量。

train_lenet.py中,分 5 步构建模型。

  • 首先定义训练集、验证集、测试集的路径,定义训练集和测试集的transforms。然后构建训练集和验证集的RMBDataset对象,把对应的路径和transforms传进去。再构建DataLoder,设置 batch_size,其中训练集设置shuffle=True,表示每个 Epoch 都打乱样本。
1
2
3
4
5
6
# 构建MyDataset实例train_data = RMBDataset(data_dir=train_dir, transform=train_transform)valid_data = RMBDataset(data_dir=valid_dir, transform=valid_transform)

# 构建DataLoder
# 其中训练集设置 shuffle=True,表示每个 Epoch 都打乱样本
train_loader = DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
valid_loader = DataLoader(dataset=valid_data, batch_size=BATCH_SIZE)
  • 第 2 步构建模型,这里采用经典的 Lenet 图片分类网络。
1
2
net = LeNet(classes=2)
net.initialize_weights()
  • 第 3 步设置损失函数,这里使用交叉熵损失函数
1
criterion = nn.CrossEntropyLoss()
  • 第 4 步设置优化器。这里采用 SGD 优化器
1
2
3
optimizer = optim.SGD(net.parameters(), lr=LR, momentum=0.9) # 选择优化器
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)
# 设置学习率下降策略
  • 第 5 步迭代训练模型,在每一个 epoch 里面,需要遍历 train_loader 取出数据,每次取得数据是一个 batchsize 大小。这里又分为 4 步。

    • 前向传播

    • 反向传播求导

    • 使用optimizer更新权重

    • 统计训练情况。每一个 epoch 完成时都需要使用scheduler更新学习率,和计算验证集的准确率、

      loss。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      for epoch in range(MAX_EPOCH):

      loss_mean = 0.
      correct = 0.
      total = 0.

      net.train()
      # 遍历 train_loader 取数据
      for i, data in enumerate(train_loader):

      # forward
      inputs, labels = data
      outputs = net(inputs)

      # backward
      optimizer.zero_grad()
      loss = criterion(outputs, labels)
      loss.backward()

      # update weights
      optimizer.step()

      # 统计分类情况
      _, predicted = torch.max(outputs.data, 1)
      total += labels.size(0)
      correct += (predicted == labels).squeeze().sum().numpy()

      # 打印训练信息
      loss_mean += loss.item()
      train_curve.append(loss.item())
      if (i+1) % log_interval == 0:
      loss_mean = loss_mean / log_interval
      print("Training:Epoch[{:0>3}/{:0>3}] Iteration[{:0>3}/{:0>3}] Loss: {:.4f} Acc:{:.2%}".format(
      epoch, MAX_EPOCH, i+1, len(train_loader), loss_mean, correct / total))
      loss_mean = 0.

      scheduler.step() # 更新学习率
      # 每个 epoch 计算验证集得准确率和loss

我们可以看到每个 iteration,我们是从train_loader中取出数据的。

1
2
3
4
5
def __iter__(self):
if self.num_workers == 0:
return _SingleProcessDataLoaderIter(self)
else:
return _MultiProcessingDataLoaderIter(self)

这里我们没有设置多进程,会执行_SingleProcessDataLoaderIter的方法。我们以_SingleProcessDataLoaderIter为例。在_SingleProcessDataLoaderIter里只有一个方法_next_data(),如下:

==1.4 _SingleProcessDataLoaderIter单进程==

1
2
3
4
5
6
def _next_data(self):
index = self._next_index() # may raise StopIteration
data = self._dataset_fetcher.fetch(index) # may raise StopIteration
if self._pin_memory:
data = _utils.pin_memory.pin_memory(data)
return data

在该方法中,self._next_index()获取一个 batchsize 大小的 index 列表,代码如下:

1
2
def _next_index(self):
return next(self._sampler_iter) # may raise StopIteration

其中调用的sample类__iter__()方法返回 batch_size 大小的随机 index 列表

1
2
3
4
5
6
7
8
9
def __iter__(self):
batch = []
for idx in self.sampler:
batch.append(idx)
if len(batch) == self.batch_size:
yield batch
batch = []
if len(batch) > 0 and not self.drop_last:
yield batch

然后再返回看 dataloader_next_data()方法,在第二行中调用了self._dataset_fetcher.fetch(index)获取数据。这里会调用_MapDatasetFetcher中的fetch()函数:

1
2
3
4
5
6
def fetch(self, possibly_batched_index):
if self.auto_collation:
data = [self.dataset[idx] for idx in possibly_batched_index]
else:
data = self.dataset[possibly_batched_index]
return self.collate_fn(data)

这里调用了self.dataset[idx],这个函数会调用dataset.__getitem__()方法获取具体的数据,所以__getitem__()方法是我们必须实现的。我们拿到的data是一个 list,每个元素是一个 tuple,每个 tuple 包括样本和标签。所以最后要使用self.collate_fn(data)把 data 转换为两个 list,第一个 元素 是样本的 batch 形式,形状为 [16, 3, 32, 32] (16 是 batch size,[3, 32, 32] 是图片像素);第二个元素是标签的 batch 形式,形状为 [16]。

所以在代码中,我们使用inputs, labels = data来接收数据。

==PyTorch 单进程数据读取流程图==

img

==1.5 _MultiProcessingDataLoaderIter(self)多进程==

[源码解析] PyTorch 分布式(2) --- 数据加载之DataLoader:https://www.cnblogs.com/rossiXYZ/p/15150504.html

总体逻辑如下:

  • 主进程把需要获取的数据 index 放入index_queue。
  • 子进程从 index_queue 之中读取 index,进行数据读取,然后把读取数据的index放入worker_result_queue。
  • 主进程的 pin_memory_thread 会从 worker_result_queue 读取数据index,依据这个index进行读取数据,进行处理,把结果放入 data_queue。
img

[PyTorch 学习笔记] Autograd 与逻辑回归

自动求导 (autograd)

在深度学习中,权值的更新是依赖于梯度的计算,因此梯度的计算是至关重要的。在 PyTorch 中,只需要搭建好前向计算图,然后利用torch.autograd自动求导得到所有张量的梯度。

torch.autograd.backward():自动求取梯度

1
torch.autograd.backward(tensors, grad_tensors=None, retain_graph=None, create_graph=False, grad_variables=None)
  • tensors: 用于求导的张量,如 loss
  • retain_graph: 保存计算图。PyTorch 采用动态图机制,默认每次反向传播之后都会释放计算图。这里设置为 True 可以不释放计算图。
  • create_graph: 创建导数计算图,用于高阶求导
  • grad_tensors: 多梯度权重。当有多个 loss 混合需要计算梯度时,设置每个 loss 的权重。

==逻辑回归 (Logistic Regression)==

逻辑回归是线性的二分类模型。模型表达式 [公式],其中 [公式][公式] 称为 sigmoid 函数,也被称为 Logistic 函数。函数曲线如下:(横坐标是 [公式],而 [公式],纵坐标是 [公式])

img

分类原则如下:class [公式]。当 [公式] 时,类别为 0;当 [公式] 时,类别为 1。

其中 [公式] 就是原来的线性回归的模型。从横坐标来看,当 [公式] 时,类别为 0;当 [公式] 时,类别为 1,直接使用线性回归也可以进行分类。逻辑回归是在线性回归的基础上加入了一个 sigmoid 函数,这是为了更好地描述置信度,把输入映射到 (0,1) 区间中,符合概率取值。

逻辑回归也被称为对数几率回归 [公式],几率的表达式为:[公式][公式] 表示正类别的概率,[公式] 表示另一个类别的概率。根据对数几率回归可以推导出逻辑回归表达式:

[公式] [公式] [公式] [公式] [公式]

==PyTorch 实现逻辑回归==

img

PyTorch 构建模型需要 5 大步骤:

  • 数据:包括数据读取,数据清洗,进行数据划分和数据预处理,比如读取图片如何预处理及数据增强。
  • 模型:包括构建模型模块,组织复杂网络,初始化网络参数,定义网络层。
  • 损失函数:包括创建损失函数,设置损失函数超参数,根据不同任务选择合适的损失函数。
  • 优化器:包括根据梯度使用某种优化器更新参数,管理模型参数,管理多个参数组实现不同学习率,调整学习率。
  • 迭代训练:组织上面 4 个模块进行反复训练。包括观察训练效果,绘制 Loss/ Accuracy 曲线,用 TensorBoard 进行可视化分析。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import numpy as np
torch.manual_seed(10)

# ============================ step 1/5 生成数据 ============================
sample_nums = 100
mean_value = 1.7
bias = 1
n_data = torch.ones(sample_nums, 2)
# 使用正态分布随机生成样本,均值为张量,方差为标量
x0 = torch.normal(mean_value * n_data, 1) + bias # 类别0 数据 shape=(100, 2)
# 生成对应标签
y0 = torch.zeros(sample_nums) # 类别0 标签 shape=(100, 1)
# 使用正态分布随机生成样本,均值为张量,方差为标量
x1 = torch.normal(-mean_value * n_data, 1) + bias # 类别1 数据 shape=(100, 2)
# 生成对应标签
y1 = torch.ones(sample_nums) # 类别1 标签 shape=(100, 1)
train_x = torch.cat((x0, x1), 0)
train_y = torch.cat((y0, y1), 0)

# ============================ step 2/5 选择模型 ============================
class LR(nn.Module):
def __init__(self):
super(LR, self).__init__()
self.features = nn.Linear(2, 1)
self.sigmoid = nn.Sigmoid()

def forward(self, x):
x = self.features(x)
x = self.sigmoid(x)
return x

lr_net = LR() # 实例化逻辑回归模型

# ============================ step 3/5 选择损失函数 ============================
loss_fn = nn.BCELoss()

# ============================ step 4/5 选择优化器 ============================
lr = 0.01 # 学习率
optimizer = torch.optim.SGD(lr_net.parameters(), lr=lr, momentum=0.9)

# ============================ step 5/5 模型训练 ============================
for iteration in range(1000):

# 前向传播
y_pred = lr_net(train_x)
# 计算 loss
loss = loss_fn(y_pred.squeeze(), train_y)
# 反向传播
loss.backward()
# 更新参数
optimizer.step()
# 清空梯度
optimizer.zero_grad()
# 绘图
if iteration % 20 == 0:
mask = y_pred.ge(0.5).float().squeeze() # 以0.5为阈值进行分类
correct = (mask == train_y).sum() # 计算正确预测的样本个数
acc = correct.item() / train_y.size(0) # 计算分类准确率

plt.scatter(x0.data.numpy()[:, 0], x0.data.numpy()[:, 1], c='r', label='class 0')
plt.scatter(x1.data.numpy()[:, 0], x1.data.numpy()[:, 1], c='b', label='class 1')

w0, w1 = lr_net.features.weight[0]
w0, w1 = float(w0.item()), float(w1.item())
plot_b = float(lr_net.features.bias[0].item())
plot_x = np.arange(-6, 6, 0.1)
plot_y = (-w0 * plot_x - plot_b) / w1

plt.xlim(-5, 7)
plt.ylim(-7, 7)
plt.plot(plot_x, plot_y)

plt.text(-5, 5, 'Loss=%.4f' % loss.data.numpy(), fontdict={'size': 20, 'color': 'red'})
plt.title("Iteration: {}\nw0:{:.2f} w1:{:.2f} b: {:.2f} accuracy:{:.2%}".format(iteration, w0, w1, plot_b, acc))
plt.legend()
# plt.savefig(str(iteration / 20)+".png")
plt.show()
plt.pause(0.5)
# 如果准确率大于 99%,则停止训练
if acc > 0.99:
break

训练的分类直线的可视化如下:

img

[PyTorch 学习笔记] 静态图与动态图机制

计算图

深度学习就是对张量进行一系列的操作,随着操作种类和数量的增多,会出现各种值得思考的问题。比如多个操作之间是否可以并行如何协同底层的不同设备,如何避免冗余的操作,以实现最高效的计算效率,同时避免一些 bug。因此产生了计算图 (Computational Graph)

计算图是用来描述运算的有向无环图,有两个主要元素:节点 (Node)边 (Edge)。节点表示数据,如向量、矩阵、张量。边表示运算,如加减乘除卷积等。

用计算图表示:[公式],如下所示:

img

可以看作, [公式] ,其中 [公式][公式]

计算图与梯度求导

这里求 [公式][公式] 的导数。根复合函数的求导法则,可以得到如下过程。

[公式]

体现到计算图中,就是根节点 [公式] 到叶子节点 [公式] 有两条路径 y -> a -> wy ->b -> w。根节点依次对每条路径的孩子节点求导,一直到叶子节点w,最后把每条路径的导数相加即可。

img

代码如下:

1
2
3
4
5
6
7
8
9
10
11
import torch
w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)
# y=(x+w)*(w+1)
a = torch.add(w, x) # retain_grad()
b = torch.add(w, 1)
y = torch.mul(a, b)
# y 求导
y.backward()
# 打印 w 的梯度,就是 y 对 w 的导数
print(w.grad)

结果为tensor([5.]),我们回顾前面说过的 Tensor 中有一个属性is_leaf标记是否为叶子节点。

img

在上面的例子中,[公式][公式] 是叶子节点,其他所有节点都依赖于叶子节点。叶子节点的概念主要是为了节省内存,在计算图中的一轮反向传播结束之后,非叶子节点的梯度是会被释放的

PyTorch 的动态图机制

PyTorch 采用的是动态图机制 (Dynamic Computational Graph),而 Tensorflow 采用的是静态图机制 (Static Computational Graph)。

动态图是运算和搭建同时进行,也就是可以先计算前面的节点的值,再根据这些值搭建后面的计算图。优点是灵活,易调节,易调试。PyTorch 里的很多写法跟其他 Python 库的代码的使用方法是完全一致的,没有任何额外的学习成本。

静态图是先搭建图,然后再输入数据进行运算。优点是高效,因为静态计算是通过先定义后运行的方式,之后再次运行的时候就不再需要重新构建计算图,所以速度会比动态图更快。但是不灵活。TensorFlow 每次运行的时候图都是一样的,是不能够改变的,所以不能直接使用 Python 的 while 循环语句,需要使用辅助函数 tf.while_loop 写成 TensorFlow 内部的形式。

Machine Learning for Malware Detection

本文总结了 kaspersky 利用机器学习为客户建立高级保护的丰富经验。

一、基本方法检测恶意软件

一个高效、健壮、可扩展的恶意软件识别模块是每个网络安全产品的关键组成部分。恶意软件识别模块会根据其在该对象上所收集的数据来决定该对象是否构成威胁。这些数据可以在不同的阶段进行收集:

  • 需要有代表性的大型数据集

    必须强调这种方法的数据驱动特性。一个创建的模型在很大程度上依赖于它在训练阶段看到的数据,以确定哪些特征与预测正确的标签有统计关联。让我们来看看为什么制作一个具有代表性的数据集如此重要。

  • 训练的模型必须是可解释的

    目前使用的大多数模型家族,如深度神经网络,都被称为黑盒模型。黑盒子模型被给予输入X,它们将通过一个难以被人类解释的复杂操作序列产生Y。这可能会在现实生活中的应用程序中带来一个问题。例如,当出现错误警报时,我们想理解为什么会发生它,我们会问这是训练集还是模型本身的问题。模型的可解释性决定了我们管理它、评估其质量和纠正其操作的容易程度。

  • 假阳性率必须非常低

    当算法将良性文件错误地标记恶意标签时,就会发生误报。我们的目标是使假阳性率尽可能低,或为零。这在机器学习应用程序中并不常见。这是很重要的,因为即使在一百万个良性文件中出现一个误报,也会给用户带来严重的后果。这是复杂的事实,有许多干净的文件在世界上,他们不断出现。

    为了解决这个问题,重要的是要对机器学习模型和指标施加高要求,并在训练期间进行优化,并明确关注低假阳性率(FPR)【FP/(FP+TN)】模型

    这还不够,因为之前看不见的新良性文件可能会被错误检测到。我们考虑到这一点,并实现了一个灵活的模型设计,允许我们动态地修复假阳性,而不需要完全重新训练模型。这些示例在我们的执行前和执行后的模型中实现,这将在以下章节中描述。

  • 算法必须允许我们快速调整它们以适应恶意软件作者的反击

    在恶意软件检测领域之外,机器学习算法经常在固定数据分布的假设下工作,这意味着它不随时间变化。当我们有一个足够大的训练集时,我们可以训练模型,以便它将有效地推理测试集中的任何新样本。随着时间的推移,该模型将继续按预期进行工作。

    在将机器学习应用于恶意软件检测后,我们必须面对我们的数据分布没有固定的事实:

    • 活跃的对手(恶意软件编写者)不断努力避免检测和发布新版本的恶意软件文件,这与在培训阶段看到的文件有显著不同。
    • 成千上万的软件公司生产的新型良性可执行文件与以前已知的可执行文件显著不同。在训练集中缺乏关于这些类型的数据,但该模型需要识别出它们是良性的。

    这就导致了数据分布的严重变化,并提出了在任何机器学习实现中检测率随着时间的推移而下降的问题。在其反恶意软件解决方案中实现机器学习的网络安全供应商面临着这个问题,并需要克服它。该体系结构需要灵活,并且必须允许在再训练(retrain)之间“动态”更新模型。供应商还必须有有效的流程来收集和标记新样本,丰富训练数据集和定期的再训练模型。

image-20220429233456832

二、卡巴斯基实验室机器学习应用

上述真实世界恶意软件检测的特性使机器学习技术的直接应用成为一项具有挑战性的任务。在将机器学习方法应用于信息安全应用方面,卡巴斯基实验室拥有近十年的经验

在执行前检测新的恶意软件的相似性哈希

在杀毒行业的初期,计算机上的恶意软件检测是基于启发式特征,识别特定的恶意软件文件:

  • 代码段
  • 代码片段或整个文件的哈希值
  • 文件属性
  • 以及这些特征的组合

我们的主要目标是创建一个可靠的恶意文件指纹——一个功能的组合,可以快速检查。在此之前,这个工作流需要通过仔细选择指示恶意软件的代表性字节序列或其他特征来手动创建检测规则。在检测过程中,产品中的抗病毒引擎针对存储在防病毒数据库中的已知恶意软件指纹,检查文件中是否存在恶意软件指纹。

然而,恶意软件编写者发明了像服务器端多态性这样的技术。这导致每天都有成千十万的恶意样本被发现。同时,所使用的指纹对文件中的微小变化也很敏感。现有恶意软件的微小变化使它失去了雷达注意。前一种方法很快就变得无效了,因为:

  • 手动创建检测规则无法跟上新出现的恶意软件流。
  • 针对已知恶意软件库检查每个文件的指纹意味着,在分析人员手动创建检测规则之前,您才能检测到新的恶意软件。

我们感兴趣的是那些对文件中的小变化具有鲁棒性的特性。这些特性将检测到恶意软件的新修改,但不需要更多的资源来进行计算。性能和可伸缩性是反恶意软件引擎处理的第一阶段的关键优先事项。

为了解决这个问题,我们专注于提取以下特性:

  • 快速计算,如从文件字节内容或代码反汇编导出的统计数据
  • 直接从可执行文件的结构中检索,比如文件格式描述

使用这些数据,我们计算了一种特定类型的哈希函数,称为局部敏感哈希(LSH)。

ssdeep, TLSN 局部哈希

两个几乎相同的文件的常规加密哈希和两个非常不同的文件的哈希差异一样大。文件的相似性和哈希值之间没有联系。然而,几乎相同的文件的LSHs映射到相同的二进制桶——它们的LSHs非常相似——而且概率非常高。两个不同文件的LSHs有很大差异。

LSH的基本思想是:将原始数据空间中的两个相邻数据点通过相同的映射或投影变换(projection)后,这两个数据点在新的数据空间中仍然相邻的概率很大,而不相邻的数据点被映射到同一个桶的概率很小。

  • https://colobu.com/2018/08/16/locality-sensitive-hashing/

image-20220429233512068

但我们走得更远。LSH的计算是无监督的。它没有考虑到我们对每个样本都是恶意软件或良性的额外知识。

有一个相似和非相似对象的数据集,我们通过引入一个训练阶段来增强了这种方法。我们实现了一种相似性哈希方法。它类似于LSH,但它是==有监督==的,并且能够利用关于相似和非相似对象对的信息。在这种情况下:

  • 我们的训练数据X将是一对文件特征表示[X1, X2]
  • Y将是一个可以告诉我们这些物体在语义上是否真的相似的标签。
  • 在训练过程中,该算法拟合哈希映射h(X)的参数,以最大化训练集中的对数,其中h(X1)和h(X2)对于相似的对象是相同的,而对于其他的对象是不同的。

该算法正在应用于可执行文件特性,它提供了具有有用检测功能的特定相似性哈希映射。事实上,我们训练了这种映射的几个版本,它们对不同特征集的局部变化的敏感性不同。例如,一个版本的相似性散列映射可以更专注于捕获可执行文件结构,而较少关注实际内容。另一种方法可能更专注于捕获文件中的ascii字符串

这就抓住了这样一种观点,即不同的特性子集可能或多或少可以区分不同类型的恶意软件文件。对于其中一个,文件内容统计数据可能显示未知恶意包装器的存在。对于其他方面,关于潜在行为的最重要信息集中在表示已使用的OSAPI、已创建的文件名、已访问的url或其他特性子集的字符串中。为了更精确的产品检测,将相似度哈希算法的结果与其他基于机器学习的检测方法相结合。

基于局部敏感哈希(有监督)+ 决策树集成的用户计算机两阶段预执行检测

为了分析在预执行阶段的文件,我们的产品将相似性哈希方法与其他训练过的算法结合在一个两阶段的方案中。为了训练这个模型,我们使用了大量我们知道是恶意软件和良性的文件。

两阶段分析设计解决了减少用户系统的==计算负载==和==防止误报==的问题。一些对检测很重要的文件特性需要更大的计算资源来计算它们。这些功能被称为“重的”。为了避免对所有扫描文件的计算,我们引入了一个称为预检测的初步阶段。当使用“轻量级”特性分析文件,并在系统上没有大量负荷的情况下提取文件时,就会发生==预检测==。在许多情况下,预检测为我们提供了足够的信息,以知道一个文件是否是良性的,并结束了文件扫描。有时它甚至会检测到一个文件是恶意软件。如果第一阶段不够,则文件将进入第二阶段的分析,即提取“重”特征以进行精确检测。

在我们的产品中,两阶段分析的工作方式如下。在预检测阶段,对扫描文件的轻量级特征计算学习到的相似度哈希映射。然后,检查是否有其他文件具有相同的哈希映射,以及它们是恶意软件还是良性的。一组具有类似哈希映射值的文件被称为哈希桶。根据扫描文件所属的散列桶,可能会出现以下结果:

  • 在一个简单的区域案例中,文件落入一个只包含一种对象的桶中:恶意软件或良性的。如果一个文件落入一个“纯恶意软件桶”,我们会检测到它是恶意软件。如果它落入一个“纯良性的桶”,我们不会扫描它更深。在这两种情况下,我们都不提取任何新的“重”特性。

  • 在硬区域中,哈希桶同时包含恶意软件和良性文件。这是系统唯一可以从扫描文件中提取“重”特征以进行精确检测的情况。对于每个硬区域,都有一个单独的特定区域的分类器训练。目前,我们使用决策树集成或基于“重”特征的相似性哈希,这取决于哪个队硬区域更有效。

image-20220429233534859

对象空间的分割

使用相似性散列映射创建的对象空间的分割的示意图表示。为简单起见,该插图只有两个维度。每个单元格的一个索引对应于特定的相似度哈希映射值。网格中的每个单元格都说明了一个具有相同相似性哈希映射值的对象区域,也称为哈希桶。点颜色:恶意(红色)和良性/未知(绿色)。有两种选项可用的:将一个区域的散列添加到恶意软件数据库(简单区域)中,或者将其作为两阶段检测器的第一部分,并与特定区域的分类器(硬区域)结合使用。

在现实中,有一些困难的区域不适合用这种两阶段的技术进行进一步的分析,因为它们包含了太多流行的良性文件。用这种方法处理它们会产生假阳性和性能下降的高风险。对于这种情况,我们不训练特定区域的分类器,也不通过该模型扫描该区域中的文件。为了在这样的区域进行正确的分析,我们使用了其他的检测技术

image-20220429233555509

预检测阶段的实现大大减少了在第二步中被大量扫描的文件的数量。这个过程提高了性能,因为在预检测阶段通过相似哈希映射查找可以快速完成。

我们的两阶段设计也降低了假阳性的风险:

  • 在第一个(预检测)阶段,我们不能在假阳性风险较高的区域使用特定区域的分类器进行检测。正因为如此,传递到第二阶段的对象的分布偏向于“恶意软件”类。这也降低了假阳性率。
  • 在第二阶段,每个硬区域的分类器只从一个桶上对恶意软件进行训练,但在训练集的所有桶中可用的所有干净对象上。这使得区域分类器能够更精确地检测特定硬区域桶的恶意软件。当模型在具有真实数据的产品中工作时,它还可以防止任何意外的假阳性。

两阶段模型的可解释性来自于数据库中的每个散列都与训练中的一些恶意软件样本子集相关联。整个模型可以通过添加检测来适应一个新的恶意软件流,包括散列映射和一个以前未观察到的区域的树集成模型。这允许我们撤销和重新训练特定区域的分类器,而不显著降低整个产品的检测率。如果没有这个,我们将需要对整个模型重新培训所有我们知道的恶意软件,我们想要做的每一个改变。话虽如此,两阶段恶意软件检测适用于在介绍中讨论的机器学习的细节。

三、针对罕见攻击的深度学习

通常,当恶意和良性样本在训练集中大量表示时,机器学习会面对任务。但是有些攻击是如此罕见,以至于我们只有一个恶意软件进行训练的例子。这是针对性高调的有针对性攻击的典型情况。在这种情况下,我们使用了一个非常特定的基于深度学习的模型架构。我们将这种方法称为exemplar network( ExNet)

image-20220429233615467

这里的想法是,我们训练模型来构建输入特征的紧凑表示。然后,我们使用它们来同时训练多个单范例的分类器(per-exemplar classifiers)——这些都是检测特定类型的恶意软件的算法。深度学习允许我们将这些多个步骤(对象特征提取、紧凑的特征表示和局部的,或每个范例的模型创建)结合到一个神经网络管道中,它可以提取各种类型的恶意软件的鉴别特征。

该模型可以有效地推广关于单个恶意软件样本和大量干净样本收集的知识。然后,它可以检测到相应的恶意软件的新修改。

四、在执行后行为检测中的深度学习

前面描述的方法是在静态分析的框架中考虑的,即在真实用户环境中执行对象之前提取和分析对象描述。

执行阶段的静态分析有许多显著的优势。其主要优点是它对用户来说是安全的。一个对象可以在它开始作用于真实用户的机器之前被检测到。但它面临着高级加密、混淆技术以及使用各种高级脚本语言、容器和无文件攻击场景的问题。这些情况都是当执行后的行为检测开始发挥作用的情况。

我们还使用深度学习方法来解决行为检测的任务。在执行后阶段,我们正在使用威胁行为引擎提供的行为日志。行为日志是在进程执行过程中发生的系统事件的序列,以及相应的参数。为了检测观察到的日志数据中的恶意活动,我们的模型将得到的事件序列压缩为一组二进制向量。然后,它训练一个深度神经网络来区分干净的和恶意的日志。

image-20220429233634048

日志的压缩阶段包括以下几个步骤:

  • 将该日志转换为一个二部行为图。此图包含两种类型的顶点:事件和参数。在每个事件和参数之间绘制边,它们一起出现在日志中的同一行中。这样的图表示比初始的原始数据要紧凑得多。它对跟踪同一多处理程序的不同运行或分析过程的行为混淆所导致的任何行排列保持鲁棒性

  • 之后,我们将自动从该图中提取特定的子图或行为模式。每个模式都包含与进程的特定活动相关的事件和相邻参数的子集,如网络通信、文件系统探索、系统寄存器的修改等

  • 我们将每个“行为模式”压缩为一个稀疏的二进制向量。此向量的每个组件负责在模板中包含一个特定的事件或参数的令牌(与web、文件和其他类型的活动相关)。

  • 训练后的深度神经网络将行为模式的稀疏二进制向量转换为称为模式嵌入的紧凑表示。然后将它们组合成一个单个向量,或进行日志嵌入

  • 最后,在日志嵌入的基础上,网络预测了日志的可疑性。

所使用的神经网络的主要特征是所有的权值都是正的,所有的激活函数都是单调的。这些特性为我们提供了许多重要的优势:

  • 在处理日志中的新行时,我们的模型的怀疑分数输出只随着时间的推移而增长。因此,恶意软件不能通过与它的主有效负载并行地执行额外的噪声或“干净的”活动来逃避检测
  • 由于模型的输出在时间上是稳定的,我们可能会免受由于在扫描干净日志时的预测波动而导致的最终错误警报。
  • 在单调空间中处理日志样本,允许我们自动选择导致检测的事件,并更方便地管理假警报

这样的方法使我们能够训练一个能够使用高级可解释的行为概念进行操作的深度学习模型。这种方法可以安全地应用于整个用户环境的多样性,并在其架构中集成了假告警修复能力。总之,所有这些都为我们提供了一种对行为检测最复杂的现代威胁的有力手段。

五、基础设施中的应用(Malware Hunter)

从有效处理卡巴斯基实验室的恶意软件流到维护大规模检测算法,机器学习在建立适当的实验室基础设施中发挥着同样重要的作用

5.1 聚类传入的对象流

每天都有成千上万的样本进入卡巴斯基实验室,同时人工对新型样本进行注释的高昂成本,减少分析师需要查看的数据量成为一项至关重要的任务。使用有效的聚类算法,我们可以从难以忍受的独立的未知文件数量增加到合理数量的对象组。这些对象组的部分将根据其中已注释的对象自动处理。

image-20220429232153712

所有最近收到的传入文件都通过我们的实验室恶意软件检测技术进行分析,包括执行前和执行后。我们的目标是标记尽可能多的对象,但有些对象仍然未分类。我们想给它们贴上标签。为此,所有的对象,包括已标记的对象,都由多个特征提取器进行处理。然后,根据文件类型,它们通过几种聚类算法(例如k-means和dbscan)一起传递。这将产生类似的对象组。

在本文的这一点上,我们将面对四种不同类型的具有未知文件的结果集群:

  1. 包含恶意软件和未知文件的集群;
  2. 包含干净和未知文件的集群;
  3. 包含恶意软件、干净和未知文件的集群;
  4. 仅包含未知文件的集群。

对于类型1-3簇中的对象,我们使用额外的机器学习算法,如贝叶斯网络(belief propagation)来验证未知样本与分类样本的相似性。在某些情况下,这甚至在第3类集群中也是有效的。这使我们能够自动标记未知文件,只为人类留下4型和部分3型的集群。这导致了每天所需的人类注释的急剧减少。

蒸馏工艺:包装更新内容

我们在实验室中检测恶意软件的方式不同于针对用户产品的最佳算法。一些最强大的分类模型需要大量的资源,如CPU/GPU的时间和内存,以及昂贵的特性提取器。

例如,由于大多数现代恶意软件编写者使用高级包装器和混淆器来隐藏有效负载功能,机器学习模型确实受益于使用具有高级行为日志的实验室内沙箱的执行日志。同时,在用户的机器上的预执行阶段收集这类日志的计算量可能会很高。它可能会导致显著的系统性能下降。

对于加壳的恶意样本,需要沙箱分析,在用户终端

在实验室中保存和运行那些“重型”模型更有效。一旦我们知道一个特定的文件是恶意软件,我们就会使用我们从模型中获得的知识来训练将在我们的产品中工作的轻量级分类器(to 用户)。

image-20220429232807391

在机器学习中,这个过程被称为蒸馏。我们用它来教我们的产品检测新的恶意软件:

  • 在我们的实验室中,我们首先从标记的文件中提取一些耗时的特征,并对它们训练一个“沉重的”在实验室内的模型。
  • 我们取一个未知文件集群,并使用我们的“重”实验室模型来对它们进行标签。
  • 然后,我们使用新标记的文件来扩充轻量级分类模型的训练集。
  • 我们向用户的产品提供了这个轻量级的模型。

蒸馏使我们能够有效地输出我们的知识的新的和未知的威胁给我们的用户。

总结

将常规任务传递给算法会给我们留下更多的时间来研究和创建。这使我们能够为客户提供更好的保护。通过我们的努力、失败和胜利,我们已经了解到什么对于让机器学习对恶意软件检测产生它的卓越影响是重要的。

亮点:
  • 有正确的数据:这是机器学习的燃料。这些数据必须具有代表性,与当前的恶意软件环境相关,并在需要时正确标记。我们成为了在提取和准备数据以及训练我们的算法方面的专家。我们用数十亿个文件样本进行了有效的收集,以增强机器学习的能力。

  • 了解理论机器学习以及如何将其应用于网络安全。我们了解机器学习是如何工作的,并跟踪该领域出现的最先进的方法。另一方面,我们也是网络安全方面的专家,我们认识到每一种创新的理论方法给网络安全实践带来的价值。

  • 了解用户的需求,并成为将机器学习实现到帮助用户满足其实际需求的产品中的关键。我们使机器学习有效和安全地工作。我们建立了网络安全市场所需要的创新解决方案

  • 建立一个足够的用户基础:这引入了“群众”检测质量的力量,并给我们需要的反馈,告诉我们是对是错。

  • 保持检测方法的多层协同作用。只要当今的先进威胁攻击载体如此多样化,网络安全解决方案就应该提供多层的保护。在我们的产品中,基于机器学习的检测与其他类型的检测协同工作,以一种多层的现代网络安全保护方法进行工作。

机器学习(一):模型训练及线上部署相关笔记

https://www.shangmayuan.com/a/2331a95928b344d782ac08ac.html

  • GBDT+LR模型训练及线上部署
  • Java调用jpmml类

一、离线模型(Offline)

离线模型存在于很多业务场景中,其中最常见的业务场景就是用在推荐系统的召回阶段,由于在推荐系统中,召回并不要求是实时的,可以根据业务的需要,调整成每天一次,或者每几个小时跑一次即可,因此,这类的模型,一般我们只需要使用Linux下的crontab定时任务脚本,每隔一段时间来启动一次就可以,然后将log文件输出到指定的文件下即可。这种方式一般来讲仅限离线模型的部署,其本质上就是一段定时任务的代码。

二、在线(Online)/近似在线(NearLine)模型

在生产系统中,实时推理和预测是最常见的需求,也是对于很多深度学习模型来说所必须达到的点。下面简介一些深度学习模型在实时预测时常见的几种部署方法:

2.1 将模型预测直接打包成http接口

将模型直接打包成一个http接口的形式是在企业中比较常见的模型上线的方式,所谓的将预测直接打包成http接口实际上一般是指将我们训练好的模型直接在线上进行预测。我们来试想一个场景,当一个模型训练好之后,我们如果想要验证这个模型的好坏,我们首先能想到的办法就是找一批数据来测试一下。实际上,将模型预测直接打包成http接口也是利用了这样的思路。

在这里,我们可以将训练好的模型提前进行加载,并初始化若干个消息队列和worker,当有新的待预测数据进入的时候,我们直接将数据通过消息队列传入到模型中进行推理和预测,最终得到结果。

对于外层接收输入,我们一般可以将接收的地方使用flask打包成一个http接口,等待传入即可。

使用这种方式直接打包成http接口的好处在于打包和部署相对比较方便,对于一些相对比较轻量级且对并发量要求不是很高的情况下相对还是比较好用的。使用值得注意的是,如果对于一个相对比较大的模型来讲,这种方式推理的时间相对就会比较长,从用户输入到结果返回可能需要200ms左右。

2.2 PMML

使用PMML部署机器学习模型 : https://zhuanlan.zhihu.com/p/79197337

PMML (Predictive Model Markup Language) 是一套通用的且与平台和环境无关的模型表示语言,机器学习在模型部署方面的一种标准部署方案,其形式是采用XML语言标记形式。我们可以将自己训练的机器学习模型打包成PMML模型文件的形式,然后使用目标环境的解析PMML模型的库来完成模型的加载并做预测。PMML是一套基于XML的标准,通过 XML Schema 定义了使用的元素和属性,主要由以下核心部分组成:

数据字典(Data Dictionary),描述输入数据。

数据转换(Transformation Dictionary和Local Transformations),应用在输入数据字段上生成新的派生字段。

模型定义 (Model),每种模型类型有自己的定义。

输出(Output),指定模型输出结果。

目前,大部分机器学习库都支持直接打包成PMML模型文件的相关函数,例如在Python中的LightGBM库,XGBoost库,Keras库等,都有对PMML的支持,直接使用相应的命令就可以生成,而在Java、R等语言中,也有相关的库可以进行PMML文件生成的命令。一般来讲,使用PMML文件进行预测的过程如下:

img

由于其平台无关性,导致PMML可以实现跨平台部署,是企业中部署机器学习模型的常见解决方案。

2.3 TensorFlow Serving

使用TensorFlow Serving进行服务部署,一般需要2台以上机器。

  • 其中一台作为TensorFlow Serving的服务器,这台服务器是专门来做模型部署和预测用,对于这台服务器,一般建议使用GPU服务器,这样会使整个推理预测的过程变得很快;
  • 另外一台服务器是业务服务器,也就是接收用户的输入以及其他业务处理的服务器。我们可以把模型部署到TensorFlow Serving的服务器上,而一般我们只需要先在服务器上使用docker创建一个TensorFlow Serving服务,然后将模型文件上传上去,当有请求进来的时候,业务服务会直接对模型所在的服务器发起服务调用,并得到模型预测的结果。

三、实际用时的一些部署方法组合

3.1 R+pmml+spark+airflow调度

用R语言训练模型并转为pmml文件,然后使用spark将这个pmml文件封装为jar,使用airflow提交到yarn。

1
2
3
4
5
val is: InputStream = fs.open(path)

val pmml: PMML = PMMLUtil.unmarshal(is)

modelEvaluator = ModelEvaluatorFactory.newInstance.newModelEvaluator(pmml)

3.2 python+sklearn+airflow调度

使用python训练好sklearn模型,并joblib.dumps()保存,然后在python文件中joblib.load()加载改文件,使用airflow离线调度。

3.3 xgboost+spark+xgb4j

  • 使用分布式的spark版的xgboost,训练好的模型直接保存为model.booster.saveModel(hdfsOutStream)二进制文件.然后xgboost4j加载该文件XGBoost.loadModel(is)实现线上实时预测

3.4 tensorflow+tensorflow的java库

四、不同需求对应的不同平台部署方案

1、放到服务器上跑,要求吞吐和时延(重点是吞吐)这种应用在互联网企业居多,一般是互联网产品的后端AI计算,例如人脸验证、语音服务、应用了深度学习的智能推荐等。

由于一般是大规模部署,这时不仅仅要考虑吞吐和时延,还要考虑功耗和成本。所以除了软件外,硬件也会下功夫,比如使用推理专用的NVIDIA P4、寒武纪MLU100等。这些推理卡比桌面级显卡功耗低,单位能耗下计算效率更高,且硬件结构更适合高吞吐量的情况软件上,一般都不会直接上深度学习框架。对于NVIDIA的产品,一般都会使用TensorRT来加速(不仅可以加速前传,还包含调度功能)。TensorRT用了CUDA、CUDNN,而且还有图优化、fp16、int8量化等。

五、模型部署时一些常用的trick

5.1 模型压缩

基于参数修剪和共享的方法针对模型参数的冗余性,试图去除冗余和不重要的项。基于低秩因子分解的技术使用矩阵/张量分解来估计深度学习模型的信息参数。基于传输/紧凑卷积滤波器的方法设计了特殊的结构卷积滤波器来降低存储和计算复杂度。知识蒸馏方法通过学习一个蒸馏模型,训练一个更紧凑的神经网络来重现一个更大的网络的输出。

一般来说,参数修剪和共享,低秩分解和知识蒸馏方法可以用于全连接层和卷积层的CNN,但另一方面,使用转移/紧凑型卷积核的方法仅支持卷积层。低秩因子分解和基于转换/紧凑型卷积核的方法提供了一个端到端的流水线,可以很容易地在CPU/GPU环境中实现。相反参数修剪和共享使用不同的方法,如矢量量化,二进制编码和稀疏约束来执行任务,这导致常需要几个步骤才能达到目标。

Treelite:树模型部署加速工具(支持XGBoost、LightGBM和Sklearn)

一、TreeLite介绍

TreeLite能够树模型编译优化为单独库,可以很方便的用于模型部署。经过优化后可以将XGBoost模型的预测速度提高2-6倍。

如上图,黑色曲线为XGBoost在不同batch size下的吞吐量,红色曲线为XGBoost经过TreeLite编译后的吞吐量。

Treelite支持众多的树模型,特别是随机森林和GBDT。同时Treelite可以很好的支持XGBoost, LightGBM和 scikit-learn,也可以将自定义模型根据要求完成编译。

二、TreeLite原理

TreeLite主要在两方面完成了改进。

  • 逻辑分支

    • 这个指令是gcc引入的,作用是允许程序员将最有可能执行的分支告诉编译器。这个指令的写法为:__builtin_expect(EXP, N)。意思是:EXP==N的概率很大。【减少重新取跳转地址】__builtin_expect 说明: https://www.jianshu.com/p/2684613a300f

    • 构建树模型+ 计算好每个分支下面样本的个数+提前预知哪一个叶子节点被执行的可能性更大,进而可以提前执行子节点逻辑。

  • 逻辑比较

    • 浮点数比较,转化为整数型比较

2.1 逻辑分支

对于树模型而言,节点的分类本质使用if语句完成,而CPU在执行if语句时会等待条件逻辑的计算。

1
2
3
4
5
if ( [conditional expression] ) {
foo();
} else {
bar();
}

如果在构建树模型时候,提前计算好每个分支下面样本的个数,则可以提前预知哪一个叶子节点被执行的可能性更大,进而可以提前执行子节点逻辑。

img

借助于编译命令,可以完成逻辑计算加速。

1
2
3
4
5
6
/* expected to be false */
if( __builtin_expect([condition],0)){
...
} else {
...
}

2.2 逻辑比较

原始的分支比较可能会有浮点数比较逻辑,可以量化为数值比较逻辑。

1
2
3
4
5
6
7
8
9
if (data[3].fvalue < 1.5) {  
/* floating-point comparison */
...
}

if (data[3].qvalue < 3) {
/* integer comparison */
...
}

参考文献

模型部署优化的学习路线是什么?

  • https://segmentfault.com/a/1190000040491572

模型部署优化这个方向其实比较宽泛。从模型完成训练,到最终将模型部署到实际硬件上,整个流程中会涉及到很多不同层面的工作,每一个环节对技术点的要求也不尽相同。

部署的流程大致可以分为以下几个环节:

模型部署流程

一、模型转换

从训练框架得到模型后,根据需求转换到相应的模型格式。模型格式的选择通常是根据公司业务端 SDK 的需求,通常为 caffe 模型或 onnx 模型,以方便模型在不同的框架之间适配。该环节的工作需要对相应的训练框架以及 caffe/onnx 等模型格式有所了解。常用的 Pytorch 和 TensorFlow 等框架都有十分成熟的社区和对应的博客或教程;caffe 和 onnx 模型格式也有很多可参考和学习的公开文档。

即使没找到有可参考的文章时,好在二者都是开源的,依然可以通过对源码和样例代码的阅读来寻找答案。

二、模型优化

此处的模型优化是指与后端无关的通用优化,比如常量折叠算数优化依赖优化函数优化算子融合以及模型信息简化等等。

部分训练框架会在训练模型导出时就包含部分上述优化过程,同时如果模型格式进行了转换操作,不同 IR 表示之间的差异可能会引入一些冗余或可优化的计算,因此在模型转换后通常也会进行一部分的模型优化操作。

该环节的工作需要对计算图的执行流程、各个 op 的计算定义、程序运行性能模型有一定了解,才能知道如果进行模型优化,如何保证优化后的模型具有更好的性能。

了解得越深入,越可以挖掘到更多的模型潜在性能。

三、模型压缩

广义上来讲,模型压缩也属于模型优化的一部分。模型压缩本身也包括很多种方法,比如剪枝、蒸馏、量化等等。模型压缩的根本目的是希望获得一个较小的模型,减少存储需求的同时降低计算量,从而达到加速的目的。

该环节的工作需要对压缩算法本身、模型涉及到的算法任务及模型结构设计、硬件平台计算流程三个方面都有一定的了解。

当因模型压缩操作导致模型精度下降时,对模型算法的了解,和该模型在硬件上的计算细节有足够的了解,才能分析出精度下降的原因,并给出针对性的解决方案。

对于模型压缩更重要的往往是工程经验, 因为在不同的硬件后端上部署相同的模型时,由于硬件计算的差异性,对精度的影响往往也不尽相同,这方面只有通过积累工程经验来不断提升。

OpenPPL也在逐步开源自己的模型压缩工具链,并对上述提到的模型算法、压缩算法和硬件平台适配等方面的知识进行介绍。

四、模型部署

模型部署是整个过程中最复杂的环节。从工程上讲,主要的核心任务是模型打包模型加密,并进行SDK封装。

在一个实际的产品中,往往会用到多个模型。模型打包是指将模型涉及到的前后处理,以及多个模型整合到一起,并加入一些其他描述性文件。模型打包的格式和模型加密的方法与具体的 SDK 相关。在该环节中主要涉及到的技能与 SDK 开发更为紧密。

从功能上讲,对部署最后的性能影响最大的肯定是SDK中包含的后端库,即实际运行模型的推理库。开发一个高性能推理库所需要的技能点就要更为广泛,并且专业。

并行计算的编程思想在不同的平台上是通用的,但不同的硬件架构的有着各自的特点,推理库的开发思路也不尽相同,这也就要求对开发后端的架构体系有着一定的了解。

具体到不同架构的编程学习,建议参考当前各大厂开源的推理库来进一步学习。

面试常见的大数据相关问题

  • https://anchorety.github.io/2019/08/14/%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E7%9A%84%E5%A4%A7%E6%95%B0%E6%8D%AE%E7%9B%B8%E5%85%B3%E9%97%AE%E9%A2%98/

海量日志数据,提取出某日访问百度次数最多的那个IP?

  • http://zoeyyoung.github.io/get-most-visit-ip.html

具体做法如下:

  1. 按照IP地址的Hash(IP)%1024值, 把海量IP日志分别存储到1024个小文件中.
  2. 对于每一个小文件, 构建一个以IP为key, 出现次数为value的HashMap, 同时记录当前出现次数最多的那个IP地址;
  3. 得到1024个小文件中的出现次数最多的IP, 再依据常规的排序算法得到总体上出现次数最多的IP.

分布式 XGBoost4J - Spark 基本原理

XGBoost4J-Spark是一个项目,旨在通过使XGBoost适应Apache Spark的MLLIB框架,无缝集成XGBoost和Apache Spark。通过集成,用户不仅可以使用XGBoost的高性能算法实现,还可以利用Spark强大的数据处理引擎实现以下功能:

  • 特征工程:特征提取,变换,降维和选择等。
  • 管道:构造,评估和调整ML管道
  • 持久性:持久化并加载机器学习模型,甚至整个管道

本文将介绍使用XGBoost4J-Spark构建机器学习管道的端到端过程。讨论

  • 使用Spark预处理数据以适合XGBoost / XGBoost4J-Spark的数据接口
  • 使用XGBoost4J-Spark训练XGBoost模型
  • 使用Spark服务XGBoost模型(预测)
  • 使用XGBoost4J-Spark构建机器学习管道
  • 在生产中运行XGBoost4J-Spark

参考文献

  • 腾讯安全科恩实验室推出首款免费在线SCA平台:BinaryAI:https://www.freebuf.com/sectool/284239.html
  • 源代码与二进制文件SCA检测原理:https://www.huaweicloud.com/zhishi/vss-010.html