2019年十月份左右顷链,迫于學業(yè)上的壓力開始入坑機器學習属提,到現(xiàn)在大約有小半年的時間了。這期間從數(shù)據(jù)挖掘分析到機器學習的原理和算法等等看了很多東西络拌,但是一直感覺它們只是被迫塞進了腦子里并沒有進行消化吸收奋隶。這個時候最需要自己給自己找點事來做了:)由于之前對Web安全有一點點了解擂送,就決定從Web應(yīng)用產(chǎn)生的HTTP數(shù)據(jù)流量進行一次機器學習的實戰(zhàn)。
GitHub地址:https://github.com/zambery/Machine-Learning-on-CSIC-2010
1 數(shù)據(jù)集及預(yù)處理
實驗使用 HTTP CSIC 2010 數(shù)據(jù)集唯欣。該數(shù)據(jù)集由西班牙最高科研理事會 CSIC 在論文 _Application of the Generic Feature __Selection Measure in Detection of Web _Attacks 中作為附件給出的嘹吨,是一個電子商務(wù)網(wǎng)站的訪問日志,包含 36000 個正常請求和 25000 多個攻擊請求境氢。異常請求樣本中包含 SQL 注入蟀拷、文件遍歷、CRLF 注入萍聊、XSS问芬、SSI 等攻擊樣本。
數(shù)據(jù)集下載鏈接:http://www.isi.csic.es/dataset/
根據(jù)觀察寿桨,該數(shù)據(jù)集除路徑(URI)和參數(shù)外其他 Header 無任何攻擊 Payload此衅,具有很多冗余信息。因此對該數(shù)據(jù)集進行格式化亭螟,只保留 HTTP 方法挡鞍、路徑和參數(shù)。
import urllib.parse
normal_file_raw = 'normalTrafficTraining/normalTrafficTraining.txt'
anomalous_file_raw = 'anomalousTrafficTest/anomalousTrafficTest.txt'
normal_file_pre = 'normal.txt'
anomalous_file_pre = 'anomalous.txt'
def pre_file(file_in, file_out=None):
with open(file_in, 'r', encoding='utf-8') as f_in:
lines = f_in.readlines()
res = []
for i in range(len(lines)):
line = lines[i].strip()
# 提取 GET類型的數(shù)據(jù)
if line.startswith("GET"):
res.append("GET " + line.split(" ")[1])
# 提取 POST類型的數(shù)據(jù)
elif line.startswith("POST") or line.startswith("PUT"):
method = line.split(' ')[0]
url = line.split(' ')[1]
j = 1
# 提取 POST包中的數(shù)據(jù)
while True:
# 定位消息正文的位置
if lines[i + j].startswith("Content-Length"):
break
j += 1
j += 2
data = lines[i + j].strip()
url += '?'+data
res.append(method + ' ' + url)
with open(file_out, 'w', encoding='utf-8') as f_out:
for line in res:
line = urllib.parse.unquote(line, encoding='ascii', errors='ignore').replace('\n', '').lower()
f_out.writelines(line + '\n')
print("{}數(shù)據(jù)預(yù)提取完成 {}條記錄".format(file_out, len(res)))
if __name__ == '__main__':
pre_file(normal_file_raw, normal_file_pre)
pre_file(anomalous_file_raw, anomalous_file_pre)
格式化后的數(shù)據(jù)如下:異常流量下載:anomalous.txt
正常流量下載:normal.txt
2 特征提取
經(jīng)過上面的處理我們有了格式化的文本數(shù)據(jù)预烙,但是如果想要運用機器學習的方法墨微,單單做到這里是不夠的,我們還需要進一步地對這里的文本數(shù)據(jù)進行特征提取扁掸。一個 Web 訪問記錄的成分是比較固定的翘县,每個部分(方法、路徑谴分、參數(shù)锈麸、HTTP 頭、Cookie 等)都有比較好的結(jié)構(gòu)化特點狸剃,因此可以把 Web 攻擊識別任務(wù)抽象為文本分類任務(wù)掐隐。這里我們采用兩鐘方法進行特征提取。
2.1 TF-IDF
對文本數(shù)據(jù)進行分類钞馁,常用的方法有詞袋模型虑省、TF-IDF、詞向量化等方法僧凰。由于只是一次簡單的知識梳理實驗探颈,這里就不對其進行比較驗證了,直接選擇TF-IDF對文本數(shù)據(jù)進行向量化處理训措。
在進行向量化處理之前伪节,先對文本數(shù)據(jù)進行整合、打標簽的操作
def load_data(file):
with open(file, 'r', encoding='utf-8') as f:
data = f.readlines()
result = []
for d in data:
d = d.strip()
if len(d) > 0:
result.append(d)
return result
normal_requests = load_data('normal.txt')
anomalous_requests = load_data('anomalous.txt')
all_requests = normal_requets + anomalous_requets
y_normal = [0] * len(normal_requests)
y_anomalous = [1] * len(anomalous_requests)
y = y_normal + y_anomalous
之后采用TF-IDF進行特征提取
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer(min_df=0.0, analyzer="word", sublinear_tf=True)
X = vectorizer.fit_transform(all_requests)
我們查看下一提取的特征向量:可以看到特征維度為33550绩鸣,這樣一個維度對于很多機器算法來說計算復(fù)雜度太高怀大,也很容易產(chǎn)生過擬合的情況。
當然遇到這種情況呀闻,應(yīng)該采用相應(yīng)的手段對其進行降維操作化借,但是由于機器限制在保留90%信息的情況下得出所需的維度需要相當長的時間,故直接采用這一維度進行實驗捡多。
2.2 自定義特征向量
在上面用TF-IDF進行特征提取時蓖康,遇到的問題是數(shù)據(jù)的維度太高,雖然我們還沒有用機器學習算法進行實際的訓(xùn)練但是這樣高的維度讓我們應(yīng)該考慮一些備用方案垒手,比如嘗試進行提取自定義的特征蒜焊。
這里我們不妨對最初的文本數(shù)據(jù)進行如下的特征提取:
- URL長度
- 請求類型
- 參數(shù)部分長度
- 參數(shù)的個數(shù)
- 參數(shù)的最大長度
- 參數(shù)的數(shù)字個數(shù)
- 參數(shù)值中數(shù)字所占比例
- 參數(shù)值中字母所占比例
- 特殊字符個數(shù)
- 特殊字符所占比例
def vector(data):
feature = []
for i in range(len(data)):
# 采用 split("?", 1)是為了處理形如 http://127.0.0.1/?open=?123的URL
s = data[i].split("?", 1)
if len(s) != 1:
url_len = len(data[i])
method = data[i].split(" ")[0]
if method == "get":
url_method = 1
elif method == "post":
url_method = 2
else:
url_method = 3
query = s[1]
parameter_len = len(query)
parameters = query.split("&")
parameter_num = len(parameters)
parameter_max_len = 0
parameter_number_num = 0
parameter_str_num = 0
parameter_spe_num = 0
par_val_sum = 0
for parameter in parameters:
try:
# 采用 split("=", 1)是為了處理形如 open=123=234&file=op的參數(shù)
[par_name, par_val] = parameter.split("=", 1)
except ValueError as err:
# 處理形如 ?open 這樣的參數(shù)
print(err)
print(data[i])
break
par_val_sum += len(par_val)
if parameter_max_len < len(par_val):
parameter_max_len = len(par_val)
parameter_number_num += len(re.findall("\d", par_val))
parameter_str_num += len(re.findall(r"[a-zA-Z]", par_val))
parameter_spe_num += len(par_val) - len(re.findall("\d", par_val)) - len(
re.findall(r"[a-zA-Z]", par_val))
try:
parameter_number_rt = parameter_number_num / par_val_sum
parameter_str_rt = parameter_str_num / par_val_sum
parameter_spe_rt = parameter_spe_num / par_val_sum
feature.append([url_len, url_method, parameter_len, parameter_num,
parameter_max_len, parameter_number_num, parameter_str_num,
parameter_spe_num, parameter_number_rt, parameter_str_rt,
parameter_spe_rt])
except ZeroDivisionError as err:
print(err)
print(data[i])
continue
return feature
自定義好后的數(shù)據(jù)用 numpy
形式保存泳梆,方便以后直接使用。
向量化的正常數(shù)據(jù):vector_normal.txt
向量化的異常數(shù)據(jù):vector_anomalous.txt
同樣地榜掌,我們也需要整合已經(jīng)向量化好的數(shù)據(jù)鸭丛,并進行打標簽操作,劃分測試集和訓(xùn)練集唐责。
# 整合數(shù)據(jù)鳞溉,打標簽
import numpy as np
normal = np.loadtxt("vector_normal")
anomalous = np.loadtxt("vector_anomalous")
all_requests = np.concatenate([normal, anomalous])
X = all_requests
y_normal = np.zeros(shape=(normal.shape[0]), dtype='int')
y_anomalous = np.ones(shape=(anomalous.shape[0]), dtype='int')
y = np.concatenate([y_normal, y_anomalous])
# 劃分測試集和訓(xùn)練集
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=666)
3 模型訓(xùn)練
這部分我們主要比較TF-IDF提取的特征和自定義提取的特征在模型訓(xùn)練中的不同
3.1 k近鄰算法
在開始之前先對數(shù)據(jù)進行歸一化操作,進一步提高模型訓(xùn)練結(jié)果的正確性:
from sklearn.preprocessing import StandardScaler
# 數(shù)據(jù)歸一化
standardScalar = StandardScaler()
standardScalar.fit(X_train)
X_train = standardScalar.transform(X_train)
X_test_std = standardScalar.transform(X_test)
在進行模型訓(xùn)練時鼠哥,我們將采用網(wǎng)格搜索的方法熟菲,使用交叉驗證的方式來評估超參數(shù)的所有可能的組合:
# 網(wǎng)格搜索的參數(shù)
param_grid = [
{
'weights': ['uniform'],
'n_neighbors': [i for i in range(2, 11)] #從1開始容易過擬合
},
{
'weights': ['distance'],
'n_neighbors': [i for i in range(2, 11)],
'p': [i for i in range(1, 6)]
}
]
# cv其實也是一個超參數(shù),一般越大越好朴恳,但是越大訓(xùn)練時間越長
grid_search = GridSearchCV(KNeighborsClassifier(), param_grid, n_jobs=-1, cv=5)
基于上面的設(shè)置可以得到如下表所示結(jié)果抄罕,其中TF-IDF使用無法得出結(jié)果(內(nèi)存爆表死機),只進行了單個模型訓(xùn)練于颖,模型參數(shù)如下:
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
metric_params=None, n_jobs=None, n_neighbors=5, p=2,
weights='uniform')
自定義特征 | TF-IDF | |
---|---|---|
訓(xùn)練時間 | 47.1 s | 60 ms |
最佳結(jié)果 | 'n_neighbors': 10<br />'p': 3,<br />'weights': 'distance' | |
grid_search.best_score_ | 0.8759619101163076 | |
測試集score | 0.8733661278988053 | 0.9233603537214443 |
精準率 | 0.8785353535353535 | 0.9194262813752373 |
召回率 | 0.8922800718132855 | 0.8872379401587625 |
F1-Score | 0.8853543707850872 | 0.9030453697949038 |
3.2 邏輯回歸
# 網(wǎng)格搜索的參數(shù)
param_grid = [
{
'C': [0.1, 1, 3, 5, 7],
'penalty': ['l1', 'l2']
}
]
grid_search = GridSearchCV(LogisticRegression(), param_grid, n_jobs=-1, cv=5)
自定義特征 | TF-IDF | |
---|---|---|
訓(xùn)練時間 | 4min 11s | 1min 31s |
最佳結(jié)果 | 'C': 7<br />'penalty': 'l1' | 'C': 7<br />'penalty': 'l2' |
grid_search.best_score_ | 0.6869882989563935 | 0.9680463440596087 |
測試集score | 0.6929023190442727 | 0.9737165315647262 |
精準率 | 0.7455587392550144 | 0.9922813036020584 |
召回率 | 0.6673506027186458 | 0.941990637085284 |
F1-Score | 0.7042901610502098 | 0.9664821969301451 |
上面的結(jié)果還是有點出乎意料的
- 邏輯回歸模型的訓(xùn)練時間竟然比k近鄰還長呆贿,這當然可能跟網(wǎng)格搜索所設(shè)置的參數(shù)有關(guān)系,但是這一點與理論沖突,仍有待后期驗證
- 由TF-IDF所提取的特征向量不僅訓(xùn)練時間快做入,模型的準確率也比自定義的特征提取高出不少冒晰,這里有些反直覺,具體原因也不明確
- 對于自定義特征的在邏輯回歸上的訓(xùn)練結(jié)果竟块,可以考慮增加多項式特征來進行優(yōu)化
3.3 決策樹
對于決策樹來說可以直接使用原始數(shù)據(jù)而不必進行特征縮放
# 網(wǎng)格搜索的參數(shù)
param_grid = [
{
'max_depth':[i for i in range(1, 10)],
'min_samples_leaf':[i for i in range(1, 20)],
'min_samples_split':[i for i in range(10, 30)],
}
]
自定義特征 | TF-IDF | |
---|---|---|
訓(xùn)練時間 | 3min 46s | 1h 7min 6s |
最佳參數(shù) | 'max_depth': 9<br />'min_samples_leaf': 1<br />'min_samples_split': 27 | 'max_depth': 9<br />'min_samples_leaf': 19<br />'min_samples_split': 10 |
grid_search.best_score_ | 0.7973224638954285 | 0.8979775648898715 |
測試集score | 0.8042164441321152 | 0.90084336362892 |
精準率 | 0.7658039881204921 | 0.951904296875 |
召回率 | 0.9258784303667608 | 0.7936087929981681 |
F1-Score | 0.8382677348194589 | 0.8655788655788657 |
從上面的結(jié)果可以看出由于TF-IDF所提取的特征維度太大壶运,因此訓(xùn)練時間達到了一個小時。
3.4 SVM
SVM對特征縮放較為敏感浪秘,因此我們在進行模型訓(xùn)練之前需要對數(shù)據(jù)進行歸一化處理
# 網(wǎng)格搜索的參數(shù)
param_grid = [
{
'kernel': ["poly"],
'degree': [1, 2, 3],
'C': [0.1, 1, 3, 5]
}
]
grid_search = GridSearchCV(SVC(), param_grid, n_jobs=-1, cv=5)
此外由于SVM的對于數(shù)據(jù)量較多的數(shù)據(jù)集訓(xùn)練時間較慢蒋情,此處TF-IDF的特征提取結(jié)果沒有進行網(wǎng)格搜索尋找最佳參數(shù),其模型參數(shù)如下:
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape='ovr', degree=3, gamma='auto_deprecated',
kernel='rbf', max_iter=-1, probability=False, random_state=None,
shrinking=True, tol=0.001, verbose=False)
自定義特征 | TF-IDF | |
---|---|---|
訓(xùn)練時間 | 8min 26s | 10min 22s |
最佳參數(shù) | 'C': 5<br />'degree': 3<br />'kernel': 'poly' | |
grid_search.best_score_ | 0.7022734460100496 | |
測試集score | 0.7114546732255798 | 0.9619258167526407 |
精準率 | 0.7404898384575299 | 0.9623700623700624 |
召回率 | 0.7289048473967684 | 0.9421941787095461 |
F1-Score | 0.7346516737753651 | 0.9521752545510646 |
3.5 隨機森林
由于隨機森林模型所需的訓(xùn)練時間較長耸携,這里就不采用網(wǎng)格搜索的方式尋找最佳參數(shù)了棵癣,進行訓(xùn)練的隨機森林模型參數(shù)如下:
RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
max_depth=None, max_features='auto', max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, n_estimators=500,
n_jobs=-1, oob_score=True, random_state=666, verbose=0,
warm_start=False)
自定義特征 | TF-IDF | |
---|---|---|
訓(xùn)練時間 | 3.77 s | 2min 50s |
測試集score | 0.947013352073085 | 0.9647916154916892 |
精準率 | 0.9471813103098019 | 0.9618792499484855 |
召回率 | 0.956655552705822 | 0.9501323020557704 |
F1-Score | 0.9518948577261708 | 0.9559696907638747 |
4 總結(jié)
這次實驗分別使用了自定義的特征向量和TF-IDF提取的特征向量進行模型訓(xùn)練,我們不妨從以下幾個方面來回顧一下模型的訓(xùn)練結(jié)果
實驗結(jié)果分析
- 訓(xùn)練時間:
從訓(xùn)練時間上看夺衍,用自定義特征進行模型訓(xùn)練所花費的時間總體要比TF-IDF提取特征進行模型訓(xùn)練所花費的時間短狈谊。當然由于有些使用了網(wǎng)格搜索尋找最佳參數(shù),有些沒有進行網(wǎng)格搜索刷后,這里無法進行定量的分析的畴。因為TF-IDF的特征維度—33550,遠遠超過自定義的特征維度—11尝胆,自定義特征的模型訓(xùn)練時間短也是可以預(yù)見的丧裁。
但在邏輯回歸中,基于相同的網(wǎng)格搜索參數(shù)含衔,自定義特征的模型訓(xùn)練時間:4min 11s煎娇、TF-IDF提取特征的模型訓(xùn)練時間:1min 31s。自定義特征的模型訓(xùn)練時間較長贪染,這一點目前無法解釋缓呛。
- 訓(xùn)練結(jié)果:
自定義特征的模型(藍色為最大值,綠色為次大值)
k近鄰 | 邏輯回歸 | 決策樹 | SVM | 隨機森林 | |
---|---|---|---|---|---|
測試集score | 0.8733661278988053 | 0.6929023190442727 | 0.8042164441321152 | 0.7114546732255798 | 0.947013352073085 |
精準率 | 0.8785353535353535 | 0.7455587392550144 | 0.7658039881204921 | 0.7404898384575299 | 0.9471813103098019 |
召回率 | 0.8922800718132855 | 0.6673506027186458 | 0.9258784303667608 | 0.7289048473967684 | 0.956655552705822 |
F1-Score | 0.8853543707850872 | 0.7042901610502098 | 0.8382677348194589 | 0.7346516737753651 | 0.9518948577261708 |
提取特征的模型訓(xùn)練(藍色為最大值杭隙,綠色為次大值)
k近鄰 | 邏輯回歸 | 決策樹 | SVM | 隨機森林 | |
---|---|---|---|---|---|
測試集score | 0.9233603537214443 | 0.9737165315647262 | 0.90084336362892 | 0.9619258167526407 | 0.9647916154916892 |
精準率 | 0.9194262813752373 | 0.9922813036020584 | 0.951904296875 | 0.9623700623700624 | 0.9618792499484855 |
召回率 | 0.8872379401587625 | 0.941990637085284 | 0.7936087929981681 | 0.9421941787095461 | 0.9501323020557704 |
F1-Score | 0.9030453697949038 | 0.9664821969301451 | 0.8655788655788657 | 0.9521752545510646 | 0.9559696907638747 |
- 通過上面兩張表的對比可以看出哟绊,隨機森林作為一種集成學習算法其在兩種特征提取方法種均有較好的表現(xiàn),但其單個模型的訓(xùn)練時間均較長痰憎。
- 由于TF-IDF提取的特征維度為33550票髓,所以上述每個訓(xùn)練模型都取得較好結(jié)果;但是自定義特征僅僅選取了11個維度铣耘,如果添加更多的特征維度其模型訓(xùn)練的性能和效果將會超過TF-IDF洽沟。
- 在特征維度較大的情況下,邏輯回歸不論在性能和效果上仍然有較好的表現(xiàn)蜗细,這一點值得進行后續(xù)的探討裆操。
- 在自定義的特征維度下,即特征維度僅為11時,k近鄰算法的表現(xiàn)最好踪区,預(yù)測結(jié)果不具有可解釋性昆烁。
心得體會
通過上面的實驗也可以看出,數(shù)據(jù)對機器學習的算法影響很大朽缴,選擇什么樣的方法進行特征提取善玫,對后續(xù)的模型訓(xùn)練至關(guān)重要水援。
以我自己目前的理解和水平密强,后續(xù)一段時間進行機器學習這一領(lǐng)域的探索可以從以下三個方面來入手:
- 一是針對數(shù)據(jù)進行更好特征提取,比如這次實驗中的數(shù)據(jù)蜗元,對HTTP攻擊的理解越深入我們越能對其進行特征描述或渤,最終高度刻畫HTTP的攻擊流量。此外奕扣,不限于HTTP的攻擊流量薪鹦,如果針對網(wǎng)絡(luò)流量包進行深入的刻畫、分析惯豆,并能總結(jié)出一種類似TF-IDF的特征提取模型也是極好的池磁。
- 二是深入理解現(xiàn)有的機器算法。以這次的實驗為例楷兽,邏輯回歸為什么在特征維度極大的數(shù)據(jù)集中仍然有較好的表現(xiàn)地熄?針對現(xiàn)有的特征向量如何結(jié)合模型的設(shè)計原理尋找最佳參數(shù),而不是通過網(wǎng)格搜索漫無目的地遍歷芯杀?這些問題都依賴于對機器算法更深入的了解端考。
- 三是探索更高階的機器學習算法,比如深度學習揭厚。從本次的實驗可以看出却特,傳統(tǒng)的機器學習方法依賴于特征工程,需要人為地進行特征提壬冈病裂明;但是深度學習可以自動地找出這個分類問題所需要的重要特征,因此探索深度學習的算法也可以作為后續(xù)的學習方向太援。