Skip to content

Commit 35b7074

Browse files
Re-enable five disabled algorithms and the perceptron (#15208)
Re-enable the four scikit-learn machine-learning examples and the neural-network perceptron that had been disabled (renamed to .broken.txt / .DISABLED), and modernize them so they import and run cleanly on current scikit-learn and pass the doctest CI: machine_learning/gaussian_naive_bayes.py machine_learning/random_forest_classifier.py - Replace the removed sklearn.metrics.plot_confusion_matrix with ConfusionMatrixDisplay.from_estimator (removed in scikit-learn 1.2). - Drop the artificial time.sleep() calls. machine_learning/gradient_boosting_regressor.py machine_learning/random_forest_regressor.py - Replace the removed load_boston dataset (removed in scikit-learn 1.2 for ethical reasons) with the bundled load_diabetes dataset so the examples run offline. - Avoid an unused-variable lint (RUF059). neural_network/perceptron.py - Use a dedicated seeded random.Random instance instead of the global random state, so training is reproducible and thread-safe under the parallel test runner. - Cap training at epoch_number epochs so it always terminates even on non-linearly-separable data (previously an unbounded while True). - Have training() and sort() return their results instead of printing, per the contribution guidelines, and update the doctests accordingly. Requested by @cclauss in #8029; perceptron follow-up to #15206.
1 parent 9619ee1 commit 35b7074

5 files changed

Lines changed: 93 additions & 83 deletions

File tree

machine_learning/gaussian_naive_bayes.py.broken.txt renamed to machine_learning/gaussian_naive_bayes.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
# Gaussian Naive Bayes Example
2-
import time
32

43
from matplotlib import pyplot as plt
54
from sklearn.datasets import load_iris
6-
from sklearn.metrics import accuracy_score, plot_confusion_matrix
5+
from sklearn.metrics import ConfusionMatrixDisplay, accuracy_score
76
from sklearn.model_selection import train_test_split
87
from sklearn.naive_bayes import GaussianNB
98

109

1110
def main():
12-
1311
"""
1412
Gaussian Naive Bayes Example using sklearn function.
1513
Iris type dataset is used to demonstrate algorithm.
1614
"""
17-
1815
# Load Iris dataset
1916
iris = load_iris()
2017

@@ -27,23 +24,21 @@ def main():
2724

2825
# Gaussian Naive Bayes
2926
nb_model = GaussianNB()
30-
time.sleep(2.9)
31-
model_fit = nb_model.fit(x_train, y_train)
32-
y_pred = model_fit.predict(x_test) # Predictions on the test set
27+
nb_model.fit(x_train, y_train)
28+
y_pred = nb_model.predict(x_test) # Predictions on the test set
3329

3430
# Display Confusion Matrix
35-
plot_confusion_matrix(
31+
ConfusionMatrixDisplay.from_estimator(
3632
nb_model,
3733
x_test,
3834
y_test,
3935
display_labels=iris["target_names"],
40-
cmap="Blues", # although, Greys_r has a better contrast...
36+
cmap="Blues",
4137
normalize="true",
4238
)
4339
plt.title("Normalized Confusion Matrix - IRIS Dataset")
4440
plt.show()
4541

46-
time.sleep(1.8)
4742
final_accuracy = 100 * accuracy_score(y_true=y_test, y_pred=y_pred)
4843
print(f"The overall accuracy of the model is: {round(final_accuracy, 2)}%")
4944

machine_learning/gradient_boosting_regressor.py.broken.txt renamed to machine_learning/gradient_boosting_regressor.py

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,37 @@
11
"""Implementation of GradientBoostingRegressor in sklearn using the
2-
boston dataset which is very popular for regression problem to
3-
predict house price.
2+
diabetes dataset, a popular regression problem used to predict
3+
disease progression one year after baseline.
4+
5+
Note: this example previously used the Boston house-price dataset,
6+
which was removed from scikit-learn (>=1.2) for ethical reasons.
7+
``load_diabetes`` is a drop-in bundled alternative that ships with
8+
scikit-learn, so the example runs offline.
49
"""
510

611
import matplotlib.pyplot as plt
712
import pandas as pd
8-
from sklearn.datasets import load_boston
13+
from sklearn.datasets import load_diabetes
914
from sklearn.ensemble import GradientBoostingRegressor
1015
from sklearn.metrics import mean_squared_error, r2_score
1116
from sklearn.model_selection import train_test_split
1217

1318

1419
def main():
15-
16-
# loading the dataset from the sklearn
17-
df = load_boston()
20+
# loading the dataset from sklearn
21+
df = load_diabetes()
1822
print(df.keys())
19-
# now let construct a data frame
20-
df_boston = pd.DataFrame(df.data, columns=df.feature_names)
21-
# let add the target to the dataframe
22-
df_boston["Price"] = df.target
23+
# now let's construct a data frame
24+
df_data = pd.DataFrame(df.data, columns=df.feature_names)
25+
# let's add the target to the dataframe
26+
df_data["Target"] = df.target
2327
# print the first five rows using the head function
24-
print(df_boston.head())
28+
print(df_data.head())
2529
# Summary statistics
26-
print(df_boston.describe().T)
30+
print(df_data.describe().T)
2731
# Feature selection
2832

29-
x = df_boston.iloc[:, :-1]
30-
y = df_boston.iloc[:, -1] # target variable
33+
x = df_data.iloc[:, :-1]
34+
y = df_data.iloc[:, -1] # target variable
3135
# split the data with 75% train and 25% test sets.
3236
x_train, x_test, y_train, y_test = train_test_split(
3337
x, y, random_state=0, test_size=0.25
@@ -43,7 +47,7 @@ def main():
4347
test_score = model.score(x_test, y_test).round(3)
4448
print("Training score of GradientBoosting is :", training_score)
4549
print("The test score of GradientBoosting is :", test_score)
46-
# Let us evaluation the model by finding the errors
50+
# Let us evaluate the model by finding the errors
4751
y_pred = model.predict(x_test)
4852

4953
# The mean squared error
@@ -52,7 +56,7 @@ def main():
5256
print(f"Test Variance score: {r2_score(y_test, y_pred):.2f}")
5357

5458
# So let's run the model against the test data
55-
fig, ax = plt.subplots()
59+
_fig, ax = plt.subplots()
5660
ax.scatter(y_test, y_pred, edgecolors=(0, 0, 0))
5761
ax.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], "k--", lw=4)
5862
ax.set_xlabel("Actual")

machine_learning/random_forest_classifier.py.broken.txt renamed to machine_learning/random_forest_classifier.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
# Random Forest Classifier Example
2+
23
from matplotlib import pyplot as plt
34
from sklearn.datasets import load_iris
45
from sklearn.ensemble import RandomForestClassifier
5-
from sklearn.metrics import plot_confusion_matrix
6+
from sklearn.metrics import ConfusionMatrixDisplay
67
from sklearn.model_selection import train_test_split
78

89

910
def main():
10-
1111
"""
1212
Random Forest Classifier Example using sklearn function.
1313
Iris type dataset is used to demonstrate algorithm.
1414
"""
15-
1615
# Load Iris dataset
1716
iris = load_iris()
1817

@@ -28,7 +27,7 @@ def main():
2827
rand_for.fit(x_train, y_train)
2928

3029
# Display Confusion Matrix of Classifier
31-
plot_confusion_matrix(
30+
ConfusionMatrixDisplay.from_estimator(
3231
rand_for,
3332
x_test,
3433
y_test,

machine_learning/random_forest_regressor.py.broken.txt renamed to machine_learning/random_forest_regressor.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
11
# Random Forest Regressor Example
2-
from sklearn.datasets import load_boston
2+
3+
from sklearn.datasets import load_diabetes
34
from sklearn.ensemble import RandomForestRegressor
45
from sklearn.metrics import mean_absolute_error, mean_squared_error
56
from sklearn.model_selection import train_test_split
67

78

89
def main():
9-
1010
"""
1111
Random Forest Regressor Example using sklearn function.
12-
Boston house price dataset is used to demonstrate the algorithm.
13-
"""
12+
The diabetes dataset is used to demonstrate the algorithm.
1413
15-
# Load Boston house price dataset
16-
boston = load_boston()
17-
print(boston.keys())
14+
Note: this example previously used the Boston house-price dataset,
15+
which was removed from scikit-learn (>=1.2) for ethical reasons.
16+
``load_diabetes`` is a drop-in bundled alternative that ships with
17+
scikit-learn, so the example runs offline.
18+
"""
19+
# Load the diabetes dataset
20+
diabetes = load_diabetes()
21+
print(diabetes.keys())
1822

1923
# Split dataset into train and test data
20-
x = boston["data"] # features
21-
y = boston["target"]
24+
x = diabetes["data"] # features
25+
y = diabetes["target"]
2226
x_train, x_test, y_train, y_test = train_test_split(
2327
x, y, test_size=0.3, random_state=1
2428
)
Lines changed: 52 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
"""
2-
Perceptron
3-
w = w + N * (d(k) - y) * x(k)
2+
Perceptron
3+
w = w + N * (d(k) - y) * x(k)
44
5-
Using perceptron network for oil analysis, with Measuring of 3 parameters
6-
that represent chemical characteristics we can classify the oil, in p1 or p2
7-
p1 = -1
8-
p2 = 1
5+
Using perceptron network for oil analysis, with Measuring of 3 parameters
6+
that represent chemical characteristics we can classify the oil, in p1 or p2
7+
p1 = -1
8+
p2 = 1
9+
10+
Reference: https://en.wikipedia.org/wiki/Perceptron
911
"""
12+
1013
import random
1114

1215

@@ -18,6 +21,7 @@ def __init__(
1821
learning_rate: float = 0.01,
1922
epoch_number: int = 1000,
2023
bias: float = -1,
24+
seed: int | None = 0,
2125
) -> None:
2226
"""
2327
Initializes a Perceptron network for oil analysis
@@ -26,6 +30,8 @@ def __init__(
2630
:param learning_rate: learning rate used in optimizing.
2731
:param epoch_number: number of epochs to train network on.
2832
:param bias: bias value for the network.
33+
:param seed: seed for the (internal) random number generator so that
34+
training is reproducible; pass ``None`` for non-deterministic weights.
2935
3036
>>> p = Perceptron([], (0, 1, 2))
3137
Traceback (most recent call last):
@@ -54,29 +60,36 @@ def __init__(
5460
self.number_sample = len(sample)
5561
self.col_sample = len(sample[0]) # number of columns in dataset
5662
self.weight: list = []
63+
# A dedicated RNG instance keeps training reproducible without touching
64+
# the global ``random`` state (which other code/tests may rely on).
65+
self._rng = random.Random(seed)
5766

58-
def training(self) -> None:
67+
def training(self) -> int:
5968
"""
60-
Trains perceptron for epochs <= given number of epochs
61-
:return: None
69+
Trains the perceptron until it stops misclassifying the training data
70+
or the maximum number of epochs (``epoch_number``) is reached, whichever
71+
comes first. The epoch cap guarantees termination even if the data is
72+
not linearly separable.
73+
74+
:return: the number of epochs the network was trained for.
75+
6276
>>> data = [[2.0149, 0.6192, 10.9263]]
6377
>>> targets = [-1]
64-
>>> perceptron = Perceptron(data,targets)
65-
>>> perceptron.training() # doctest: +ELLIPSIS
66-
('\\nEpoch:\\n', ...)
67-
...
78+
>>> perceptron = Perceptron(data, targets)
79+
>>> perceptron.training()
80+
5
6881
"""
6982
for sample in self.sample:
7083
sample.insert(0, self.bias)
7184

7285
for _ in range(self.col_sample):
73-
self.weight.append(random.random())
86+
self.weight.append(self._rng.random())
7487

7588
self.weight.insert(0, self.bias)
7689

7790
epoch_count = 0
7891

79-
while True:
92+
while epoch_count < self.epoch_number:
8093
has_misclassified = False
8194
for i in range(self.number_sample):
8295
u = 0
@@ -92,28 +105,28 @@ def training(self) -> None:
92105
* self.sample[i][j]
93106
)
94107
has_misclassified = True
95-
# print('Epoch: \n',epoch_count)
96108
epoch_count = epoch_count + 1
97-
# if you want control the epoch or just by error
109+
# stop early once every sample is classified correctly
98110
if not has_misclassified:
99-
print(("\nEpoch:\n", epoch_count))
100-
print("------------------------\n")
101-
# if epoch_count > self.epoch_number or not error:
102111
break
103112

104-
def sort(self, sample: list[float]) -> None:
113+
return epoch_count
114+
115+
def sort(self, sample: list[float]) -> int:
105116
"""
117+
Classifies a single observation as P1 (-1) or P2 (1). The network must
118+
be trained first.
119+
106120
:param sample: example row to classify as P1 or P2
107-
:return: None
121+
:return: -1 if the sample is classified as P1, otherwise 1
122+
108123
>>> data = [[2.0149, 0.6192, 10.9263]]
109124
>>> targets = [-1]
110-
>>> perceptron = Perceptron(data,targets)
111-
>>> perceptron.training() # doctest: +ELLIPSIS
112-
('\\nEpoch:\\n', ...)
113-
...
114-
>>> perceptron.sort([-0.6508, 0.1097, 4.0009]) # doctest: +ELLIPSIS
115-
('Sample: ', ...)
116-
classification: P...
125+
>>> perceptron = Perceptron(data, targets)
126+
>>> perceptron.training()
127+
5
128+
>>> perceptron.sort([2.0149, 0.6192, 10.9263])
129+
-1
117130
"""
118131
if len(self.sample) == 0:
119132
raise ValueError("Sample data can not be empty")
@@ -122,23 +135,16 @@ def sort(self, sample: list[float]) -> None:
122135
for i in range(self.col_sample + 1):
123136
u = u + self.weight[i] * sample[i]
124137

125-
y = self.sign(u)
126-
127-
if y == -1:
128-
print(("Sample: ", sample))
129-
print("classification: P1")
130-
else:
131-
print(("Sample: ", sample))
132-
print("classification: P2")
138+
return self.sign(u)
133139

134140
def sign(self, u: float) -> int:
135141
"""
136142
threshold function for classification
137143
:param u: input number
138-
:return: 1 if the input is greater than 0, otherwise -1
139-
>>> data = [[0],[-0.5],[0.5]]
140-
>>> targets = [1,-1,1]
141-
>>> perceptron = Perceptron(data,targets)
144+
:return: 1 if the input is greater than or equal to 0, otherwise -1
145+
>>> data = [[0], [-0.5], [0.5]]
146+
>>> targets = [1, -1, 1]
147+
>>> perceptron = Perceptron(data, targets)
142148
>>> perceptron.sign(0)
143149
1
144150
>>> perceptron.sign(-0.5)
@@ -224,8 +230,8 @@ def sign(self, u: float) -> int:
224230
network = Perceptron(
225231
sample=samples, target=target, learning_rate=0.01, epoch_number=1000, bias=-1
226232
)
227-
network.training()
228-
print("Finished training perceptron")
233+
epochs = network.training()
234+
print(f"Finished training perceptron in {epochs} epoch(s)")
229235
print("Enter values to predict or q to exit")
230236
while True:
231237
sample: list = []
@@ -235,4 +241,6 @@ def sign(self, u: float) -> int:
235241
break
236242
observation = float(user_input)
237243
sample.insert(i, observation)
238-
network.sort(sample)
244+
classification = network.sort(sample)
245+
label = "P1" if classification == -1 else "P2"
246+
print(f"Sample: {sample} classification: {label}")

0 commit comments

Comments
 (0)