Skip to content

Commit a8af29b

Browse files
luizcarloscfgithub-actions[bot]Panquesito7
authored
feat: add k-nearest neighbors algorithm (#2416)
* feat: add k-nearest neighbors, class Knn * updating DIRECTORY.md * clang-format and clang-tidy fixes for 8dfacfd * fix: comments in k-nearest neighbors * test: add more tests for k-nearest-neighbors algorithm * fix: description of k-nearest neighbors algorithm * chore: apply suggestions from code review --------- Co-authored-by: github-actions[bot] <[email protected]> Co-authored-by: David Leal <[email protected]>
1 parent b89d1da commit a8af29b

File tree

2 files changed

+197
-0
lines changed

2 files changed

+197
-0
lines changed

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@
168168
## Machine Learning
169169
* [A Star Search](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/machine_learning/a_star_search.cpp)
170170
* [Adaline Learning](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/machine_learning/adaline_learning.cpp)
171+
* [K Nearest Neighbors](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/machine_learning/k_nearest_neighbors.cpp)
171172
* [Kohonen Som Topology](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/machine_learning/kohonen_som_topology.cpp)
172173
* [Kohonen Som Trace](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/machine_learning/kohonen_som_trace.cpp)
173174
* [Neural Network](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/machine_learning/neural_network.cpp)
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/**
2+
* @file
3+
* @brief Implementation of [K-Nearest Neighbors algorithm]
4+
* (https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm).
5+
* @author [Luiz Carlos Cosmi Filho](https://github.com/luizcarloscf)
6+
* @details K-nearest neighbors algorithm, also known as KNN or k-NN, is a
7+
* supervised learning classifier, which uses proximity to make classifications.
8+
* This implementantion uses the Euclidean Distance as distance metric to find
9+
* the K-nearest neighbors.
10+
*/
11+
12+
#include <algorithm> /// for std::transform and std::sort
13+
#include <cassert> /// for assert
14+
#include <cmath> /// for std::pow and std::sqrt
15+
#include <iostream> /// for std::cout
16+
#include <numeric> /// for std::accumulate
17+
#include <unordered_map> /// for std::unordered_map
18+
#include <vector> /// for std::vector
19+
20+
/**
21+
* @namespace machine_learning
22+
* @brief Machine learning algorithms
23+
*/
24+
namespace machine_learning {
25+
26+
/**
27+
* @namespace k_nearest_neighbors
28+
* @brief Functions for the [K-Nearest Neighbors algorithm]
29+
* (https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm) implementation
30+
*/
31+
namespace k_nearest_neighbors {
32+
33+
/**
34+
* @brief Compute the Euclidean distance between two vectors.
35+
*
36+
* @tparam T typename of the vector
37+
* @param a first unidimentional vector
38+
* @param b second unidimentional vector
39+
* @return double scalar representing the Euclidean distance between provided
40+
* vectors
41+
*/
42+
template <typename T>
43+
double euclidean_distance(const std::vector<T>& a, const std::vector<T>& b) {
44+
std::vector<double> aux;
45+
std::transform(a.begin(), a.end(), b.begin(), std::back_inserter(aux),
46+
[](T x1, T x2) { return std::pow((x1 - x2), 2); });
47+
aux.shrink_to_fit();
48+
return std::sqrt(std::accumulate(aux.begin(), aux.end(), 0.0));
49+
}
50+
51+
/**
52+
* @brief K-Nearest Neighbors (Knn) class using Euclidean distance as
53+
* distance metric.
54+
*/
55+
class Knn {
56+
private:
57+
std::vector<std::vector<double>> X_{}; ///< attributes vector
58+
std::vector<int> Y_{}; ///< labels vector
59+
60+
public:
61+
/**
62+
* @brief Construct a new Knn object.
63+
* @details Using lazy-learning approch, just holds in memory the dataset.
64+
* @param X attributes vector
65+
* @param Y labels vector
66+
*/
67+
explicit Knn(std::vector<std::vector<double>>& X, std::vector<int>& Y)
68+
: X_(X), Y_(Y){};
69+
70+
/**
71+
* Copy Constructor for class Knn.
72+
*
73+
* @param model instance of class to be copied
74+
*/
75+
Knn(const Knn& model) = default;
76+
77+
/**
78+
* Copy assignment operator for class Knn
79+
*/
80+
Knn& operator=(const Knn& model) = default;
81+
82+
/**
83+
* Move constructor for class Knn
84+
*/
85+
Knn(Knn&&) = default;
86+
87+
/**
88+
* Move assignment operator for class Knn
89+
*/
90+
Knn& operator=(Knn&&) = default;
91+
92+
/**
93+
* @brief Destroy the Knn object
94+
*/
95+
~Knn() = default;
96+
97+
/**
98+
* @brief Classify sample.
99+
* @param sample sample
100+
* @param k number of neighbors
101+
* @return int label of most frequent neighbors
102+
*/
103+
int predict(std::vector<double>& sample, int k) {
104+
std::vector<int> neighbors;
105+
std::vector<std::pair<double, int>> distances;
106+
for (size_t i = 0; i < this->X_.size(); ++i) {
107+
auto current = this->X_.at(i);
108+
auto label = this->Y_.at(i);
109+
auto distance = euclidean_distance(current, sample);
110+
distances.emplace_back(distance, label);
111+
}
112+
std::sort(distances.begin(), distances.end());
113+
for (int i = 0; i < k; i++) {
114+
auto label = distances.at(i).second;
115+
neighbors.push_back(label);
116+
}
117+
std::unordered_map<int, int> frequency;
118+
for (auto neighbor : neighbors) {
119+
++frequency[neighbor];
120+
}
121+
std::pair<int, int> predicted;
122+
predicted.first = -1;
123+
predicted.second = -1;
124+
for (auto& kv : frequency) {
125+
if (kv.second > predicted.second) {
126+
predicted.second = kv.second;
127+
predicted.first = kv.first;
128+
}
129+
}
130+
return predicted.first;
131+
}
132+
};
133+
} // namespace k_nearest_neighbors
134+
} // namespace machine_learning
135+
136+
/**
137+
* @brief Self-test implementations
138+
* @returns void
139+
*/
140+
static void test() {
141+
std::cout << "------- Test 1 -------" << std::endl;
142+
std::vector<std::vector<double>> X1 = {{0.0, 0.0}, {0.25, 0.25},
143+
{0.0, 0.5}, {0.5, 0.5},
144+
{1.0, 0.5}, {1.0, 1.0}};
145+
std::vector<int> Y1 = {1, 1, 1, 1, 2, 2};
146+
auto model1 = machine_learning::k_nearest_neighbors::Knn(X1, Y1);
147+
std::vector<double> sample1 = {1.2, 1.2};
148+
std::vector<double> sample2 = {0.1, 0.1};
149+
std::vector<double> sample3 = {0.1, 0.5};
150+
std::vector<double> sample4 = {1.0, 0.75};
151+
assert(model1.predict(sample1, 2) == 2);
152+
assert(model1.predict(sample2, 2) == 1);
153+
assert(model1.predict(sample3, 2) == 1);
154+
assert(model1.predict(sample4, 2) == 2);
155+
std::cout << "... Passed" << std::endl;
156+
std::cout << "------- Test 2 -------" << std::endl;
157+
std::vector<std::vector<double>> X2 = {
158+
{0.0, 0.0, 0.0}, {0.25, 0.25, 0.0}, {0.0, 0.5, 0.0}, {0.5, 0.5, 0.0},
159+
{1.0, 0.5, 0.0}, {1.0, 1.0, 0.0}, {1.0, 1.0, 1.0}, {1.5, 1.5, 1.0}};
160+
std::vector<int> Y2 = {1, 1, 1, 1, 2, 2, 3, 3};
161+
auto model2 = machine_learning::k_nearest_neighbors::Knn(X2, Y2);
162+
std::vector<double> sample5 = {1.2, 1.2, 0.0};
163+
std::vector<double> sample6 = {0.1, 0.1, 0.0};
164+
std::vector<double> sample7 = {0.1, 0.5, 0.0};
165+
std::vector<double> sample8 = {1.0, 0.75, 1.0};
166+
assert(model2.predict(sample5, 2) == 2);
167+
assert(model2.predict(sample6, 2) == 1);
168+
assert(model2.predict(sample7, 2) == 1);
169+
assert(model2.predict(sample8, 2) == 3);
170+
std::cout << "... Passed" << std::endl;
171+
std::cout << "------- Test 3 -------" << std::endl;
172+
std::vector<std::vector<double>> X3 = {{0.0}, {1.0}, {2.0}, {3.0},
173+
{4.0}, {5.0}, {6.0}, {7.0}};
174+
std::vector<int> Y3 = {1, 1, 1, 1, 2, 2, 2, 2};
175+
auto model3 = machine_learning::k_nearest_neighbors::Knn(X3, Y3);
176+
std::vector<double> sample9 = {0.5};
177+
std::vector<double> sample10 = {2.9};
178+
std::vector<double> sample11 = {5.5};
179+
std::vector<double> sample12 = {7.5};
180+
assert(model3.predict(sample9, 3) == 1);
181+
assert(model3.predict(sample10, 3) == 1);
182+
assert(model3.predict(sample11, 3) == 2);
183+
assert(model3.predict(sample12, 3) == 2);
184+
std::cout << "... Passed" << std::endl;
185+
}
186+
187+
/**
188+
* @brief Main function
189+
* @param argc commandline argument count (ignored)
190+
* @param argv commandline array of arguments (ignored)
191+
* @return int 0 on exit
192+
*/
193+
int main(int argc, char* argv[]) {
194+
test(); // run self-test implementations
195+
return 0;
196+
}

0 commit comments

Comments
 (0)