【新人賽】阿里雲惡意程序檢測 -- 實踐記錄10.20 - 數據預處理 / 訓練數據分析 / TF-IDF模型調參


Colab連接與數據預處理

Colab連接方法見上一篇博客

數據預處理:

import pandas as pd
import pickle
import numpy as np

# 訓練數據和測試數據路徑
train_path = './security_train.csv'
test_path = './security_test.csv'

# 將csv格式的訓練數據處理為txt文本,只包含文件標簽和api序列
def read_train_file(path):
    labels = []  # 文件標簽,0-正常/1-勒索病毒/ ... /7-木馬程序
    files = []     # api序列,文件調用的一系列API名稱
    data = pd.read_csv(path)

    # for data in data1:
    group_fileid = data.groupby('file_id') # 根據fileid分組數據
    for file_name, file_group in group_fileid:
        print(file_name)
        file_labels = file_group['label'].values[0]
        result = file_group.sort_values(['tid', 'index'], ascending=True) # 根據tid, index升序排列
        api_sequence = ' '.join(result['api']) # 生成api序列
        labels.append(file_labels)
        files.append(api_sequence)

    print("labels length: ", len(labels))

    with open(path.split('/')[-1] + ".txt", 'w') as f:
        for i in range(len(labels)):
            f.write(str(labels[i]) + ' ' + files[i] + '\n')

# 將txt文本格式轉為pkl序列化文件
# 這種格式讀取時依然是結構化數據,而非字符串
def load_train2h5py(path="security_train.csv.txt"):
    labels = []
    files = []

    with open(path) as f:
        for i in f.readlines():
            i = i.strip('\n')
            labels.append(i[0])
            files.append(i[2:])

    labels = np.asarray(labels)
    print(labels.shape)

    with open("security_train.csv.pkl", 'wb') as f:
        pickle.dump(labels, f)
        pickle.dump(files, f)

# 將csv格式的訓練數據處理為txt文本,只包含文件編號和api序列
def read_test_file(path):
    names = []
    files = []
    data = pd.read_csv(path)

    # for data in data1:
    group_fileid = data.groupby('file_id')
    for file_name, file_group in group_fileid:
        print(file_name)
        result = file_group.sort_values(['tid', 'index'], ascending=True)
        api_sequence = ' '.join(result['api'])
        names.append(file_name)
        files.append(api_sequence)

    print("names length: ", len(names))
    
    with open("security_test.csv.pkl", 'wb') as f:
        pickle.dump(names, f)
        pickle.dump(files, f)

print("read train file.....")
read_train_file(train_path)
load_train2h5py()
print("read test file......")
read_test_file(test_path)

訓練數據分析

import pandas as pd

train_path = './security_train.csv'
test_path = './security_test.csv'

df = pd.read_csv(train_path)
df_test = pd.read_csv(test_path)

查看行列索引

print(df.columns)
print(df.index)

Index(['file_id', 'label', 'api', 'tid', 'index'], dtype='object')
RangeIndex(start=0, stop=89806693, step=1)

文件label統計:

df = df.drop(['api', 'tid', 'index'], axis=1)
df = df.drop_duplicates()
df['label'].value_counts() / df.label.count()

結果如下,可見訓練數據中除了正常文件外,感染型病毒是最多的。

0 0.358465
5 0.308850
7 0.107079
2 0.086124
3 0.059048
6 0.037085
1 0.036149
4 0.007201
Name: label, dtype: float64

文件調用的api統計

df.api.value_counts()

頭尾數據展示,可見LdrGetProcedureAddress這個api調用次數最多:

統計文檔頻率:(文檔頻率求倒數,再求log,就是逆文本頻率idf)

file_sum = 13887 # 文件總數,數據預處理中已求出
df = df.drop(['label', 'tid', 'index'], axis=1)
df = df.drop_duplicates()
df['api'].value_counts() / file_sum

頭尾數據展示,可見NtClose,LdrGetProcedureAddress,LdrGetDllHandle等在每個文檔中幾乎都出現。

對比一下測試數據的文檔頻率,可以看出來,文檔頻率較大的api還是差不多這幾個,所以訓練數據和測試數據api的分布總體上差不多。

統計每個文件有多少個線程調用,以及該線程調用api的次數,看看文件運行狀況

groupid = df.groupby('file_id')
groupid['tid'].value_counts()

頭尾數據展示,可見運行此文件的線程不止一個:

統計每次調用該api的文件類型,一般都是什么類型的文件會調用此api

groupapi = df.groupby('api')
groupapi['label'].value_counts()

頭尾數據展示,可以看到這些API會被哪種類型的文件更多地調用,有一些規律性,

TF-IDF模型訓練代碼

import pickle
import numpy as np
import xgboost as xgb
from sklearn.model_selection import StratifiedKFold
from sklearn.feature_extraction.text import TfidfVectorizer
with open("security_train.csv.pkl", "rb") as f:
    labels = pickle.load(f)
    files = pickle.load(f)

with open("security_test.csv.pkl", "rb") as f:
    file_names = pickle.load(f)
    outfiles = pickle.load(f)

tf-idf特征抽取,TfidfVectorizer參數解釋:
ngram_range: 要提取的n-gram的n-values的下限和上限范圍,[min_n, max_n]
min_df: 忽略低於給出閾值的文檔頻率的詞條。max_df: 忽略高於給出閾值的文檔頻率的詞條。
int 類型時表示一個詞在文檔中出現的次數。
float 類型時表示詞出現的文檔數與語料庫文檔數的百分比。

print("start tfidf...")
vectorizer = TfidfVectorizer(ngram_range=(1, 1), min_df=1, max_df=1.0) # 此處參數可以調

fit(): 根據參數規則進行操作,比如濾除停用詞等,擬合原始數據,生成文檔中有價值的詞匯表
transform(): 使用符合fit的詞匯表或提供給構造函數的詞匯表,從原始文本文檔中提取詞頻,轉換成詞頻矩陣

train_features = vectorizer.fit_transform(files)
print("train_features fit transform")

out_features = vectorizer.transform(outfiles)
print("out_features transform")

k折交叉切分,分層采樣,StratifiedKFold參數解析:
n_splits:折疊次數,默認為3,至少為2。
shuffle: 是否在每次分割之前打亂順序。
random_state:隨機種子,在shuffle==True時使用,默認使用np.random。

meta_train = np.zeros(shape=(len(files), 8))
meta_test = np.zeros(shape=(len(outfiles), 8))
skf = StratifiedKFold(n_splits=2, random_state=4, shuffle=True)

xgboost 是一種集成學習方法,通過構建多棵決策樹來實現分類和回歸任務。
XGBoost參數解析:
eta [default=0.3, alias: learning_rate] 學習率
max_depth [default=6] 樹的最大深度,可以用來防止過擬合,典型值是3-10
colsample_bytree [default=1] 列采樣率,也就是特征采樣率
subsample [default=1] 構建每棵樹對樣本的采樣率,如果設置成0.5,XGBoost會隨機選擇一半的樣本作為訓練集
objective[默認reg:linear] 損失函數,multi:softprob:和softmax一樣,但是返回的是每個數據屬於各個類別的概率
num_class(softmax分類的個數)
eval_metric 對於有效數據的度量方法,mlogloss 多分類logloss損失函數
silent [default=0] 取0時表示打印出運行時信息,取1時表示以緘默方式運行,不打印運行時信息

for i, (tr_ind, te_ind) in enumerate(skf.split(train_features, labels)):
    X_train, X_train_label = train_features[tr_ind], labels[tr_ind]
    X_val, X_val_label = train_features[te_ind], labels[te_ind]

    print('FOLD: {}'.format(str(i)))
    print(len(te_ind), len(tr_ind))
    
    dtrain = xgb.DMatrix(X_train, label=X_train_label)
    dtest = xgb.DMatrix(X_val, X_val_label)
    dout = xgb.DMatrix(out_features)
    
    param = {'max_depth': 6, 'eta': 0.1, 'eval_metric': 'mlogloss', 'silent': 1, 'objective': 'multi:softprob',
             'num_class': 8, 'subsample': 0.8, 'colsample_bytree': 0.85}

    evallist = [(dtrain, 'train'), (dtest, 'val')]  # 測試 , (dtrain, 'train')
    num_round = 300  # 循環次數
    bst = xgb.train(param, dtrain, num_round, evallist, early_stopping_rounds=50)

    pred_val = bst.predict(dtest)
    pred_test = bst.predict(dout)
    meta_train[te_ind] = pred_val
    meta_test += pred_test
    
meta_test /= 2.0

TF-IDF調參過程

訓練目標:

看一下n-gram,n取多少,訓練得到的結果最好。(每次修改ngram_range參數,其他參數的設置見上述代碼,暫不做修改)

ngram_range=(1, 1),使用1-gram:

print(train_features.shape)
print(out_features.shape)

(13887, 295)
(12955, 295)

訓練得到的結果如下:

FOLD 1: train-mlogloss:0.101924 val-mlogloss:0.405779
FOLD 2: train-mlogloss:0.105662 val-mlogloss:0.406391
平均:train-mlogloss: 0.103793 val-mlogloss: 0.406085

修改參數ngram_range=(1, 2):

print(train_features.shape)
print(out_features.shape)

(13887, 20542)
(12955, 20542)

訓練得到的結果如下:

FOLD 1: train-mlogloss:0.077268 val-mlogloss:0.34466
FOLD 2: train-mlogloss:0.075055 val-mlogloss:0.348921
平均:train-mlogloss: 0.0761615 val-mlogloss: 0.3467905

效果優於1-gram。

修改參數ngram_range=(1, 3):

print(train_features.shape)
print(out_features.shape)

(13887, 180858)
(12955, 180858)

訓練得到的結果如下:

FOLD 1: train-mlogloss:0.070912 val-mlogloss:0.338498
FOLD 2: train-mlogloss:0.070977 val-mlogloss:0.344922
平均:train-mlogloss: 0.0709445 val-mlogloss: 0.34171

比2-gram稍好。

修改參數ngram_range=(1, 4):

print(train_features.shape)
print(out_features.shape)

(13887, 647361)
(12955, 647361)

訓練得到的結果如下:

FOLD 1: train-mlogloss:0.068388 val-mlogloss:0.335232
FOLD 2: train-mlogloss:0.068232 val-mlogloss:0.33844
平均:train-mlogloss: 0.06831 val-mlogloss: 0.336836

比3-gram稍好。

修改參數ngram_range=(1, 5):

print(train_features.shape)
print(out_features.shape)

(13887, 1512641)
(12955, 1512641)

訓練得到的結果如下:

FOLD 1: train-mlogloss:0.066721 val-mlogloss:0.336091
FOLD 2: train-mlogloss:0.065707 val-mlogloss:0.339242
平均:train-mlogloss: 0.066214 val-mlogloss: 0.3376665

比4-gram的要差。

修改參數ngram_range=(1, 6):

print(train_features.shape)
print(out_features.shape)

(13887, 2787688)
(12955, 2787688)

訓練得到的結果如下:

FOLD 1: train-mlogloss:0.068417 val-mlogloss:0.334676
FOLD 2: train-mlogloss:0.069143 val-mlogloss:0.340957
平均:train-mlogloss: 0.06878 val-mlogloss: 0.3378165

比5-gram還要更差一點,不僅在驗證集上表現不好,在訓練集上表現也更差。

由測試集和驗證集可知,n=4時預測效果最好。

提交線上:
n=3, logloss=0.528974
n=4, logloss=0.522686
n=5, logloss=0.526627

同樣也是n=4時效果最佳。

實踐心得

關於此問題下TF-IDF的效果沒有N-gram好的原因:(就是TfidfVectorizer跑的效果不如CountVectorizer)

百度過來的解釋:

在本質上IDF是一種試圖抑制噪音的加權,並且單純地認為文本頻率小的單詞就越重要,文本頻率大的單詞就越無用。這對於大部分文本信息,並不是完全正確的。IDF的簡單結構並不能使提取的關鍵詞,十分有效地反映單詞的重要程度和特征詞的分布情況,使其無法很好地完成對權值調整的功能。尤其是在同類語料庫中,這一方法有很大弊端,往往一些同類文本的關鍵詞被掩蓋。例如:語料庫D中教育類文章偏多,而文本j是一篇屬於教育類的文章,那么教育類相關的詞語的IDF值將會偏小,使提取文本關鍵詞的召回率更低。

TF-IDF的優點是實現簡單,相對容易理解。但是,TFIDF算法提取關鍵詞的缺點也很明顯,嚴重依賴語料庫,需要選取質量較高且和所處理文本相符的語料庫進行訓練。另外,對於IDF來說,它本身是一種試圖抑制噪聲的加權,本身傾向於文本中頻率小的詞,這使得TF-IDF算法的精度不高。TF-IDF算法還有一個缺點就是不能反應詞的位置信息,在對關鍵詞進行提取的時候,詞的位置信息,例如文本的標題、文本的首句和尾句等含有較重要的信息,應該賦予較高的權重。

學長說的:

NLP任務本質是詞非常多,可能上萬,但是我們這個任務API總數只有幾百個。(所以這個問題因為數據的特殊性,所以和一般的NLP問題采用的方法不一樣)

組會總結

1)考慮到實際應用的情況下,盡量做單模型。
2)要考慮問題的物理意義,不能一味地堆算法。
3)哪些地方用哪個技術,對應的技術能不能用?會不會產生什么問題?比如文本分類長度如果特別長的話有些模型可能用不了。考慮計算資源/預訓練模型等。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM