Skip to content

Commit 4402903

Browse files
feat: add Perceptron algorithm in machine learning (#1000)
1 parent aa24550 commit 4402903

File tree

3 files changed

+235
-0
lines changed

3 files changed

+235
-0
lines changed

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@
209209
* [Linear Regression](https://github.com/TheAlgorithms/Rust/blob/master/src/machine_learning/linear_regression.rs)
210210
* [Logistic Regression](https://github.com/TheAlgorithms/Rust/blob/master/src/machine_learning/logistic_regression.rs)
211211
* [Naive Bayes](https://github.com/TheAlgorithms/Rust/blob/master/src/machine_learning/naive_bayes.rs)
212+
* [Perceptron](https://github.com/TheAlgorithms/Rust/blob/master/src/machine_learning/perceptron.rs)
212213
* [Principal Component Analysis](https://github.com/TheAlgorithms/Rust/blob/master/src/machine_learning/principal_component_analysis.rs)
213214
* Loss Function
214215
* [Average Margin Ranking Loss](https://github.com/TheAlgorithms/Rust/blob/master/src/machine_learning/loss_function/average_margin_ranking_loss.rs)

src/machine_learning/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ mod logistic_regression;
66
mod loss_function;
77
mod naive_bayes;
88
mod optimization;
9+
mod perceptron;
910
mod principal_component_analysis;
1011

1112
pub use self::cholesky::cholesky;
@@ -23,4 +24,6 @@ pub use self::loss_function::neg_log_likelihood;
2324
pub use self::naive_bayes::naive_bayes;
2425
pub use self::optimization::gradient_descent;
2526
pub use self::optimization::Adam;
27+
pub use self::perceptron::classify;
28+
pub use self::perceptron::perceptron;
2629
pub use self::principal_component_analysis::principal_component_analysis;

src/machine_learning/perceptron.rs

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
/// Returns the weights and bias after performing Perceptron algorithm on the input data points.
2+
/// The Perceptron is a binary classification algorithm that learns a linear separator.
3+
/// Labels should be either -1.0 or 1.0 for the two classes.
4+
pub fn perceptron(
5+
data_points: Vec<(Vec<f64>, f64)>,
6+
max_iterations: usize,
7+
learning_rate: f64,
8+
) -> Option<(Vec<f64>, f64)> {
9+
if data_points.is_empty() {
10+
return None;
11+
}
12+
13+
let num_features = data_points[0].0.len();
14+
if num_features == 0 {
15+
return None;
16+
}
17+
18+
let mut weights = vec![0.0; num_features];
19+
let mut bias = 0.0;
20+
21+
for _ in 0..max_iterations {
22+
let mut misclassified = 0;
23+
24+
for (features, label) in &data_points {
25+
let prediction = predict(&weights, bias, features);
26+
27+
if prediction != *label {
28+
misclassified += 1;
29+
30+
for (weight, feature) in weights.iter_mut().zip(features.iter()) {
31+
*weight += learning_rate * label * feature;
32+
}
33+
bias += learning_rate * label;
34+
}
35+
}
36+
37+
if misclassified == 0 {
38+
break;
39+
}
40+
}
41+
42+
Some((weights, bias))
43+
}
44+
45+
/// Make a prediction using the given weights and bias.
46+
fn predict(weights: &[f64], bias: f64, features: &[f64]) -> f64 {
47+
let sum = weights
48+
.iter()
49+
.zip(features.iter())
50+
.map(|(w, x)| w * x)
51+
.sum::<f64>()
52+
+ bias;
53+
54+
if sum >= 0.0 {
55+
1.0
56+
} else {
57+
-1.0
58+
}
59+
}
60+
61+
/// Classify a new data point using the learned weights and bias.
62+
pub fn classify(weights: &[f64], bias: f64, features: &[f64]) -> Option<f64> {
63+
if weights.is_empty() || features.is_empty() {
64+
return None;
65+
}
66+
67+
if weights.len() != features.len() {
68+
return None;
69+
}
70+
71+
Some(predict(weights, bias, features))
72+
}
73+
74+
#[cfg(test)]
75+
mod test {
76+
use super::*;
77+
78+
#[test]
79+
fn test_perceptron_linearly_separable() {
80+
let data = vec![
81+
(vec![1.0, 1.0], 1.0),
82+
(vec![2.0, 2.0], 1.0),
83+
(vec![3.0, 3.0], 1.0),
84+
(vec![-1.0, -1.0], -1.0),
85+
(vec![-2.0, -2.0], -1.0),
86+
(vec![-3.0, -3.0], -1.0),
87+
];
88+
89+
let result = perceptron(data, 100, 0.1);
90+
assert!(result.is_some());
91+
92+
let (weights, bias) = result.unwrap();
93+
94+
let prediction1 = classify(&weights, bias, &[2.5, 2.5]);
95+
assert_eq!(prediction1, Some(1.0));
96+
97+
let prediction2 = classify(&weights, bias, &[-2.5, -2.5]);
98+
assert_eq!(prediction2, Some(-1.0));
99+
}
100+
101+
#[test]
102+
fn test_perceptron_xor_like() {
103+
let data = vec![
104+
(vec![0.0, 0.0], -1.0),
105+
(vec![1.0, 1.0], 1.0),
106+
(vec![0.0, 1.0], -1.0),
107+
(vec![1.0, 0.0], -1.0),
108+
];
109+
110+
let result = perceptron(data, 100, 0.1);
111+
assert!(result.is_some());
112+
113+
let (weights, _bias) = result.unwrap();
114+
assert_eq!(weights.len(), 2);
115+
}
116+
117+
#[test]
118+
fn test_perceptron_single_feature() {
119+
let data = vec![
120+
(vec![1.0], 1.0),
121+
(vec![2.0], 1.0),
122+
(vec![3.0], 1.0),
123+
(vec![-1.0], -1.0),
124+
(vec![-2.0], -1.0),
125+
(vec![-3.0], -1.0),
126+
];
127+
128+
let result = perceptron(data, 100, 0.1);
129+
assert!(result.is_some());
130+
131+
let (weights, bias) = result.unwrap();
132+
assert_eq!(weights.len(), 1);
133+
134+
let prediction1 = classify(&weights, bias, &[5.0]);
135+
assert_eq!(prediction1, Some(1.0));
136+
137+
let prediction2 = classify(&weights, bias, &[-5.0]);
138+
assert_eq!(prediction2, Some(-1.0));
139+
}
140+
141+
#[test]
142+
fn test_perceptron_empty_data() {
143+
let result = perceptron(vec![], 100, 0.1);
144+
assert_eq!(result, None);
145+
}
146+
147+
#[test]
148+
fn test_perceptron_empty_features() {
149+
let data = vec![(vec![], 1.0), (vec![], -1.0)];
150+
let result = perceptron(data, 100, 0.1);
151+
assert_eq!(result, None);
152+
}
153+
154+
#[test]
155+
fn test_perceptron_zero_iterations() {
156+
let data = vec![(vec![1.0, 1.0], 1.0), (vec![-1.0, -1.0], -1.0)];
157+
158+
let result = perceptron(data, 0, 0.1);
159+
assert!(result.is_some());
160+
161+
let (weights, bias) = result.unwrap();
162+
assert_eq!(weights, vec![0.0, 0.0]);
163+
assert_eq!(bias, 0.0);
164+
}
165+
166+
#[test]
167+
fn test_classify_empty_weights() {
168+
let result = classify(&[], 0.0, &[1.0, 2.0]);
169+
assert_eq!(result, None);
170+
}
171+
172+
#[test]
173+
fn test_classify_empty_features() {
174+
let result = classify(&[1.0, 2.0], 0.0, &[]);
175+
assert_eq!(result, None);
176+
}
177+
178+
#[test]
179+
fn test_classify_mismatched_dimensions() {
180+
let result = classify(&[1.0, 2.0], 0.0, &[1.0]);
181+
assert_eq!(result, None);
182+
}
183+
184+
#[test]
185+
fn test_perceptron_different_learning_rates() {
186+
let data = vec![
187+
(vec![1.0, 1.0], 1.0),
188+
(vec![2.0, 2.0], 1.0),
189+
(vec![-1.0, -1.0], -1.0),
190+
(vec![-2.0, -2.0], -1.0),
191+
];
192+
193+
let result1 = perceptron(data.clone(), 100, 0.01);
194+
let result2 = perceptron(data, 100, 1.0);
195+
196+
assert!(result1.is_some());
197+
assert!(result2.is_some());
198+
199+
let (weights1, bias1) = result1.unwrap();
200+
let (weights2, bias2) = result2.unwrap();
201+
202+
let prediction1 = classify(&weights1, bias1, &[3.0, 3.0]);
203+
let prediction2 = classify(&weights2, bias2, &[3.0, 3.0]);
204+
205+
assert_eq!(prediction1, Some(1.0));
206+
assert_eq!(prediction2, Some(1.0));
207+
}
208+
209+
#[test]
210+
fn test_perceptron_with_bias() {
211+
let data = vec![
212+
(vec![1.0], 1.0),
213+
(vec![2.0], 1.0),
214+
(vec![10.0], 1.0),
215+
(vec![0.0], -1.0),
216+
(vec![-1.0], -1.0),
217+
(vec![-10.0], -1.0),
218+
];
219+
220+
let result = perceptron(data, 100, 0.1);
221+
assert!(result.is_some());
222+
223+
let (weights, bias) = result.unwrap();
224+
225+
let prediction_positive = classify(&weights, bias, &[5.0]);
226+
let prediction_negative = classify(&weights, bias, &[-5.0]);
227+
228+
assert_eq!(prediction_positive, Some(1.0));
229+
assert_eq!(prediction_negative, Some(-1.0));
230+
}
231+
}

0 commit comments

Comments
 (0)