【问题标题】:Word2Vec + LSTM on API SequenceAPI 序列上的 Word2Vec + LSTM
【发布时间】:2017-11-12 06:23:39
【问题描述】:

我正在尝试将 word2Vec 和 LSTM 应用于包含文件的 API 跟踪日志的数据集,其中包括 API 函数调用及其用于二进制分类的参数。

数据如下:

File_ID,    Label,   API Trace log
 1,           M,      kernel32 LoadLibraryA kernel32.dll
                      kernel32 GetProcAddress MZ\x90 ExitProcess
                      ...

 2,           V,     kernel32 GetModuleHandleA RPCRT4.dll
                     kernel32 GetCurrentThreadId d\x8B\x0D0 POINTER POINTER
                     ...

API 跟踪包括:模块名称、API 函数名称、参数(以空格分隔)

以文件1的第一个API trace为例,kernel32是模块名,LoadLibraryA是函数名,kernel32.dll是参数。每个 API 跟踪由 \n 分隔,以便每一行依次表示一个 API 序列信息。

首先,我根据所有 API 跟踪日志的行句训练​​了一个 word2vec 模型。大约有 5k 个 API 函数调用,例如LoadLibraryA,GetProcAddress。但是,由于参数值可能会有所不同,因此在包含这些参数后,模型会变得非常大(有 300,000 个词汇表)。

之后,我通过应用 word2vec 的 embedding_wrights 训练了一个 LSTM,模型结构如下:

model = Sequential() 
model.add(Embedding(output_dim=vocab_dim, input_dim=n_symbols, \
                mask_zero=False, weights=[embedding_weights], \
                trainable=False))
model.add(LSTM(dense_dim,kernel_initializer='he_normal', dropout=0.15, 
recurrent_dropout=0.15, implementation=2))
model.add(Dropout(0.3))
model.add(Dense(1))
model.add(Activation('sigmoid'))
model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=epochs, batch_size=batch_size, callbacks=[early_stopping, parallel_check_cb])

我得到 embedding_weights 的方法是创建一个矩阵,为 word2vec 模型中的每个词汇表,将模型中单词的索引映射到它的向量

def create_embedding_weights(model, max_index=0):
    # dimensionality of your word vectors
    num_features = len(model[model.vocab.keys()[0]])
    n_symbols = len(model.vocab) + 1  # adding 1 to account for 0th index (for masking)
    # Only word2vec feature set
    embedding_weights = np.zeros((max(n_symbols + 1, max_index + 1), num_features))
    for word, value in model.vocab.items():
        embedding_weights[value.index, :] = model[word]

    return embedding_weights

对于训练数据,我做的是对API调用中的每个单词,将实际单词转换为word2vec模型的索引,使其与上面embedding_weights中的索引一致。 e.g. kernel32 -> 0, LoadLibraryA -> 1, kernel32.dll -> 2. GetProcAddress -> 4, MZ\x90 -> 5, ExitProcess ->6

所以文件 1 的训练数据看起来像 [0, 1, 2, 3, 4, 5, 6]。请注意,我没有为每个 API 跟踪进行行拆分。结果,模型可能不知道 API 跟踪的起点和终点在哪里?而且模型的训练准确率很差——准确率为 50% :(

我的问题是,在准备训练和验证数据集时,在将实际单词映射到其索引时,我是否也应该分行?然后将上面的训练数据更改为以下,每个 API 跟踪由一行分隔,并且可能将缺失值填充到 word2vec 索引中不存在的 -1。

[[0, 1, 2, -1]
 [3, 4, 5, 6]]

同时我使用一个非常简单的结构进行训练,而 word2vec 模型相当大,任何关于结构的建议也将不胜感激。

【问题讨论】:

  • @Marcin Możejko,你能帮我开绿灯吗?提前致谢。

标签: tensorflow deep-learning keras lstm word2vec


【解决方案1】:

我至少会将跟踪线一分为三:

  • 模块(制作字典和嵌入)
  • 函数(制作字典和嵌入)
  • 参数(制作字典和嵌入 - 稍后查看详细信息)

由于这是一个非常具体的应用程序,我认为最好保持嵌入可训练(嵌入的全部意义在于创建有意义的向量,其含义在很大程度上取决于将要使用它们的模型. 问题:你是如何创建 word2vec 模型的?它是从什么数据中学习的?)。

这个模型会有更多的输入。所有这些都是从零到最大字典索引的整数。考虑使用mask_zero=True 并将所有文件填充到maxFileLines

moduleInput = Input(maxFileLines,) 
functionInput = Input(maxFileLines,)    

对于参数,我可能会创建一个子序列,就好像参数列表是一个句子一样。 (同样,mask_zero=True,并填充到maxNumberOfParameters

parametersInput = Input(maxFileLines, maxNumberOfParameters)

函数和模块嵌入:

moduleEmb = Embedding(.....mask_zero=True,)(moduleInput)    
functionEmb = Embedding(.....mask_zero=True)(functionInput)

现在,对于参数,我想创建一个序列序列(也许这太多了)。为此,我首先将线条尺寸转移到批量尺寸并仅使用 length = maxNumberOfParameters:

paramEmb = Lambda(lambda x: K.reshape(x,(-1,maxNumberOfParameters)))(parametersInput)
paramEmb = Embedding(....,mask_zero=True)(paramEmb)
paramEmb = Lambda(lambda x: K.reshape(x,(-1,maxFileLines,embeddingSize)))(paramEmb)

现在我们将它们全部连接到最后一个维度,我们准备好进入 LSTM:

joinedEmbeddings = Concatenate()([moduleEmb,functoinEmb,paramEmb])
out = LSTM(...)(joinedEmbeddings)
out = ......

model = Model([moduleInput,functionInput,parametersInput], out)

如何准备输入

使用此模型,您需要三个单独的输入。一种用于模块,一种用于功能,一种用于参数。

这些输入将仅包含索引(无向量)。他们不需要以前的 word2vec 模型。嵌入是 word2vec 转换器。

所以,获取文件行并拆分。首先我们用逗号分隔,然后我们用空格分隔 API 调用:

import numpy as np

#read the file
loadedFile = open(fileName,'r')
allLines = [l.strip() for l in loadedFile.readlines()] 
loadedFile.close()

#split by commas
splitLines = []
for l in allLines[1:]: #use 1 here only if you have headers in the file
    splitLines.append (l.split(','))
splitLines = np.array(splitLines)

#get the split values and separate ids, targets and calls
ids = splitLines[:,0]
targets = splitLines[:,1]
calls = splitLines[:,2]

#split the calls by space, adding dummy parameters (spaces) to the max length
splitCalls = []
for c in calls:
    splitC = c.strip().split(' ')

    #pad the parameters (space for dummy params)
    for i in range(len(splitC),maxParams+2):
        splitC.append(' ') 

    splitCalls.append(splitC)

splitCalls = np.array(splitCalls)

modules = splitCalls[:,0]
functions = splitCalls[:,1]
parameters = splitCalls[:,2:] #notice the parameters have an extra dimension

现在让我们制作索引:

modIndices, modCounts = np.unique(modules,return_counts=True)
funcIndices, funcCounts = np.unique(functions,return_counts=True)

#for de parameters, let's flatten the array first (because we have 2 dimensions)
flatParams = parameters.reshape((parameters.shape[0]*parameters.shape[1],))
paramIndices, paramCounts = np.unique(flatParams,return_counts=True)

这些将创建一个唯一单词列表并获取它们的计数。在这里,您可以自定义要在“另一个单词”类中分组的单词。 (可能基于计数,如果计数太少,则将其设为“另一个词”)。

然后我们来制作字典:

def createDic(uniqueWords):
    dic = {}
    for i,word in enumerate(uniqueWords):
         dic[word] = i + 1 # +1 because we want to reserve the zeros for padding     
    return dic

请注意参数,因为我们在那里使用了一个虚拟空间:

moduleDic = createDic(modIndices)
funcDic = createDic(funcIndices)
paramDic = createDic(paramIndices[1:]) #make sure the space got the first position here    
paramDic[' '] = 0

好吧,现在我们只是替换原来的值:

moduleData = [moduleDic[word] for word in modules]
funcData = [funcDic[word] for word in functions]
paramData = [[paramDic[word] for word in paramLine] for paramLine in parameters]

填充它们:

for i in range(len(moduleData),maxFileLines):
    moduleData.append(0)
    funcData.append(0)
    paramData.append([0] * maxParams)

对每个文件执行此操作,并存储在文件列表中:

moduleTrainData = []  
functionTrainData = []
paramTrainData = []
for each file do the above and:
    moduleTrainData.append(moduleData)
    functionTrainData.append(funcData)
    paramTrainData.append(paramData)

moduleTrainData = np.asarray(moduleTrainData)
functionTrainData = np.asarray(functionTrainData)
paramTrainData = np.asarray(paramTrainData)

这就是输入的全部内容。

model.fit([moduleTrainData,functionTrainData,paramTrainData],outputLabels,...)

【讨论】:

  • 非常感谢您的建议。我从 API Trace Log 训练了 word2Vec 模型,该模型将每一行(模块 + 函数 + 参数)作为 lineSentence 输入。 maxFileLines 是所有文件中 API 跟踪的最大数量吗?例如文件 1 有 1000 个 API 函数调用,文件 n 有 5000 个 API 函数调用,因此 maxFileLines 为 5000。
  • 我完全是新手,所以请与我一起回答我的愚蠢问题 :) moduleInput、functionInput 和 parametersInput 定义了模型输入数据的形状。那么在准备训练和验证数据时,我仍然需要将实际单词转换/映射到目录索引吗?例如 kernel32 的索引为 0,所以在训练数据中,使用 0 代替 kernel32。我的问题是 kernel32 的向量如何在模型中发挥作用?
  • 是的,您应该使用三个单独的数组。一个数组用于模块输入,一个函数输入,另一个用于参数输入。是的,它们应该是整数索引。 (但保留 0 用于填充,从 1 开始真正的单词)。模型中的Embedding 层负责将索引转换为向量。 (不同之处在于您没有前一组用于嵌入的权重,您将让嵌入训练它自己以获得可能的最佳向量)。
  • 是的,maxFileLines 根据你的说法是 5000。创建输入数组时,将少于 5000 行的文件用零补齐(不要忘记在嵌入中打开 mask_zero)。
  • 再次感谢。对于任何缺失值,我应该在三个输入中填充 0。例如,某些函数调用没有任何参数,所以在 parametersInput 中我需要填充 0 作为参数的值,以便参数的顺序与函数调用的顺序相匹配?例如函数getProcessID没有参数,是文件3的第11个函数调用,所以这个文件的第11个参数输入全0来匹配这个函数?
猜你喜欢
  • 2018-08-04
  • 1970-01-01
  • 1970-01-01
  • 2016-10-20
  • 2019-01-14
  • 1970-01-01
  • 2018-12-08
  • 2021-10-13
  • 2018-07-16
相关资源
最近更新 更多