Вредоносные веб-страницы - это страницы, которые устанавливают в вашу систему вредоносное ПО, которое нарушает работу компьютера и собирает вашу личную информацию и многие худшие случаи. Классификация этих веб-страниц в Интернете является очень важным аспектом для обеспечения безопасного просмотра пользователем.

Цель состоит в том, чтобы разделить веб-страницы на две категории: вредоносные [плохие] и безопасные [хорошие] веб-страницы. Для конкретной цели сначала выполняется предварительная обработка набора данных и обучение модели 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