API 序列上的 Word2Vec + LSTM
Word2Vec + LSTM on API Sequence
我正在尝试将 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函数名,参数(以空格space分隔)
以文件1的第一个API轨迹为例,kernel32为模块名,LoadLibraryA为函数名,kernel32.dll为参数。每条API条轨迹之间用\n隔开,这样每一行依次代表一个API条序列信息。
首先我根据所有API trace log的line sentence训练了一个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 trace 的起点和终点在哪里?而且模型的训练准确率很差——准确率为 50% :(
我的问题是,在准备训练和验证数据集时,在将实际单词映射到它们的索引时是否也应该分割线?然后将上面的训练数据改成如下,每个API trace用一行隔开,可能把缺失值补到-1,word2vec的索引中不存在。
[[0, 1, 2, -1]
[3, 4, 5, 6]]
同时我使用非常简单的结构进行训练,而 word2vec 模型相当大,任何关于结构的建议也将不胜感激。
我至少会把轨迹线分成三部分:
- 模块(制作字典和嵌入)
- 函数(制作字典和嵌入)
- 参数(制作字典和嵌入 - 稍后查看详细信息)
由于这是一个非常具体的应用程序,我认为最好保持嵌入可训练(嵌入的全部意义在于创建有意义的向量,而意义在很大程度上取决于将要训练的模型使用它们。问题:你是如何创建 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 转换器。
因此,获取文件行并拆分。首先我们用逗号分隔,然后我们用 spaces:
分隔 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)
这些将创建一个独特的单词列表并获取它们的计数。在这里您可以自定义要在 "another word" class 中分组的单词。 (也许基于计数,如果计数太少,则将其设为"another word")。
接下来我们来制作字典:
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
请注意参数,因为我们在那里使用了虚拟 space:
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,...)
我正在尝试将 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函数名,参数(以空格space分隔)
以文件1的第一个API轨迹为例,kernel32为模块名,LoadLibraryA为函数名,kernel32.dll为参数。每条API条轨迹之间用\n隔开,这样每一行依次代表一个API条序列信息。
首先我根据所有API trace log的line sentence训练了一个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 trace 的起点和终点在哪里?而且模型的训练准确率很差——准确率为 50% :(
我的问题是,在准备训练和验证数据集时,在将实际单词映射到它们的索引时是否也应该分割线?然后将上面的训练数据改成如下,每个API trace用一行隔开,可能把缺失值补到-1,word2vec的索引中不存在。
[[0, 1, 2, -1]
[3, 4, 5, 6]]
同时我使用非常简单的结构进行训练,而 word2vec 模型相当大,任何关于结构的建议也将不胜感激。
我至少会把轨迹线分成三部分:
- 模块(制作字典和嵌入)
- 函数(制作字典和嵌入)
- 参数(制作字典和嵌入 - 稍后查看详细信息)
由于这是一个非常具体的应用程序,我认为最好保持嵌入可训练(嵌入的全部意义在于创建有意义的向量,而意义在很大程度上取决于将要训练的模型使用它们。问题:你是如何创建 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 转换器。
因此,获取文件行并拆分。首先我们用逗号分隔,然后我们用 spaces:
分隔 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)
这些将创建一个独特的单词列表并获取它们的计数。在这里您可以自定义要在 "another word" class 中分组的单词。 (也许基于计数,如果计数太少,则将其设为"another word")。
接下来我们来制作字典:
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
请注意参数,因为我们在那里使用了虚拟 space:
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,...)