44 KiB
44 KiB
In [1]:
import pandas as pd
# Load the dataset
data = pd.read_csv("./demo_dataset.csv")
In [2]:
# Display the first few rows of the dataset
print(data.head())
log_id source_ip destination_port protocol bytes_transferred \ 0 10 10.0.0.100 STRING_PORT FTP 4096 1 12 172.16.254.100 110 POP3 NEGATIVE 2 27 172.16.254.200 110 POP3 NON_NUMERIC 3 1 192.168.1.100 80 HTTP 1024 4 2 192.168.1.81 53 TLS 9765 threat_level 0 ? 1 1 2 1 3 0 4 0
In [3]:
# Get a summary of column data types and non-null counts
print(data.info())
<class 'pandas.core.frame.DataFrame'> RangeIndex: 100 entries, 0 to 99 Data columns (total 6 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 log_id 100 non-null int64 1 source_ip 99 non-null object 2 destination_port 99 non-null object 3 protocol 100 non-null object 4 bytes_transferred 100 non-null object 5 threat_level 100 non-null object dtypes: int64(1), object(5) memory usage: 4.8+ KB None
In [4]:
print(data.isnull().sum())log_id 0 source_ip 1 destination_port 1 protocol 0 bytes_transferred 0 threat_level 0 dtype: int64
In [5]:
import re
def is_valid_ip(ip):
pattern = re.compile(r'^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$')
return bool(pattern.match(ip))
# Check for invalid IP addresses
invalid_ips = data[~data['source_ip'].astype(str).apply(is_valid_ip)]
print(invalid_ips)
log_id source_ip destination_port protocol bytes_transferred \ 40 41 10.0.0.300 25 SMTP 4096 51 52 10.10.10.450 STRING_PORT FTP 4096 55 56 NaN 53 DNS 1024 57 58 192.168.1.475 NaN UDP 2048 63 64 MISSING_IP 53 DNS 1024 65 66 192.168.1.600 UNUSED_PORT UDP 2048 71 72 MISSING_IP 53 DNS 1024 74 75 172.16.1.400 80 HTTP 1024 82 83 172.16.1.450 80 HTTP 1024 87 88 MISSING_IP 53 DNS 1024 88 89 10.10.10.700 443 TLS 512 92 93 INVALID_IP 110 POP3 4096 93 94 192.168.1.1050 53 DNS NON_NUMERIC 95 96 MISSING_IP 25 SMTP 4096 97 98 192.168.1.1100 UNUSED_PORT UDP 2048 threat_level 40 0 51 ? 55 0 57 1 63 0 65 1 71 0 74 0 82 0 87 0 88 1 92 1 93 0 95 1 97 0
In [6]:
def is_valid_port(port):
try:
port = int(port)
return 0 <= port <= 65535
except ValueError:
return False
# Check for invalid port numbers
invalid_ports = data[~data['destination_port'].apply(is_valid_port)]
print(invalid_ports)log_id source_ip destination_port protocol bytes_transferred \ 0 10 10.0.0.100 STRING_PORT FTP 4096 34 35 192.168.1.200 STRING_PORT FTP 4096 51 52 10.10.10.450 STRING_PORT FTP 4096 57 58 192.168.1.475 NaN UDP 2048 65 66 192.168.1.600 UNUSED_PORT UDP 2048 67 68 10.10.10.77 STRING_PORT FTP 4096 78 79 172.16.254.77 999999 HTTP 2048 97 98 192.168.1.1100 UNUSED_PORT UDP 2048 threat_level 0 ? 34 ? 51 ? 57 1 65 1 67 ? 78 1 97 0
In [7]:
valid_protocols = ['TCP', 'TLS', 'SSH', 'POP3', 'DNS', 'HTTPS', 'SMTP', 'FTP', 'UDP', 'HTTP']
# Check for invalid protocol values
invalid_protocols = data[~data['protocol'].isin(valid_protocols)]
print(invalid_protocols)log_id source_ip destination_port protocol bytes_transferred \ 30 31 192.168.1.119 443 UNKNOWN 9513 80 81 192.168.1.224 25 UNKNOWN 1161 threat_level 30 2 80 1
In [8]:
def is_valid_bytes(bytes):
try:
bytes = int(bytes)
return bytes >= 0
except ValueError:
return False
# Check for invalid bytes transferred
invalid_bytes = data[~data['bytes_transferred'].apply(is_valid_bytes)]
print(invalid_bytes)
log_id source_ip destination_port protocol bytes_transferred \ 1 12 172.16.254.100 110 POP3 NEGATIVE 2 27 172.16.254.200 110 POP3 NON_NUMERIC 93 94 192.168.1.1050 53 DNS NON_NUMERIC threat_level 1 1 2 1 93 0
In [9]:
def is_valid_threat_level(threat_level):
try:
threat_level = int(threat_level)
return 0 <= threat_level <= 2
except ValueError:
return False
# Check for invalid threat levels
invalid_threat_levels = data[~data['threat_level'].apply(is_valid_threat_level)]
print(invalid_threat_levels)
log_id source_ip destination_port protocol bytes_transferred \ 0 10 10.0.0.100 STRING_PORT FTP 4096 34 35 192.168.1.200 STRING_PORT FTP 4096 51 52 10.10.10.450 STRING_PORT FTP 4096 67 68 10.10.10.77 STRING_PORT FTP 4096 threat_level 0 ? 34 ? 51 ? 67 ?
In [10]:
# the ignore errors covers the fact that there might be some overlap between indexes that match other invalid criteria
data = data.drop(invalid_ips.index, errors='ignore')
data = data.drop(invalid_ports.index, errors='ignore')
data = data.drop(invalid_protocols.index, errors='ignore')
data = data.drop(invalid_bytes.index, errors='ignore')
data = data.drop(invalid_threat_levels.index, errors='ignore')
print(data.describe(include='all'))
log_id source_ip destination_port protocol bytes_transferred \
count 77.000000 77 77 77 77
unique NaN 68 6 9 73
top NaN 192.168.1.55 80 HTTP 1024
freq NaN 3 22 22 4
mean 46.519481 NaN NaN NaN NaN
std 28.591317 NaN NaN NaN NaN
min 1.000000 NaN NaN NaN NaN
25% 22.000000 NaN NaN NaN NaN
50% 45.000000 NaN NaN NaN NaN
75% 70.000000 NaN NaN NaN NaN
max 100.000000 NaN NaN NaN NaN
threat_level
count 77
unique 3
top 1
freq 26
mean NaN
std NaN
min NaN
25% NaN
50% NaN
75% NaN
max NaN
In [11]:
import pandas as pd
import numpy as np
import re
from ipaddress import ip_address
df = pd.read_csv('demo_dataset.csv')
invalid_ips = ['INVALID_IP', 'MISSING_IP']
invalid_ports = ['STRING_PORT', 'UNUSED_PORT']
invalid_bytes = ['NON_NUMERIC', 'NEGATIVE']
invalid_threat = ['?']
df.replace(invalid_ips + invalid_ports + invalid_bytes + invalid_threat, np.nan, inplace=True)
df['destination_port'] = pd.to_numeric(df['destination_port'], errors='coerce')
df['bytes_transferred'] = pd.to_numeric(df['bytes_transferred'], errors='coerce')
df['threat_level'] = pd.to_numeric(df['threat_level'], errors='coerce')
def is_valid_ip(ip):
pattern = re.compile(r'^((25[0-5]|2[0-4][0-9]|[01]?\d?\d)\.){3}(25[0-5]|2[0-4]\d|[01]?\d?\d)$')
if pd.isna(ip) or not pattern.match(str(ip)):
return np.nan
return ip
df['source_ip'] = df['source_ip'].apply(is_valid_ip)
In [12]:
import pandas as pd
import numpy as np
import re
from ipaddress import ip_address
df = pd.read_csv('demo_dataset.csv')
invalid_ips = ['INVALID_IP', 'MISSING_IP']
invalid_ports = ['STRING_PORT', 'UNUSED_PORT']
invalid_bytes = ['NON_NUMERIC', 'NEGATIVE']
invalid_threat = ['?']
df.replace(invalid_ips + invalid_ports + invalid_bytes + invalid_threat, np.nan, inplace=True)
df['destination_port'] = pd.to_numeric(df['destination_port'], errors='coerce')
df['bytes_transferred'] = pd.to_numeric(df['bytes_transferred'], errors='coerce')
df['threat_level'] = pd.to_numeric(df['threat_level'], errors='coerce')
def is_valid_ip(ip):
pattern = re.compile(r'^((25[0-5]|2[0-4][0-9]|[01]?\d?\d)\.){3}(25[0-5]|2[0-4]\d|[01]?\d?\d)$')
if pd.isna(ip) or not pattern.match(str(ip)):
return np.nan
return ip
df['source_ip'] = df['source_ip'].apply(is_valid_ip)
In [13]:
from sklearn.impute import SimpleImputer
numeric_cols = ['destination_port', 'bytes_transferred', 'threat_level']
categorical_cols = ['protocol']
num_imputer = SimpleImputer(strategy='median')
df[numeric_cols] = num_imputer.fit_transform(df[numeric_cols])
cat_imputer = SimpleImputer(strategy='most_frequent')
df[categorical_cols] = cat_imputer.fit_transform(df[categorical_cols])
In [14]:
valid_protocols = ['TCP', 'TLS', 'SSH', 'POP3', 'DNS', 'HTTPS', 'SMTP', 'FTP', 'UDP', 'HTTP']
df.loc[~df['protocol'].isin(valid_protocols), 'protocol'] = df['protocol'].mode()[0]
df['source_ip'] = df['source_ip'].fillna('0.0.0.0')
df['destination_port'] = df['destination_port'].clip(lower=0, upper=65535)
In [15]:
print(df.describe(include='all'))
log_id source_ip destination_port protocol bytes_transferred \
count 100.000000 100 100.000000 100 100.00000
unique NaN 76 NaN 9 NaN
top NaN 0.0.0.0 NaN HTTP NaN
freq NaN 15 NaN 27 NaN
mean 50.500000 NaN 776.860000 NaN 4138.64000
std 29.011492 NaN 6542.582099 NaN 2526.40978
min 1.000000 NaN 22.000000 NaN 498.00000
25% 25.750000 NaN 53.000000 NaN 1693.25000
50% 50.500000 NaN 80.000000 NaN 4096.00000
75% 75.250000 NaN 110.000000 NaN 5971.75000
max 100.000000 NaN 65535.000000 NaN 9765.00000
threat_level
count 100.000000
unique NaN
top NaN
freq NaN
mean 0.930000
std 0.781801
min 0.000000
25% 0.000000
50% 1.000000
75% 2.000000
max 2.000000
In [16]:
from sklearn.impute import KNNImputer
knn_imputer = KNNImputer(n_neighbors=5)
df[numeric_cols] = knn_imputer.fit_transform(df[numeric_cols])
In [17]:
from sklearn.impute import SimpleImputer
numeric_cols = ['destination_port', 'bytes_transferred', 'threat_level']
categorical_cols = ['protocol']
num_imputer = SimpleImputer(strategy='median')
df[numeric_cols] = num_imputer.fit_transform(df[numeric_cols])
cat_imputer = SimpleImputer(strategy='most_frequent')
df[categorical_cols] = cat_imputer.fit_transform(df[categorical_cols])
In [18]:
import pandas as pd
import numpy as np
import re
from ipaddress import ip_address
df = pd.read_csv('demo_dataset.csv')
invalid_ips = ['INVALID_IP', 'MISSING_IP']
invalid_ports = ['STRING_PORT', 'UNUSED_PORT']
invalid_bytes = ['NON_NUMERIC', 'NEGATIVE']
invalid_threat = ['?']
df.replace(invalid_ips + invalid_ports + invalid_bytes + invalid_threat, np.nan, inplace=True)
df['destination_port'] = pd.to_numeric(df['destination_port'], errors='coerce')
df['bytes_transferred'] = pd.to_numeric(df['bytes_transferred'], errors='coerce')
df['threat_level'] = pd.to_numeric(df['threat_level'], errors='coerce')
def is_valid_ip(ip):
pattern = re.compile(r'^((25[0-5]|2[0-4][0-9]|[01]?\d?\d)\.){3}(25[0-5]|2[0-4]\d|[01]?\d?\d)$')
if pd.isna(ip) or not pattern.match(str(ip)):
return np.nan
return ip
df['source_ip'] = df['source_ip'].apply(is_valid_ip)
In [19]:
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder(handle_unknown='ignore', sparse_output=False)
encoded = encoder.fit_transform(df[['protocol']])
encoded_df = pd.DataFrame(encoded, columns=encoder.get_feature_names_out(['protocol']))
df = pd.concat([df.drop('protocol', axis=1), encoded_df], axis=1)
In [20]:
dfOut [20]:
| log_id | source_ip | destination_port | bytes_transferred | threat_level | protocol_DNS | protocol_FTP | protocol_HTTP | protocol_HTTPS | protocol_POP3 | protocol_SMTP | protocol_SSH | protocol_TLS | protocol_UDP | protocol_UNKNOWN | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 10 | 10.0.0.100 | NaN | 4096.0 | NaN | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 1 | 12 | 172.16.254.100 | 110.0 | NaN | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 2 | 27 | 172.16.254.200 | 110.0 | NaN | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 3 | 1 | 192.168.1.100 | 80.0 | 1024.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 4 | 2 | 192.168.1.81 | 53.0 | 9765.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 95 | 96 | NaN | 25.0 | 4096.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 96 | 97 | 192.168.1.60 | 80.0 | 4351.0 | 1.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 97 | 98 | NaN | NaN | 2048.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 |
| 98 | 99 | 192.168.1.16 | 22.0 | 9069.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 99 | 100 | 192.168.1.14 | 25.0 | 1494.0 | 2.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 |
100 rows × 15 columns
In [21]:
import numpy as np
# Apply logarithmic transformation to a skewed feature to reduce its skewness
df["bytes_transferred"] = np.log1p(df["bytes_transferred"]) # Add 1 to avoid log(0)
In [22]:
from sklearn.model_selection import train_test_split
# Separate features (X) and target (y)
X = df.drop("threat_level", axis=1)
y = df["threat_level"]
# Initial split: 80% training, 20% testing
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1337)
# Second split: from the 80% training portion, allocate 60% for final training and 20% for validation
X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=0.25, random_state=1337)
In [23]:
XOut [23]:
| log_id | source_ip | destination_port | bytes_transferred | protocol_DNS | protocol_FTP | protocol_HTTP | protocol_HTTPS | protocol_POP3 | protocol_SMTP | protocol_SSH | protocol_TLS | protocol_UDP | protocol_UNKNOWN | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 10 | 10.0.0.100 | NaN | 8.318010 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 1 | 12 | 172.16.254.100 | 110.0 | NaN | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 2 | 27 | 172.16.254.200 | 110.0 | NaN | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 3 | 1 | 192.168.1.100 | 80.0 | 6.932448 | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 4 | 2 | 192.168.1.81 | 53.0 | 9.186662 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 95 | 96 | NaN | 25.0 | 8.318010 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 96 | 97 | 192.168.1.60 | 80.0 | 8.378391 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 97 | 98 | NaN | NaN | 7.625107 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 |
| 98 | 99 | 192.168.1.16 | 22.0 | 9.112728 | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 99 | 100 | 192.168.1.14 | 25.0 | 7.309881 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 |
100 rows × 14 columns
In [24]:
yOut [24]:
0 NaN
1 1.0
2 1.0
3 0.0
4 0.0
...
95 1.0
96 1.0
97 0.0
98 0.0
99 2.0
Name: threat_level, Length: 100, dtype: float64In [ ]: