Upload 16 files
Browse files- AnomalyDetection.joblib +3 -0
- app.py +109 -27
- main.ipynb +126 -9
AnomalyDetection.joblib
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:bc12a2bc1e9baf2ddea04cf92a42f1f745debf5f365456f84377dad16cc40b78
|
3 |
+
size 1131848
|
app.py
CHANGED
@@ -4,22 +4,21 @@ import joblib
|
|
4 |
import numpy as np
|
5 |
import pandas as pd
|
6 |
|
|
|
|
|
|
|
7 |
st.set_page_config(
|
8 |
page_title="NASA Turbofan Playground",
|
9 |
page_icon="🧊",
|
10 |
initial_sidebar_state="expanded",
|
11 |
-
|
12 |
-
'Get Help': 'https://www.extremelycoolapp.com/help',
|
13 |
-
'Report a bug': "https://www.extremelycoolapp.com/bug",
|
14 |
-
'About': "# This is a header. This is an *extremely* cool app!"
|
15 |
-
}
|
16 |
)
|
17 |
|
18 |
# Set the background image
|
19 |
background_image = """
|
20 |
<style>
|
21 |
[data-testid="stAppViewContainer"] > .main {
|
22 |
-
background-image: url("https://w.wallhaven.cc/full/
|
23 |
background-size: 100vw 100vh; # This sets the size to cover 100% of the viewport width and height
|
24 |
background-position: center;
|
25 |
background-repeat: no-repeat;
|
@@ -40,13 +39,13 @@ html_code = """
|
|
40 |
|
41 |
<style>
|
42 |
@keyframes rainbow-text-animation {
|
43 |
-
0% { color:
|
44 |
-
16.67% { color:
|
45 |
-
33.33% { color:
|
46 |
-
50% { color:
|
47 |
-
66.67% { color:
|
48 |
-
83.33% { color:
|
49 |
-
100% { color:
|
50 |
}
|
51 |
|
52 |
.title-container {
|
@@ -57,24 +56,24 @@ html_code = """
|
|
57 |
}
|
58 |
|
59 |
.neon-text {
|
60 |
-
font-family:
|
61 |
font-size: 4em;
|
62 |
margin: 0;
|
63 |
animation: rainbow-text-animation 5s infinite linear;
|
64 |
-
text-shadow: 0 0 5px rgba(
|
65 |
-
0 0 10px rgba(
|
66 |
-
0 0 20px rgba(
|
67 |
-
0 0 40px rgba(
|
68 |
-
0 0 80px rgba(
|
69 |
-
0 0 90px rgba(
|
70 |
-
0 0 100px rgba(
|
71 |
-
0 0 150px rgba(
|
72 |
}
|
73 |
</style>
|
74 |
"""
|
75 |
|
76 |
st.markdown(html_code, unsafe_allow_html=True)
|
77 |
-
|
78 |
|
79 |
st.markdown(
|
80 |
"""
|
@@ -163,16 +162,59 @@ for col, limits in columns.items():
|
|
163 |
|
164 |
# Main page
|
165 |
# st.title("Turbofan Engine RUL Prediction")
|
166 |
-
st.markdown('<p class="message-box">Turbofan Engine RUL Prediction</p>', unsafe_allow_html=True)
|
167 |
-
st.markdown('<p class="success-message2">Provide the necessary inputs in the sidebar to predict the Remaining Useful Life (RUL) of the turbofan engine.</p>', unsafe_allow_html=True)
|
168 |
# st.write("Provide the necessary inputs in the sidebar to predict the Remaining Useful Life (RUL) of the turbofan engine.")
|
169 |
|
170 |
# Prepare the input for prediction
|
171 |
input_data = [inputs[col] for col in columns]
|
172 |
input_data = [input_data] # Assuming the model expects a 2D array
|
173 |
|
|
|
174 |
# Predict the RUL
|
175 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
176 |
prediction = model.predict(input_data)
|
177 |
predicted_rul = int(prediction[0])
|
178 |
input_cycles = inputs['cycles']
|
@@ -219,4 +261,44 @@ if st.sidebar.button("Predict RUL"):
|
|
219 |
|
220 |
st.markdown(f'<p class="unsuccess-message">Potential anomaly detected in sensor: {anomaly_sensor}.</p>', unsafe_allow_html=True)
|
221 |
|
222 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
import numpy as np
|
5 |
import pandas as pd
|
6 |
|
7 |
+
from sklearn.ensemble import IsolationForest
|
8 |
+
|
9 |
+
|
10 |
st.set_page_config(
|
11 |
page_title="NASA Turbofan Playground",
|
12 |
page_icon="🧊",
|
13 |
initial_sidebar_state="expanded",
|
14 |
+
|
|
|
|
|
|
|
|
|
15 |
)
|
16 |
|
17 |
# Set the background image
|
18 |
background_image = """
|
19 |
<style>
|
20 |
[data-testid="stAppViewContainer"] > .main {
|
21 |
+
background-image: url("https://w.wallhaven.cc/full/yx/wallhaven-yxldyk.jpg");
|
22 |
background-size: 100vw 100vh; # This sets the size to cover 100% of the viewport width and height
|
23 |
background-position: center;
|
24 |
background-repeat: no-repeat;
|
|
|
39 |
|
40 |
<style>
|
41 |
@keyframes rainbow-text-animation {
|
42 |
+
0% { color: white; }
|
43 |
+
16.67% { color: grey; }
|
44 |
+
33.33% { color: grey; }
|
45 |
+
50% { color: black; }
|
46 |
+
66.67% { color: grey; }
|
47 |
+
83.33% { color: white; }
|
48 |
+
100% { color: white; }
|
49 |
}
|
50 |
|
51 |
.title-container {
|
|
|
56 |
}
|
57 |
|
58 |
.neon-text {
|
59 |
+
font-family: Trebuchet MS , sans-serif;
|
60 |
font-size: 4em;
|
61 |
margin: 0;
|
62 |
animation: rainbow-text-animation 5s infinite linear;
|
63 |
+
text-shadow: 0 0 5px rgba(0, 0, 0, 0.8),
|
64 |
+
0 0 10px rgba(0, 0, 0, 0.7),
|
65 |
+
0 0 20px rgba(0, 0, 0, 0.6),
|
66 |
+
0 0 40px rgba(0, 0, 0, 0.6),
|
67 |
+
0 0 80px rgba(0, 0, 0, 0.6),
|
68 |
+
0 0 90px rgba(0, 0, 0, 0.6),
|
69 |
+
0 0 100px rgba(0, 0, 0, 0.6),
|
70 |
+
0 0 150px rgba(0, 0, 0, 0.6);
|
71 |
}
|
72 |
</style>
|
73 |
"""
|
74 |
|
75 |
st.markdown(html_code, unsafe_allow_html=True)
|
76 |
+
|
77 |
|
78 |
st.markdown(
|
79 |
"""
|
|
|
162 |
|
163 |
# Main page
|
164 |
# st.title("Turbofan Engine RUL Prediction")
|
165 |
+
# st.markdown('<p class="message-box">Turbofan Engine RUL Prediction and Anomaly Detection</p>', unsafe_allow_html=True)
|
166 |
+
# st.markdown('<p class="success-message2">Provide the necessary inputs in the sidebar to predict the Remaining Useful Life (RUL) of the turbofan engine.</p>', unsafe_allow_html=True)
|
167 |
# st.write("Provide the necessary inputs in the sidebar to predict the Remaining Useful Life (RUL) of the turbofan engine.")
|
168 |
|
169 |
# Prepare the input for prediction
|
170 |
input_data = [inputs[col] for col in columns]
|
171 |
input_data = [input_data] # Assuming the model expects a 2D array
|
172 |
|
173 |
+
inp_sensor_data = input_data[0][1:]
|
174 |
# Predict the RUL
|
175 |
+
predict_rul = st.sidebar.button("Predict RUL")
|
176 |
+
if not predict_rul:
|
177 |
+
|
178 |
+
# Title and Description
|
179 |
+
|
180 |
+
st.write("Welcome to the NASA Turbofan Playground. This tool leverages machine learning "
|
181 |
+
"to predict Remaining Useful Life (RUL) and detect anomalies in turbofan engine sensor data.")
|
182 |
+
st.divider()
|
183 |
+
# About the Application
|
184 |
+
st.header("About the Application")
|
185 |
+
st.markdown("""
|
186 |
+
This application provides two main features:
|
187 |
+
- **Remaining Useful Life (RUL) Prediction:** Predicts when a turbofan engine is likely to fail based on its current sensor readings.
|
188 |
+
- **Sensor Anomaly Detection:** Identifies anomalies in the sensor data to detect potential issues before they lead to failures.
|
189 |
+
""")
|
190 |
+
st.divider()
|
191 |
+
# Key Features
|
192 |
+
st.header("Key Features")
|
193 |
+
st.markdown("""
|
194 |
+
- **RUL Prediction:** Input current sensor readings manually or via file upload to predict RUL.
|
195 |
+
- **Anomaly Detection:** Detect anomalies in sensor readings to monitor engine health.
|
196 |
+
""")
|
197 |
+
st.divider()
|
198 |
+
# Supported Sensors
|
199 |
+
st.header("Supported Sensors")
|
200 |
+
st.markdown("""
|
201 |
+
- **Temperature Sensors:** T24, T30, T50
|
202 |
+
- **Pressure Sensors:** P30, Ps30
|
203 |
+
- **Speed Sensors:** Nf, Nc, NRf, NRc
|
204 |
+
- **Bleed Air Sensors:** BPR, htBleed
|
205 |
+
- **Flow Sensors:** W31, W32
|
206 |
+
- **Other Parameters:** phi
|
207 |
+
""")
|
208 |
+
st.divider()
|
209 |
+
# Technology Stack
|
210 |
+
st.header("Technology Stack")
|
211 |
+
st.markdown("""
|
212 |
+
- **Frontend:** Streamlit
|
213 |
+
- **Backend:** Python
|
214 |
+
- **Machine Learning Models:** Random Forest (RUL Prediction), Isolation Forest (Anomaly Detection)
|
215 |
+
""")
|
216 |
+
|
217 |
+
if predict_rul:
|
218 |
prediction = model.predict(input_data)
|
219 |
predicted_rul = int(prediction[0])
|
220 |
input_cycles = inputs['cycles']
|
|
|
261 |
|
262 |
st.markdown(f'<p class="unsuccess-message">Potential anomaly detected in sensor: {anomaly_sensor}.</p>', unsafe_allow_html=True)
|
263 |
|
264 |
+
|
265 |
+
|
266 |
+
# Load the pre-trained model
|
267 |
+
model_path = 'AnomalyDetection.joblib'
|
268 |
+
iso_forest = joblib.load(model_path)
|
269 |
+
|
270 |
+
# Define sensor columns based on the dataset (excluding 'RUL' since it's not used for anomaly detection)
|
271 |
+
sensor_columns = ['T24', 'T30', 'T50', 'P30', 'Nf', 'Nc', 'Ps30', 'phi', 'NRf', 'NRc', 'BPR', 'htBleed', 'W31', 'W32']
|
272 |
+
|
273 |
+
|
274 |
+
|
275 |
+
# Button to run anomaly detection with the provided sensor data
|
276 |
+
if st.sidebar.button("Detect Anomaly of Sensors"):
|
277 |
+
# Convert inputs to DataFrame
|
278 |
+
input_data = pd.DataFrame([inp_sensor_data], columns=sensor_columns)
|
279 |
+
|
280 |
+
# Predict anomaly
|
281 |
+
anomaly_score = iso_forest.decision_function(input_data)
|
282 |
+
anomaly_flag = iso_forest.predict(input_data)
|
283 |
+
|
284 |
+
# Display results
|
285 |
+
st.write(f"Anomaly Score: {anomaly_score[0]:.4f}")
|
286 |
+
if anomaly_flag[0] == -1:
|
287 |
+
st.write("Anomaly Detected!")
|
288 |
+
else:
|
289 |
+
st.write("No Anomaly Detected.")
|
290 |
+
|
291 |
+
# Identify contributing sensors
|
292 |
+
sensor_importance = {}
|
293 |
+
for sensor in sensor_columns:
|
294 |
+
input_temp = input_data.copy()
|
295 |
+
input_temp[sensor] = 0 # Remove the influence of this sensor
|
296 |
+
temp_scores = iso_forest.decision_function(input_temp)
|
297 |
+
importance = np.mean(np.abs(anomaly_score - temp_scores))
|
298 |
+
sensor_importance[sensor] = importance
|
299 |
+
|
300 |
+
sorted_sensors = sorted(sensor_importance.items(), key=lambda x: x[1], reverse=True)
|
301 |
+
|
302 |
+
st.write("Sensors contributing to anomalies in descending order of importance: ")
|
303 |
+
for sensor, importance in sorted_sensors:
|
304 |
+
st.write(f"{sensor}: {importance:.4f}")
|
main.ipynb
CHANGED
@@ -3411,7 +3411,7 @@
|
|
3411 |
},
|
3412 |
{
|
3413 |
"cell_type": "code",
|
3414 |
-
"execution_count":
|
3415 |
"metadata": {},
|
3416 |
"outputs": [
|
3417 |
{
|
@@ -3698,7 +3698,7 @@
|
|
3698 |
"[20631 rows x 16 columns]"
|
3699 |
]
|
3700 |
},
|
3701 |
-
"execution_count":
|
3702 |
"metadata": {},
|
3703 |
"output_type": "execute_result"
|
3704 |
}
|
@@ -3710,7 +3710,7 @@
|
|
3710 |
},
|
3711 |
{
|
3712 |
"cell_type": "code",
|
3713 |
-
"execution_count":
|
3714 |
"metadata": {},
|
3715 |
"outputs": [
|
3716 |
{
|
@@ -3997,7 +3997,7 @@
|
|
3997 |
"[13096 rows x 16 columns]"
|
3998 |
]
|
3999 |
},
|
4000 |
-
"execution_count":
|
4001 |
"metadata": {},
|
4002 |
"output_type": "execute_result"
|
4003 |
}
|
@@ -10952,6 +10952,7 @@
|
|
10952 |
}
|
10953 |
],
|
10954 |
"source": [
|
|
|
10955 |
"RUL = 200\n",
|
10956 |
"current_cycle = 175\n",
|
10957 |
"percentage_rul = current_cycle/RUL \n",
|
@@ -10973,7 +10974,7 @@
|
|
10973 |
}
|
10974 |
],
|
10975 |
"source": [
|
10976 |
-
"\n",
|
10977 |
"if percentage_rul <.4:\n",
|
10978 |
" print(\"The Remaining Useful is more than 60%, \\nEngine Health is Excellent\")\n",
|
10979 |
"elif percentage_rul <.6:\n",
|
@@ -10984,19 +10985,135 @@
|
|
10984 |
" print(\"Warning the Remaining Useful is critical and less than 20%, \\nEngine maintainance Required\")"
|
10985 |
]
|
10986 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10987 |
{
|
10988 |
"cell_type": "code",
|
10989 |
-
"execution_count":
|
10990 |
"metadata": {},
|
10991 |
"outputs": [],
|
10992 |
-
"source": [
|
|
|
|
|
|
|
10993 |
},
|
10994 |
{
|
10995 |
"cell_type": "code",
|
10996 |
-
"execution_count":
|
10997 |
"metadata": {},
|
10998 |
"outputs": [],
|
10999 |
-
"source": [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11000 |
}
|
11001 |
],
|
11002 |
"metadata": {
|
|
|
3411 |
},
|
3412 |
{
|
3413 |
"cell_type": "code",
|
3414 |
+
"execution_count": 2,
|
3415 |
"metadata": {},
|
3416 |
"outputs": [
|
3417 |
{
|
|
|
3698 |
"[20631 rows x 16 columns]"
|
3699 |
]
|
3700 |
},
|
3701 |
+
"execution_count": 2,
|
3702 |
"metadata": {},
|
3703 |
"output_type": "execute_result"
|
3704 |
}
|
|
|
3710 |
},
|
3711 |
{
|
3712 |
"cell_type": "code",
|
3713 |
+
"execution_count": 3,
|
3714 |
"metadata": {},
|
3715 |
"outputs": [
|
3716 |
{
|
|
|
3997 |
"[13096 rows x 16 columns]"
|
3998 |
]
|
3999 |
},
|
4000 |
+
"execution_count": 3,
|
4001 |
"metadata": {},
|
4002 |
"output_type": "execute_result"
|
4003 |
}
|
|
|
10952 |
}
|
10953 |
],
|
10954 |
"source": [
|
10955 |
+
"# example\n",
|
10956 |
"RUL = 200\n",
|
10957 |
"current_cycle = 175\n",
|
10958 |
"percentage_rul = current_cycle/RUL \n",
|
|
|
10974 |
}
|
10975 |
],
|
10976 |
"source": [
|
10977 |
+
"# rules\n",
|
10978 |
"if percentage_rul <.4:\n",
|
10979 |
" print(\"The Remaining Useful is more than 60%, \\nEngine Health is Excellent\")\n",
|
10980 |
"elif percentage_rul <.6:\n",
|
|
|
10985 |
" print(\"Warning the Remaining Useful is critical and less than 20%, \\nEngine maintainance Required\")"
|
10986 |
]
|
10987 |
},
|
10988 |
+
{
|
10989 |
+
"cell_type": "markdown",
|
10990 |
+
"metadata": {},
|
10991 |
+
"source": [
|
10992 |
+
"## Creating a Anomaly Detection Model for sensor anomaly"
|
10993 |
+
]
|
10994 |
+
},
|
10995 |
{
|
10996 |
"cell_type": "code",
|
10997 |
+
"execution_count": 12,
|
10998 |
"metadata": {},
|
10999 |
"outputs": [],
|
11000 |
+
"source": [
|
11001 |
+
"sensor_columns = ['T24', 'T30', 'T50', 'P30', 'Nf', 'Nc', 'Ps30', 'phi', 'NRf',\n",
|
11002 |
+
" 'NRc', 'BPR', 'htBleed', 'W31', 'W32']"
|
11003 |
+
]
|
11004 |
},
|
11005 |
{
|
11006 |
"cell_type": "code",
|
11007 |
+
"execution_count": 13,
|
11008 |
"metadata": {},
|
11009 |
"outputs": [],
|
11010 |
+
"source": [
|
11011 |
+
"from sklearn.ensemble import IsolationForest"
|
11012 |
+
]
|
11013 |
+
},
|
11014 |
+
{
|
11015 |
+
"cell_type": "code",
|
11016 |
+
"execution_count": 18,
|
11017 |
+
"metadata": {},
|
11018 |
+
"outputs": [
|
11019 |
+
{
|
11020 |
+
"name": "stdout",
|
11021 |
+
"output_type": "stream",
|
11022 |
+
"text": [
|
11023 |
+
"Sensors contributing to anomalies in descending order of importance:\n",
|
11024 |
+
"P30: 0.0486\n",
|
11025 |
+
"W31: 0.0473\n",
|
11026 |
+
"W32: 0.0464\n",
|
11027 |
+
"phi: 0.0404\n",
|
11028 |
+
"T30: 0.0383\n",
|
11029 |
+
"Nf: 0.0350\n",
|
11030 |
+
"BPR: 0.0315\n",
|
11031 |
+
"NRf: 0.0313\n",
|
11032 |
+
"T24: 0.0297\n",
|
11033 |
+
"htBleed: 0.0290\n",
|
11034 |
+
"T50: 0.0269\n",
|
11035 |
+
"Ps30: 0.0219\n",
|
11036 |
+
"NRc: 0.0207\n",
|
11037 |
+
"Nc: 0.0202\n"
|
11038 |
+
]
|
11039 |
+
},
|
11040 |
+
{
|
11041 |
+
"data": {
|
11042 |
+
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA1sAAAIjCAYAAAD1OgEdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy80BEi2AAAACXBIWXMAAA9hAAAPYQGoP6dpAABURElEQVR4nO3deVxV1f7/8fdRBkUZnJi+Iqg5z1NEOSaJSl6nyjFxSjPU0uyqtxzrhmmZNpjVdagbptnXJk2vOJei5UDmkKlp5BXQnBBLFFi/P/pxvp3AAWR7QF7Px+M8Yq+99t6ffbaU79bea9uMMUYAAAAAgAJVwtkFAAAAAMCdiLAFAAAAABYgbAEAAACABQhbAAAAAGABwhYAAAAAWICwBQAAAAAWIGwBAAAAgAUIWwAAAABgAcIWAAAAAFiAsAUAKBA2m01Tp051dhnFSkhIiAYOHGj5cY4fPy6bzabFixfb2wYOHKiyZctafuxs/PkCUBQRtgDgNpk3b55sNptCQ0OdXUqRcPz4cQ0aNEjVq1dXqVKl5O/vr9atW2vKlCnOLs0Sbdu2lc1mk81mU4kSJeTl5aVatWrp0UcfVVxcXIEd58svvyy0oaUw1wYA+WEzxhhnFwEAxcF9992nkydP6vjx4zp8+LDuuusuZ5dUoGw2m6ZMmVIgf1k+cuSIWrRoodKlS2vw4MEKCQlRUlKSdu/erdWrV+vy5cu3XnAh07ZtWx09elQxMTGSpEuXLunIkSNasWKFfvrpJz3yyCP64IMP5Orqat8mPT1dJUqUcGi7kZEjR+rNN99UXv7zb4xRenq6XF1dVbJkSUl/jGx9/PHHSktLu+n93Eptly9flouLi1xcXArseABgNf6NBQC3wbFjx7Rt2zatWLFCw4cPV2xs7B07QlMQXn31VaWlpSkhIUHBwcEO606dOnVba7l06ZLKlClzW47l7e2t/v37O7TNmDFDo0eP1rx58xQSEqKXXnrJvs7d3d3SejIyMpSVlSU3NzeVKlXK0mPdiLOPDwD5wW2EAHAbxMbGqly5coqMjNRDDz2k2NjYHH2yn4t5+eWX9c4776h69epyd3dXixYt9O233+bov2HDBrVq1UplypSRj4+PunbtqoMHDzr0mTp1qmw2m3788Uf1799f3t7eqlSpkiZNmiRjjH755Rd17dpVXl5e8vf31yuvvOKw/ZUrVzR58mQ1a9ZM3t7eKlOmjFq1aqWNGzde93w3btwom82mTz75JMe6JUuWyGazKT4+/prbHz16VJUrV84RtCTJ19c3R9vq1avVpk0beXp6ysvLSy1atNCSJUsc+ixfvlzNmjVT6dKlVbFiRfXv31///e9/HfpkP4d09OhRde7cWZ6enurXr58kKSsrS3PmzFG9evVUqlQp+fn5afjw4Tp37pzDPnbu3KmIiAhVrFhRpUuXVtWqVTV48OBrf1k3ULJkSb322muqW7eu3njjDV24cMG+7q/PbF29elXTpk1TjRo1VKpUKVWoUEEtW7a034Y4cOBAvfnmm5Jkv2XRZrNJcvzzN2fOHPufvwMHDuT6zFa2n376SRERESpTpowCAwM1ffp0h5GpTZs2yWazadOmTQ7b/XWf16stu+2vo6Z79uxRp06d5OXlpbJly6p9+/bavn27Q5/FixfLZrNp69atGjt2rCpVqqQyZcqoe/fuOn369I0vAADcAka2AOA2iI2NVY8ePeTm5qY+ffrorbfe0rfffqsWLVrk6LtkyRJdvHhRw4cPl81m08yZM9WjRw/99NNP9tvF1q1bp06dOqlatWqaOnWqfv/9d73++uu67777tHv3boWEhDjss1evXqpTp45mzJihVatW6YUXXlD58uX19ttv6/7779dLL72k2NhYjRs3Ti1atFDr1q0lSampqfrXv/6lPn366LHHHtPFixe1YMECRURE6JtvvlHjxo1zPd+2bdsqKChIsbGx6t69e47vonr16goLC7vm9xUcHKx169Zpw4YNuv/++6/73S5evFiDBw9WvXr1NHHiRPn4+GjPnj1as2aN+vbta+8zaNAgtWjRQjExMUpJSdHcuXO1detW7dmzRz4+Pvb9ZWRkKCIiQi1bttTLL78sDw8PSdLw4cPt+xk9erSOHTumN954Q3v27NHWrVvl6uqqU6dOqUOHDqpUqZImTJggHx8fHT9+XCtWrLjuOdxIyZIl1adPH02aNElff/21IiMjc+03depUxcTEaOjQobr77ruVmpqqnTt3avfu3XrggQc0fPhwnTx5UnFxcfr3v/+d6z4WLVqky5cva9iwYXJ3d1f58uWVlZWVa9/MzEx17NhR99xzj2bOnKk1a9ZoypQpysjI0PTp0/N0jjdT25/t379frVq1kpeXl/7+97/L1dVVb7/9ttq2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tWqpXLlyt/cEAAAAABQbTg1baWlpSkhIUEJCgiTp2LFjSkhIUGJiotLS0vTMM89o+/btOn78uNavX6+uXbvqrrvuUkREhCSpTp066tixox577DF988032rp1q0aOHKnevXsrMDBQktS3b1+5ublpyJAh2r9/v5YtW6a5c+c63CYIAAAAAAXNqWFr586datKkiZo0aSJJGjt2rJo0aaLJkyerZMmS2rt3r/72t7+pZs2aGjJkiJo1a6avvvpK7u7u9n3Exsaqdu3aat++vTp37qyWLVs6vEPL29tba9eu1bFjx9SsWTM9/fTTmjx5MtO+AwAAALCUzRhjnF1EYZeamipvb29duHCB57cAAACcKGTCKmeXYLnjMyKdXQKuIy/ZoEg9swUAAAAARQVhCwAAAAAsQNgCAAAAAAsQtgAAAADAAi7OLgAAAADA/8nvJCBMrFH4MLIFAAAAABYgbAEAAACABQhbAAAAAGABwhYAAAAAWICwBQAAAAAWIGwBAAAAgAUIWwAAAABgAcIWAAAAAFiAsAUAAAAAFiBsAQAAAIAFXJxdAAAAAIqfkAmrnF0CYDlGtgAAAADAAoQtAAAAALAAYQsAAAAALEDYAgAAAAALELYAAAAAwAKELQAAAACwAGELAAAAACxA2AIAAAAACxC2AAAAAMAChC0AAAAAsABhCwAAAAAsQNgCAAAAAAsQtgAAAADAAoQtAAAAALAAYQsAAAAALEDYAgAAAAALELYAAAAAwAKELQAAAACwAGELAAAAACxA2AIAAAAACxC2AAAAAMAChC0AAAAAsABhCwAAAAAsQNgCAAAAAAsQtgAAAADAAoQtAAAAALAAYQsAAAAALEDYAgAAAAALuDi7AAAAABRdIRNWObsEoNBy6sjWli1b1KVLFwUGBspms+nTTz+1r7t69arGjx+vBg0aqEyZMgoMDNSAAQN08uRJh32EhITIZrM5fGbMmOHQZ+/evWrVqpVKlSqloKAgzZw583acHgAAAIBizKlh69KlS2rUqJHefPPNHOt+++037d69W5MmTdLu3bu1YsUKHTp0SH/7299y9J0+fbqSkpLsn1GjRtnXpaamqkOHDgoODtauXbs0a9YsTZ06Ve+8846l5wYAAACgeHPqbYSdOnVSp06dcl3n7e2tuLg4h7Y33nhDd999txITE1WlShV7u6enp/z9/XPdT2xsrK5cuaKFCxfKzc1N9erVU0JCgmbPnq1hw4YV3MkAAAAAwJ8UqQkyLly4IJvNJh8fH4f2GTNmqEKFCmrSpIlmzZqljIwM+7r4+Hi1bt1abm5u9raIiAgdOnRI586dy/U46enpSk1NdfgAAAAAQF4UmQkyLl++rPHjx6tPnz7y8vKyt48ePVpNmzZV+fLltW3bNk2cOFFJSUmaPXu2JCk5OVlVq1Z12Jefn599Xbly5XIcKyYmRtOmTbPwbAAAAADc6YpE2Lp69aoeeeQRGWP01ltvOawbO3as/eeGDRvKzc1Nw4cPV0xMjNzd3fN1vIkTJzrsNzU1VUFBQfkrHgAAAECxVOjDVnbQ+vnnn7VhwwaHUa3chIaGKiMjQ8ePH1etWrXk7++vlJQUhz7Zy9d6zsvd3T3fQQ0AAAAApEL+zFZ20Dp8+LDWrVunChUq3HCbhIQElShRQr6+vpKksLAwbdmyRVevXrX3iYuLU61atXK9hRAAAAAACoJTR7bS0tJ05MgR+/KxY8eUkJCg8uXLKyAgQA899JB2796tlStXKjMzU8nJyZKk8uXLy83NTfHx8dqxY4fatWsnT09PxcfHa8yYMerfv789SPXt21fTpk3TkCFDNH78eO3bt09z587Vq6++6pRzBgAAAFA82IwxxlkH37Rpk9q1a5ejPSoqSlOnTs0xsUW2jRs3qm3bttq9e7eeeOIJ/fDDD0pPT1fVqlX16KOPauzYsQ63Ae7du1fR0dH69ttvVbFiRY0aNUrjx4+/6TpTU1Pl7e2tCxcu3PA2RgAAgOIkZMIqZ5eA/+/4jEhnl1As5CUbODVsFRWELQAAgNwRtgoPwtbtkZdsUKif2QIAAACAooqwBQAAAAAWIGwBAAAAgAUIWwAAAABgAcIWAAAAAFiAsAUAAAAAFiBsAQAAAIAFCFsAAAAAYAHCFgAAAABYgLAFAAAAABYgbAEAAACABQhbAAAAAGABwhYAAAAAWCBfYeunn34q6DoAAAAA4I6Sr7B11113qV27dvrggw90+fLlgq4JAAAAAIq8fIWt3bt3q2HDhho7dqz8/f01fPhwffPNNwVdGwAAAAAUWfkKW40bN9bcuXN18uRJLVy4UElJSWrZsqXq16+v2bNn6/Tp0wVdJwAAAAAUKbc0QYaLi4t69Oih5cuX66WXXtKRI0c0btw4BQUFacCAAUpKSiqoOgEAAACgSLmlsLVz50498cQTCggI0OzZszVu3DgdPXpUcXFxOnnypLp27VpQdQIAAABAkeKSn41mz56tRYsW6dChQ+rcubPef/99de7cWSVK/JHdqlatqsWLFyskJKQgawUAAACAIiNfYeutt97S4MGDNXDgQAUEBOTax9fXVwsWLLil4gAAAACgqMpX2Dp8+PAN+7i5uSkqKio/uwcAAACAIi9fz2wtWrRIy5cvz9G+fPlyvffee7dcFAAAAAAUdfkKWzExMapYsWKOdl9fX7344ou3XBQAAAAAFHX5CluJiYmqWrVqjvbg4GAlJibeclEAAAAAUNTlK2z5+vpq7969Odq/++47VahQ4ZaLAgAAAICiLl9hq0+fPho9erQ2btyozMxMZWZmasOGDXryySfVu3fvgq4RAAAAAIqcfM1G+Pzzz+v48eNq3769XFz+2EVWVpYGDBjAM1sAAAAAoHyGLTc3Ny1btkzPP/+8vvvuO5UuXVoNGjRQcHBwQdcHAAAAAEVSvsJWtpo1a6pmzZoFVQsAAAAA3DHyFbYyMzO1ePFirV+/XqdOnVJWVpbD+g0bNhRIcQAAAABQVOUrbD355JNavHixIiMjVb9+fdlstoKuCwAAAACKtHyFraVLl+qjjz5S586dC7oeAAAAALgj5Gvqdzc3N911110FXQsAAAAA3DHyFbaefvppzZ07V8aYgq4HAAAAAO4I+bqN8Ouvv9bGjRu1evVq1atXT66urg7rV6xYUSDFAQAA4PYImbDK2SUAd5x8hS0fHx917969oGsBAAAAgDtGvsLWokWLCroOAAAAALij5OuZLUnKyMjQunXr9Pbbb+vixYuSpJMnTyotLa3AigMAAACAoipfI1s///yzOnbsqMTERKWnp+uBBx6Qp6enXnrpJaWnp2v+/PkFXScAAAAAFCn5Gtl68skn1bx5c507d06lS5e2t3fv3l3r168vsOIAAAAAoKjK18jWV199pW3btsnNzc2hPSQkRP/9738LpDAAAAAAKMryNbKVlZWlzMzMHO0nTpyQp6fnLRcFAAAAAEVdvsJWhw4dNGfOHPuyzWZTWlqapkyZos6dOxdUbQAAAABQZOXrNsJXXnlFERERqlu3ri5fvqy+ffvq8OHDqlixoj788MOCrhEAAAAAipx8ha3KlSvru+++09KlS7V3716l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9HvrrbdUvXp1ubm5qVatWvr3v//tsN5ms+ntt9/Wgw8+KA8PD9WpU0fx8fE6cuSI2rZtqzJlyujee+/V0aNH7dscPXpUXbt2lZ+fn8qWLasWLVpo3bp11zyXwYMH68EHH3Rou3r1qnx9fbVgwYJct/n888/1yCOPaMiQIbrrrrtUr1499enTR//85z8d+i1cuFD16tWTu7u7AgICNHLkSPu6xMREde3aVWXLlpWXl5ceeeQRpaSk2NdPnTpVjRs31r/+9S9VrVpVpUqVkiSdP39eQ4cOVaVKleTl5aX7779f33333TXPDwBQMAhbAIDb4qOPPlLt2rVVq1Yt9e/fXwsXLpQxJke/Z599Vq+88op27twpFxcXDR482L7uk08+0ZNPPqmnn35a+/bt0/DhwzVo0CBt3LjRYR/PP/+8BgwYoISEBNWuXVt9+/bV8OHDNXHiRO3cuVPGGIcQk5aWps6dO2v9+vXas2ePOnbsqC5duigxMTHXcxk6dKjWrFmjpKQke9vKlSv122+/qVevXrlu4+/vr+3bt+vnn3++5nf01ltvKTo6WsOGDdP333+vzz//XHfddZckKSsrS127dtXZs2e1efNmxcXF6aeffspxvCNHjuh///d/tWLFCiUkJEiSHn74YZ06dUqrV6/Wrl271LRpU7Vv315nz569Zi0AgAJgAAC4De69914zZ84cY4wxV69eNRUrVjQbN260r9+4caORZNatW2dvW7VqlZFkfv/9d/s+HnvsMYf9Pvzww6Zz5872ZUnmueeesy/Hx8cbSWbBggX2tg8//NCUKlXquvXWq1fPvP766/bl4OBg8+qrr9qX69ata1566SX7cpcuXczAgQOvub+TJ0+ae+65x0gyNWvWNFFRUWbZsmUmMzPT3icwMNA8++yzuW6/du1aU7JkSZOYmGhv279/v5FkvvnmG2OMMVOmTDGurq7m1KlT9j5fffWV8fLyMpcvX3bYX/Xq1c3bb7993e8AAHBrGNkCAFju0KFD+uabb9SnTx9JkouLi3r16pXrLXcNGza0/xwQECBJOnXqlCTp4MGDuu+++xz633fffTp48OA19+Hn5ydJatCggUPb5cuXlZqaKumPka1x48apTp068vHxUdmyZXXw4MFrjmxJf4xuLVq0SJKUkpKi1atXO4zC/VVAQIDi4+P1/fff68knn1RGRoaioqLUsWNHZWVl6dSpUzp58qTat2+f6/YHDx5UUFCQgoKC7G1169aVj4+Pw/kHBwerUqVK9uXvvvtOaWlpqlChgsqWLWv/HDt2zOFWSgBAwWOCDACA5RYsWKCMjAyHCTGMMXJ3d9cbb7whb29ve7urq6v9Z5vNJumPW+jyIrd9XG+/48aNU1xcnF5++WXdddddKl26tB566CFduXLlmscYMGCAJkyYoPj4eG3btk1Vq1ZVq1atblhb/fr1Vb9+fT3xxBN6/PHH1apVK23evFnNmzfP0zleS5kyZRyW09LSFBAQoE2bNuXo6+PjUyDHBADkjrAFALBURkaG3n//fb3yyivq0KGDw7pu3brpww8/1OOPP35T+6pTp462bt2qqKgoe9vWrVtVt27dW6px69atGjhwoLp37y7pj4By/Pjx625ToUIFdevWTYsWLVJ8fLwGDRqU5+Nm133p0iV5enoqJCRE69evV7t27XL0rVOnjn755Rf98ssv9tGtAwcO6Pz589c9/6ZNmyo5OVkuLi4KCQnJc40AgPwjbAEALLVy5UqdO3dOQ4YMcRjBkqSePXtqwYIFNx22nnnmGT3yyCNq0qSJwsPD9cUXX2jFihXXnTnwZtSoUUMrVqxQly5dZLPZNGnSpJsaTRs6dKgefPBBZWZmOgTA3IwYMUKBgYG6//77VblyZSUlJemFF15QpUqVFBYWJumP2QQff/xx+fr6qlOnTrp48aK2bt2qUaNGKTw8XA0aNFC/fv00Z84cZWRk6IknnlCbNm2uOyoWHh6usLAwdevWTTNnzlTNmjV18uRJrVq1St27dy+wETUAQE48swUAsNSCBQsUHh6eI2hJf4StnTt3au/evTe1r27dumnu3Ll6+eWXVa9ePb399ttatGiR2rZte0s1zp49W+XKldO9996rLl26KCIiQk2bNr3hduHh4QoICFBERMQN3xkWHh6u7du36+GHH1bNmjXVs2dPlSpVSuvXr1eFChUkSVFRUZozZ47mzZunevXq6cEHH9Thw4cl/XHr42effaZy5cqpdevWCg8PV7Vq1bRs2bLrHtdms+nLL79U69atNWjQINWsWVO9e/fWzz//bH+eDQBgDZsxucy7CwAAbigtLU3/8z//o0WLFqlHjx7OLgcAUMhwGyEAAHmUlZWlX3/9Va+88op8fHz0t7/9zdklAQAKIcIWAAB5lJiYqKpVq6py5cpavHixXFz4zykAICduIwQAAAAACzBBBgAAAABYgLAFAAAAABYgbAEAAACABQhbAAAAAGABwhYAAAAAWICwBQAAAAAWIGwBAAAAgAUIWwAAAABggf8HA5MClbVf2rkAAAAASUVORK5CYII=",
|
11043 |
+
"text/plain": [
|
11044 |
+
"<Figure size 1000x600 with 1 Axes>"
|
11045 |
+
]
|
11046 |
+
},
|
11047 |
+
"metadata": {},
|
11048 |
+
"output_type": "display_data"
|
11049 |
+
}
|
11050 |
+
],
|
11051 |
+
"source": [
|
11052 |
+
"iso_forest = IsolationForest(contamination=0.05, random_state=42) # Set contamination rate as needed\n",
|
11053 |
+
"iso_forest.fit(df[sensor_columns])\n",
|
11054 |
+
"\n",
|
11055 |
+
"anomaly_scores = iso_forest.decision_function(df[sensor_columns])\n",
|
11056 |
+
"df['anomaly_score'] = anomaly_scores\n",
|
11057 |
+
"df['anomaly'] = iso_forest.predict(df[sensor_columns])\n",
|
11058 |
+
"\n",
|
11059 |
+
"# Anomaly flagging: -1 for anomalies, 1 for normal points\n",
|
11060 |
+
"df['anomaly'] = df['anomaly'].map({1: 0, -1: 1})\n",
|
11061 |
+
"\n",
|
11062 |
+
"\n",
|
11063 |
+
"# Calculate feature importance based on the change in anomaly scores when each sensor is removed\n",
|
11064 |
+
"sensor_importance = {}\n",
|
11065 |
+
"for sensor in sensor_columns:\n",
|
11066 |
+
" df_temp = df[sensor_columns].copy()\n",
|
11067 |
+
" df_temp[sensor] = 0 # Remove the influence of this sensor\n",
|
11068 |
+
" temp_scores = iso_forest.decision_function(df_temp)\n",
|
11069 |
+
" importance = np.mean(np.abs(anomaly_scores - temp_scores))\n",
|
11070 |
+
" sensor_importance[sensor] = importance\n",
|
11071 |
+
"\n",
|
11072 |
+
"# Sort sensors by importance\n",
|
11073 |
+
"sorted_sensors = sorted(sensor_importance.items(), key=lambda x: x[1], reverse=True)\n",
|
11074 |
+
"\n",
|
11075 |
+
"print(\"Sensors contributing to anomalies in descending order of importance:\")\n",
|
11076 |
+
"for sensor, importance in sorted_sensors:\n",
|
11077 |
+
" print(f\"{sensor}: {importance:.4f}\")\n",
|
11078 |
+
"\n",
|
11079 |
+
"# Optional: Visualize anomalies\n",
|
11080 |
+
"import matplotlib.pyplot as plt\n",
|
11081 |
+
"\n",
|
11082 |
+
"plt.figure(figsize=(10, 6))\n",
|
11083 |
+
"plt.hist(anomaly_scores, bins=50)\n",
|
11084 |
+
"plt.title('Anomaly Scores Distribution')\n",
|
11085 |
+
"plt.xlabel('Anomaly Score')\n",
|
11086 |
+
"plt.ylabel('Frequency')\n",
|
11087 |
+
"plt.show()"
|
11088 |
+
]
|
11089 |
+
},
|
11090 |
+
{
|
11091 |
+
"cell_type": "code",
|
11092 |
+
"execution_count": 19,
|
11093 |
+
"metadata": {},
|
11094 |
+
"outputs": [
|
11095 |
+
{
|
11096 |
+
"data": {
|
11097 |
+
"text/plain": [
|
11098 |
+
"['AnomalyDetection.joblib']"
|
11099 |
+
]
|
11100 |
+
},
|
11101 |
+
"execution_count": 19,
|
11102 |
+
"metadata": {},
|
11103 |
+
"output_type": "execute_result"
|
11104 |
+
}
|
11105 |
+
],
|
11106 |
+
"source": [
|
11107 |
+
"import joblib \n",
|
11108 |
+
"joblib.dump(iso_forest, 'AnomalyDetection.joblib')"
|
11109 |
+
]
|
11110 |
+
},
|
11111 |
+
{
|
11112 |
+
"cell_type": "markdown",
|
11113 |
+
"metadata": {},
|
11114 |
+
"source": [
|
11115 |
+
"# Thank you!"
|
11116 |
+
]
|
11117 |
}
|
11118 |
],
|
11119 |
"metadata": {
|