Вредоносные веб-страницы - это страницы, которые устанавливают в вашу систему вредоносное ПО, которое нарушает работу компьютера и собирает вашу личную информацию и многие худшие случаи. Классификация этих веб-страниц в Интернете является очень важным аспектом для обеспечения безопасного просмотра пользователем.
Цель состоит в том, чтобы разделить веб-страницы на две категории: вредоносные [плохие] и безопасные [хорошие] веб-страницы. Для конкретной цели сначала выполняется предварительная обработка набора данных и обучение модели DNN, которая реализована в Pytorch.
Набор данных, используемый в проекте, взят из Mendeley Data. Набор данных содержит такие функции, как необработанное содержимое веб-страницы, географическое положение, длина javascript, скрытый код JavaScript веб-страницы и т. Д. Набор данных содержит около 1,5 миллионов веб-страниц, из которых 1,2 миллиона предназначены для обучения и 300 КБ для тестирования. Фрагмент набора данных приведен ниже.
Набор данных сильно искажен, 97,73% набора данных - это доброкачественные веб-страницы, а 2,27% - вредоносные веб-страницы, поэтому важен тщательный выбор показателей оценки, поскольку просто точность не дает правильной оценки, поэтому мы будем использовать f1_score, отзыв и путаницу матрица.
Теперь, во-первых, мы импортируем все необходимые библиотеки.
# Importing the Required Libraries import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim from torch.utils.data import Dataset, DataLoader from sklearn import metrics from sklearn.preprocessing import LabelEncoder, StandardScaler
После импорта всех необходимых библиотек мы импортируем набор данных, затем предварительно обработаем его и добавим в набор данных некоторые функции. [Обширный исследовательский анализ данных проводится перед предварительной обработкой и разработкой функций, и он приведен в полной записной книжке на Kaggle]. Мы добавим следующие функции: Тип сети, Количество специальных символов в необработанном контенте и Длина контента.
# Importing the dataset df_train=pd.read_csv("../input/Webpages_Classification_train_data.csv") df_test=pd.read_csv("../input/Webpages_Classification_test_data.csv") # Dropping the redundant column df_train.drop(columns = "Unnamed: 0", inplace = True) df_test.drop(columns = "Unnamed: 0", inplace = True)
Теперь мы напишем несколько функций для добавления упомянутых функций, функции содержатся в классе с именем 'preproc', первая функция - подсчитать специальный символ в необработанном содержимом, а вторая функция - назначить тип сеть ('A', 'B' и 'C') с использованием IP-адреса, для получения дополнительной информации посетите Сетевые классы.
class preproc: # Counting the Special Characters in the content def count_special(string): count = 0 for char in string: if not(char.islower()) and not(char.isupper()) and not(char.isdigit()): if char != ' ': count += 1 return count # Identifying the type of network [A, B, C] def network_type(ip): ip_str = ip.split(".") ip = [int(x) for x in ip_str] if ip[0]>=0 and ip[0]<=127: return (ip_str[0], "A") elif ip[0]>=128 and ip[0]<=191: return (".".join(ip_str[0:2]), "B") else: return (".".join(ip_str[0:3]), "C")
Теперь, используя функции для генерации функций, а также взяв длину необработанного содержимого.
# Adding Feature that shows the Network type df_train['Network']= df_train['ip_add'].apply(lambda x : preproc.network_type(x)) #Getting the Network type df_train['net_part'], df_train['net_type'] = zip(*df_train.Network) df_train.drop(columns = ['Network'], inplace = True) # Adding Feature that shows the Number of Special Character in the Content df_train['special_char'] = df_train['content'].apply(lambda x: preproc.count_special(x)) # Length of the Content df_train['content_len'] = df_train['content'].apply(lambda x: len(x))
Теперь функции добавляются в набор данных до кодирования и нормализации метки,
Мы предварительно обработаем набор данных и удалим ненужные столбцы, воспользуемся Scikit-learn LabelEncoder для кодирования меток и стандартным скаляром для нормализации. Мы опустим столбцы - ‘url’, ‘ip_add’ и ‘content’. и предварительно обработать его. Здесь le_dict и ss_dict имеют экземпляры кодировщика меток и стандартные скалярные экземпляры, поэтому мы можем использовать их для набора данных тестирования.
# This le_dict will save the Label Encoder Class so that the same Label Encoder instance can be used for the test dataset le_dict = {} for feature in ls: le = LabelEncoder() le_dict[feature] = le df_train[feature] = le.fit_transform(df_train[feature]) # Encoding the Labels df_train.label.replace({'bad' : 1, 'good' : 0}, inplace = True) # Normalizing the 'content_len' and 'special_char' in training data ss_dict = {} for feature in ['content_len', 'special_char']: ss = StandardScaler() ss_fit = ss.fit(df_train[feature].values.reshape(-1, 1)) ss_dict[feature] = ss_fit d = ss_fit.transform(df_train[feature].values.reshape(-1, 1)) df_train[feature] = pd.DataFrame(d, index = df_train.index, columns = [feature])
Данные обучения после предварительной обработки,
Мы построим тепловую карту корреляции функций, чтобы увидеть корреляцию функций с меткой.
# Pearson Correlation Heatmap plt.rcParams['figure.figsize'] == [18, 16] sns.set(font_scale = 1) sns.heatmap(df_train.corr(method = 'pearson'), annot = True, cmap = "YlGnBu");
Есть несколько интересных вещей, на которые стоит обратить внимание: 'content_len', 'special_char', 'js_obf_len' и 'js_len 'имеют очень высокую положительную корреляцию с лейблом. Есть и более интересные выводы о вредоносных и безопасных веб-страницах. (Пожалуйста, проверьте полную Записную книжку Kaggle для обширного EDA)
Теперь, продолжая предварительную обработку, мы просто применим те же функции и проделаем тот же процесс с данными тестирования. [Я просто покажу, как использовать одни и те же le_dict и ss_dict для кодирования и нормализации меток, поскольку предыдущие шаги для разработки функций такие же.]
# Using the same label encoders for the features as used in the training dataset for feature in ls: le = le_dict[feature] df_test[feature] = le.fit_transform(df_test[feature]) df_test.label.replace({'bad' : 1, 'good' : 0}, inplace = True) # Normalizing the 'content_len' and 'special_char' in testing data ss_fit = ss_dict['content_len'] d = ss_fit.transform(df_test['content_len'].values.reshape(-1, 1)) df_test['content_len'] = pd.DataFrame(d, index = df_test.index, columns = ['content_len']) ss_fit = ss_dict['special_char'] d = ss_fit.transform(df_test['special_char'].values.reshape(-1, 1)) df_test['special_char'] = pd.DataFrame(d, index = df_test.index, columns = ['special_char'])
Данные тестирования после предварительной обработки,
Теперь вся предварительная обработка завершена, мы будем моделировать с помощью PyTorch, и для этого сначала нам нужно создать собственный набор данных и загрузчик данных.
Мы установим класс конфигурации, который включает размер пакета, устройство (CPU или GPU), скорость обучения и эпохи .
# Configuration Class class config: BATCH_SIZE = 128 DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") LEARNING_RATE = 2e-5 EPOCHS = 20
Теперь мы создадим собственный набор данных и загрузчик данных, используя «Dataset» и «DataLoader» из PyTorch. Для тестирования мы установим размер пакета равным 1. df_train_loader и df_test_loader - это последний загрузчик данных, который мы будем использовать для обучения и тестирования.
# Making the custom dataset for pytorch class MaliciousBenignData(Dataset): def __init__(self, df): self.df = df self.input = self.df.drop(columns = ['label']).values self.target = self.df.label def __len__(self): return (len(self.df)) def __getitem__(self, idx): return (torch.tensor(self.input[idx]), torch.tensor(self.target[idx])) # Creating the dataloader for pytorch def create_dataloader(df, batch_size): cls = MaliciousBenignData(df) return DataLoader( cls, batch_size = batch_size, num_workers = 0 ) df_train_loader = create_dataloader(df_train, batch_size=config.BATCH_SIZE) df_test_loader = create_dataloader(df_test, batch_size = 1) # Here for testing using the batch size as 1
Пользовательские загрузчики данных готовы !! Пришло время создать модель. А пока мы будем упрощать DNN.
# Making the DNN model class dnn(nn.Module): def __init__(self): super(dnn, self).__init__() self.fc1 = nn.Linear(10, 64) self.fc2 = nn.Linear(64, 128) self.fc3 = nn.Linear(128, 128) self.out = nn.Linear(128, 1) self.dropout1 = nn.Dropout(p = 0.2) self.dropout2 = nn.Dropout(p = 0.3) self.batchn1 = nn.BatchNorm1d(num_features = 64) self.batchn2 = nn.BatchNorm1d(num_features = 128) def forward(self, inputs): t = self.fc1(inputs) t = F.relu(t) t = self.batchn1(t) t = self.dropout1(t) t = self.fc2(t) t = F.relu(t) t = self.batchn2(t) t = self.dropout2(t) t = self.fc3(t) t = F.relu(t) t = self.out(t) return t
Теперь перенесем модель на соответствующее устройство (указанное в классе конфигурации). Критерий и Оптимизатор для модели будут BCEWithLogitsLoss и Adam соответственно.
# Transfer the model on the device -- 'GPU' if available or Default 'CPU' model = dnn() model.to(config.DEVICE) # Criterion and the Optimizer for the model criterion = nn.BCEWithLogitsLoss() optimizer = optim.Adam(model.parameters(), lr= config.LEARNING_RATE)
Мы напишем очень простую функцию двоичной точности, которая будет вычислять двоичную точность для каждой эпохи. здесь сигмовидные и круглые функции из PyTorch используются для получения прогноза.
def binary_acc(predictions, y_test): y_pred = torch.round(torch.sigmoid(predictions)) correct = (y_pred == y_test).sum().float() acc = torch.round((correct/y_test.shape[0])*100) return acc
Наконец, мы напишем функцию обучения и функцию оценки (очень часто в PyTorch).
Функция обучения принимает 'устройство', 'data_loader', 'optimizer', 'критерий' и 'модель' как параметры. Мы рассчитаем потерю эпох с помощью BCEWithLogitsLoss, а точность - с помощью функции двоичной точности, которую мы написали.
# Training function def train_model(model, device, data_loader, optimizer, criterion): # Putting the model in training mode model.train() for epoch in range(1, config.EPOCHS+1): epoch_loss = 0 epoch_acc = 0 for X, y in data_loader: X = X.to(device) y_ = torch.tensor(y.unsqueeze(1), dtype = torch.float32) y = y_.to(device) # Zeroing the gradient optimizer.zero_grad() predictions = model(X.float()) loss = criterion(predictions, y) acc = binary_acc(predictions, y) loss.backward() # Calculate Gradient optimizer.step() # Updating Weights epoch_loss += loss.item() epoch_acc += acc.item() print (f"Epoch -- {epoch} | Loss : {epoch_loss/len(data_loader): .5f} | Accuracy : {epoch_acc/len(data_loader): .5f}")
Функция оценки будет очень похожей, но сначала мы переведем модель в режим оценки. мы будем использовать torch.no_grad (), чтобы уменьшить потребление памяти. Функция оценки вернет ‘y_test_al’ и ‘y_pred’, которые являются метками True и прогнозируемыми значениями соответственно.
# Evaluation Function def eval_model(model, device, data_loader): # Putting the model in evaluation mode model.eval() y_pred = [] y_test_al = [] with torch.no_grad(): for X_test, y_test in data_loader: X_test = X_test.to(device) predictions = model(X_test.float()) pred = torch.round(torch.sigmoid(predictions)) y_test_al.append(y_test.tolist()) y_pred.append(pred.tolist()) # Changing the Predictions into list y_test_al = [ele[0] for ele in y_test_al] y_pred = [int(ele[0][0]) for ele in y_pred] # the format of the prediction is [[[0]], [[1]]] return (y_test_al, y_pred)
Теперь все готово, и все, что нам нужно сделать, это обучить модель, оценить модель и проверить ее работоспособность. Для обучения мы просто вызовем функцию обучения.
# Training the Model train_model(model, config.DEVICE, df_train_loader, optimizer, criterion)
Для оценки вызов функции оценки. У нас будут истинные ярлыки и прогнозы в виде списка, и можно будет использовать показатели из sklearn.
# Evaluating the model and getting the predictions y_test, preds = eval_model(model, config.DEVICE, df_test_loader)
Мы воспользуемся классификационным_отчетом из sklearn.metrics и построим тепловую карту матрицы неточностей с помощью seaborn.
# Classification Report cls_report = metrics.classification_report(y_test, preds) print ("") print (f"Accuracy : {metrics.accuracy_score(y_test, preds)*100 : .3f} %") print ("") print ("Classification Report : ") print (cls_report) # Setting the params for the plot plt.rcParams['figure.figsize'] = [10, 7] sns.set(font_scale = 1.2) # Confusion Matrix cm = metrics.confusion_matrix(y_test, preds) # Plotting the Confusion Matrix ax = sns.heatmap(cm, annot = True, cmap = 'YlGnBu') ax.set(title = "Confusion Matrix", xlabel = 'Predicted Labels', ylabel = 'True Labels');
Здесь мы можем проигнорировать точность, поскольку это будет просто предвзятым отношением, но мы видим, что f1_score и оценка отзыва очень хороши, и в соответствии с метриками путаницы наша модель может довольно хорошо классифицировать веб-страницы. Поскольку мы знали, что наш набор данных смещен, мы проигнорировали точность.
Самое интересное здесь то, что special_char, content_len, js_len и js_obf_len имеют очень высокую положительную корреляцию с ярлыками, поэтому они являются наиболее важными функциями, которые оценивают производительность модели. Есть много интересных выводов о вредоносных и безопасных веб-страницах, благодаря которым наша модель может работать так хорошо. (Я настоятельно рекомендую проверить EDA в Kaggle Notebook).
Я также обучил еще 3 модели машинного обучения на наборе данных для сравнения и развернул их с помощью Flask и PyWebIO. Я запустил блокнот kaggle только для модели DNN. Весь код и развёртывание со всеми моделями есть у меня на Github.
P.S. Я все еще учусь, поэтому буду благодарен за отзывы :)
Ссылки:
[1] Github - https://github.com/SumitM0432/Malicious-Webpage-Classifier
[2] Блокнот Kaggle - https://www.kaggle.com/sumitm004/malicious-webpage-classifier-using-dnn-pytorch
[3] Набор данных - https://data.mendeley.com/datasets/gdx3pkwp47/2