Repository navigation
Expand file tree
/
Copy pathEngineFailureKNN.cpp
More file actions
342 lines (272 loc) · 13.5 KB
/
Copy pathEngineFailureKNN.cpp
File metadata and controls
342 lines (272 loc) · 13.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
#include <algorithm>
#include <random>
#include <chrono>
#include <map>
// User Configurable Parameters
const std::string DATA_FILE = "data/engine_failure_dataset.csv";
const float Train_Split = 0.7; // value from 0 to 1, 0.8 means 80% of your data will be used for training
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); // seed that shuffles your data
const int k = 5; // The k value for the KNN algorithm
// Define a struct for our data
struct Engine_Data {
double Temperature;
double RPM;
double Fuel_Efficiency;
double Vibration_X;
double Vibration_Y;
double Vibration_Z;
double Torque;
double Power_Output;
int Fault_Condition;
double Distance; // This is used when we calculate the distance between our test point and the engine data point
};
// This is our vector of data that we will be using through the whole script
std::vector<Engine_Data> Engine_Data_Vector;
// Read the CSV file and return 0/1 depending on if it was a success or not
static bool readCSV() {
std::ifstream file(DATA_FILE);
if (!file.is_open()) {
std::cerr << "Failed to open file: " << DATA_FILE << std::endl;
return 0;
}
std::string line;
// Skip the header line
std::getline(file, line);
while (std::getline(file, line)) {
std::stringstream ss(line);
std::string Temperature_str, RPM_str, Fuel_Efficiency_str, Vibration_X_str, Vibration_Y_str, Vibration_Z_str, Torque_str, Power_Output_str, Fault_Condition_str, Operational_Mode_str;
std::getline(ss, Temperature_str, ',');
std::getline(ss, RPM_str, ',');
std::getline(ss, Fuel_Efficiency_str, ',');
std::getline(ss, Vibration_X_str, ',');
std::getline(ss, Vibration_Y_str, ',');
std::getline(ss, Vibration_Z_str, ',');
std::getline(ss, Torque_str, ',');
std::getline(ss, Power_Output_str, ',');
std::getline(ss, Fault_Condition_str, ',');
std::getline(ss, Operational_Mode_str, ',');
// Convert data types
double Temperature = std::stod(Temperature_str);
double RPM = std::stod(RPM_str);
double Fuel_Efficiency = std::stod(Fuel_Efficiency_str);
double Vibration_X = std::stod(Vibration_X_str);
double Vibration_Y = std::stod(Vibration_Y_str);
double Vibration_Z = std::stod(Vibration_Z_str);
double Torque = std::stod(Torque_str);
double Power_Output = std::stod(Power_Output_str);
int Fault_Condition = std::stoi(Fault_Condition_str);
double Distance = 0;
Engine_Data_Vector.push_back({Temperature, RPM, Fuel_Efficiency, Vibration_X, Vibration_Y, Vibration_Z, Torque, Power_Output, Fault_Condition, Distance});
}
file.close();
return 1; // Return 1 to mean that it was all successful
}
// Calculate the euclidean distance between 2 points that are of type Engine_Data
static double euclidean_distance(const Engine_Data& point1, const Engine_Data& point2) {
double Temperature_Difference = std::pow(point1.Temperature - point2.Temperature, 2);
double RPM_Difference = std::pow(point1.RPM - point2.RPM, 2);
double Fuel_Efficiency_Difference = std::pow(point1.Fuel_Efficiency - point2.Fuel_Efficiency, 2);
double Vibration_X_Difference = std::pow(point1.Vibration_X - point2.Vibration_X, 2);
double Vibration_Y_Difference = std::pow(point1.Vibration_Y - point2.Vibration_Y, 2);
double Vibration_Z_Difference = std::pow(point1.Vibration_Z - point2.Vibration_Z, 2);
double Torque_Difference = std::pow(point1.Torque - point2.Torque, 2);
double Power_Output_Difference = std::pow(point1.Power_Output - point2.Power_Output, 2);
double Total_Distance = std::sqrt(Temperature_Difference + RPM_Difference + Fuel_Efficiency_Difference + Vibration_X_Difference + Vibration_Y_Difference + Vibration_Z_Difference + Torque_Difference + Power_Output_Difference);
return Total_Distance;
}
// Given some parameters, return the k closest neighbours
static std::vector<Engine_Data> find_neighbours(const int& Train_Split_Number, const Engine_Data& instance, const int& k) {
// We will create a vector of distances structs and use that to store what index is how far away from our testing point
struct Distances {
double Distance;
int Index;
};
std::vector<Distances> Distances_Vector;
for (int i = 0; i < Train_Split_Number; i++) {
double dist = euclidean_distance(Engine_Data_Vector[i], instance);
Engine_Data_Vector[i].Distance = dist; // Set the distance for each data point
Distances_Vector.push_back({ dist, i });
}
// Lambda function to sort our distances vector
auto compareByDistance = [](Distances Distance1, Distances Distance2) {
return Distance1.Distance < Distance2.Distance;
};
std::sort(Distances_Vector.begin(), Distances_Vector.end(), compareByDistance);
// This will be our k Engine_Data values
std::vector<Engine_Data> result;
for (int i = 0; i < k; i++) {
result.push_back({ Engine_Data_Vector[Distances_Vector[i].Index] });
}
return result;
}
// Function to sort the map according
// to value in a (key-value) pairs
static int get_most_frequent(std::map<std::string, double>& M) {
// Declare vector of pairs
std::vector <std::pair<std::string, double> > A;
// Copy key-value pair from Map
// to vector of pairs
for (auto& it : M) {
A.push_back(it);
}
auto cmp = [](std::pair<std::string, double> a, std::pair<std::string, double> b) {
return a.second > b.second;
};
// Sort using comparator function
sort(A.begin(), A.end(), cmp);
auto firstElement = A.begin();
return std::stoi(firstElement->first);
}
// Given our neighbours, return which "Fault_Code" wins, but this takes into account distances, so the closest neighbours get a bigger influence
static int weighted_vote(const std::vector<Engine_Data>& neighbours) {
// Fault Code and its frequency
std::map<std::string, double> frequencies = { {"0",0}, {"1",0}, {"2",0}, {"3",0}};
for (int i = 0; i < neighbours.size(); i++) {
frequencies[std::to_string(neighbours[i].Fault_Condition)] += static_cast<double>(1) / (std::pow(neighbours[i].Distance, 2) + 1);
}
int winner = get_most_frequent(frequencies);
return winner;
}
// Since RPM is in thousands, and Fuel Efficiency is in the 10s, we need to normalise our data to make sure they are all within the same range
static void normalise_data() {
// This is bad code, I am aware
// Didnt set it to 0 since 0 will always be min, and then used index 0 and 1 to try avoid a division by 0 error, although it should never happen
struct min_data_struct {
double Temperature = Engine_Data_Vector[0].Temperature;
double RPM = Engine_Data_Vector[0].RPM;
double Fuel_Efficiency = Engine_Data_Vector[0].Fuel_Efficiency;
double Vibration_X = Engine_Data_Vector[0].Vibration_X;
double Vibration_Y = Engine_Data_Vector[0].Vibration_Y;
double Vibration_Z = Engine_Data_Vector[0].Vibration_Z;
double Torque = Engine_Data_Vector[0].Torque;
double Power_Output = Engine_Data_Vector[0].Power_Output;
};
min_data_struct min_data;
struct max_data_struct {
double Temperature = Engine_Data_Vector[1].Temperature;
double RPM = Engine_Data_Vector[1].RPM;
double Fuel_Efficiency = Engine_Data_Vector[1].Fuel_Efficiency;
double Vibration_X = Engine_Data_Vector[1].Vibration_X;
double Vibration_Y = Engine_Data_Vector[1].Vibration_Y;
double Vibration_Z = Engine_Data_Vector[1].Vibration_Z;
double Torque = Engine_Data_Vector[1].Torque;
double Power_Output = Engine_Data_Vector[1].Power_Output;
};
max_data_struct max_data;
for (const Engine_Data& element : Engine_Data_Vector) {
double current_Temperature = element.Temperature;
double current_RPM = element.RPM;
double current_Fuel_Efficiency = element.Fuel_Efficiency;
double current_Vibration_X = element.Vibration_X;
double current_Vibration_Y = element.Vibration_Y;
double current_Vibration_Z = element.Vibration_Z;
double current_Torque = element.Torque;
double current_Power_Output = element.Power_Output;
if (current_Temperature < min_data.Temperature) {
min_data.Temperature = current_Temperature;
}
else if (current_Temperature > max_data.Temperature) {
max_data.Temperature = current_Temperature;
}
if (current_RPM < min_data.RPM) {
min_data.RPM = current_RPM;
}
else if (current_RPM > max_data.RPM) {
max_data.RPM = current_RPM;
}
if (current_Fuel_Efficiency < min_data.Fuel_Efficiency) {
min_data.Fuel_Efficiency = current_Fuel_Efficiency;
}
else if (current_Fuel_Efficiency > max_data.Fuel_Efficiency) {
max_data.Fuel_Efficiency = current_Fuel_Efficiency;
}
if (current_Vibration_X < min_data.Vibration_X) {
min_data.Vibration_X = current_Vibration_X;
}
else if (current_Vibration_X > max_data.Vibration_X) {
max_data.Vibration_X = current_Vibration_X;
}
if (current_Vibration_Y < min_data.Vibration_Y) {
min_data.Vibration_Y = current_Vibration_Y;
}
else if (current_Vibration_Y > max_data.Vibration_Y) {
max_data.Vibration_Y = current_Vibration_Y;
}
if (current_Vibration_Z < min_data.Vibration_Z) {
min_data.Vibration_Z = current_Vibration_Z;
}
else if (current_Vibration_Z > max_data.Vibration_Z) {
max_data.Vibration_Z = current_Vibration_Z;
}
if (current_Torque < min_data.Torque) {
min_data.Torque = current_Torque;
}
else if (current_Torque > max_data.Torque) {
max_data.Torque = current_Torque;
}
if (current_Power_Output < min_data.Power_Output) {
min_data.Power_Output = current_Power_Output;
}
else if (current_Power_Output > max_data.Power_Output) {
max_data.Power_Output = current_Power_Output;
}
}
for (Engine_Data& element : Engine_Data_Vector) {
double current_Temperature = element.Temperature;
double current_RPM = element.RPM;
double current_Fuel_Efficiency = element.Fuel_Efficiency;
double current_Vibration_X = element.Vibration_X;
double current_Vibration_Y = element.Vibration_Y;
double current_Vibration_Z = element.Vibration_Z;
double current_Torque = element.Torque;
double current_Power_Output = element.Power_Output;
element.Temperature = static_cast<double>(current_Temperature - min_data.Temperature) / (max_data.Temperature - min_data.Temperature);
element.RPM = static_cast<double>(current_RPM - min_data.RPM) / (max_data.RPM - min_data.RPM);
element.Fuel_Efficiency = static_cast<double>(current_Fuel_Efficiency - min_data.Fuel_Efficiency) / (max_data.Fuel_Efficiency - min_data.Fuel_Efficiency);
element.Vibration_X = static_cast<double>(current_Vibration_X - min_data.Vibration_X) / (max_data.Vibration_X - min_data.Vibration_X);
element.Vibration_Y = static_cast<double>(current_Vibration_Y - min_data.Vibration_Y) / (max_data.Vibration_Y - min_data.Vibration_Y);
element.Vibration_Z = static_cast<double>(current_Vibration_Z - min_data.Vibration_Z) / (max_data.Vibration_Z - min_data.Vibration_Z);
element.Torque = static_cast<double>(current_Torque - min_data.Torque) / (max_data.Torque - min_data.Torque);
element.Power_Output = static_cast<double>(current_Power_Output - min_data.Power_Output) / (max_data.Power_Output - min_data.Power_Output);
}
}
int main()
{
// First load the CSV file
bool data = readCSV();
if (data) {
std::cout << "CSV file loaded successfully." << "\n";
std::cout << "Loaded " << Engine_Data_Vector.size() << " items." << std::endl;
}
else {
std::cout << "Error occured while reading CSV file." << "\n";
};
// Using the data we just loaded, shuffle the data and then calculate the number of elements that will be used for training
shuffle(Engine_Data_Vector.begin(), Engine_Data_Vector.end(), std::default_random_engine(seed));
if (0 <= Train_Split && Train_Split <= 1) {
// int should always round down so even if this returns a float, we should have no issues
const int Train_Split_Number = Train_Split * Engine_Data_Vector.size(); // Number of training data we need
std::cout << Train_Split_Number << " training data points." << std::endl;
normalise_data();
// Count how many predictions we get right
size_t success = 0;
for (int i = Train_Split_Number; i < Engine_Data_Vector.size(); i++) {
std::vector<Engine_Data> neighbours = find_neighbours(Train_Split_Number, Engine_Data_Vector[i], k);
int winner = weighted_vote(neighbours);
if (winner = Engine_Data_Vector[i].Fault_Condition) {
success++;
}
}
std::cout << "Success Rate: " << 100 * (static_cast<double>(success) / (Engine_Data_Vector.size() - Train_Split_Number)) << "%" << std::endl;
}
else {
std::cout << "The variable Train_Split is not between 0 and 1." << std::endl;
return 1;
}
return 0;
}