-
-
Notifications
You must be signed in to change notification settings - Fork 26.2k
MNT Speed up of plot_face_recognition.py example #21725
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MNT Speed up of plot_face_recognition.py example #21725
Conversation
…ning GridSearchCV. Speed up is around 3x (plot_face_recognition.py running time reduced from 21s to 6s)
Hey Pramod, thanks for the PR. I appreciate the effort :) `Running with -1 by default is problematic because on machines with a large number of CPUs (e.g. 64 or more), spawning the workers can dominate with concurrent access to the hard disk just to start the python interpreters and import the modules. Furthermore it can also use too much memory and cause crashes. This is why we would rather use a small number of workers (e.g. 2 instead of -1) when we want to use parallelism in examples or tests in scikit-learn.` Therefore, we might either change the param to |
@pramodatre You could maybe try and alter the |
@sveneschlbeck Thank you for your note on Also, I will try to play with |
@pramodatre I would wait for a second reviewer but yes, I think that setting |
@sveneschlbeck Sounds good for the second reviewer input for |
@pramodatre Sounds great! All help is welcome. :) Here's a tip for you. If we make improvements or changes to the code, we always back it up by posting some kind of proof (proof that it did indeed increase speed in this case). This could be done by pasting in a screenshot of your execution showing both the execution time before/after and the output result (plots) before/after. This makes it easy to analyse whether the changes are helpful or not. In general, we want to decrease the execution time while keeping the results the same, meaning
|
@sveneschlbeck That's very helpful! Thank you for summarizing the goal for this effort :) |
Let's use Sometimes starting work processes and data communication overhead between processes do no make it possible to observe an actual speed improvement (depending the size of the data and the duration of the parallel tasks). |
@@ -108,7 +108,7 @@ | |||
"C": [1e3, 5e3, 1e4, 5e4, 1e5], | |||
"gamma": [0.0001, 0.0005, 0.001, 0.005, 0.01, 0.1], | |||
} | |||
clf = GridSearchCV(SVC(kernel="rbf", class_weight="balanced"), param_grid) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively trying RandomizedSearchCV
with n_iter=10
should work well here and be faster than an exhaustive grid search of the 5x5 combinations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition, I think that it could be good to add a StandardScaler
before applying the PCA
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The RandomizedSearch
looks like a good idea using the loguniform
to sample C
and gamma
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this idea! Yeah, totally makes sense! I will try this option and report performance differences I register.
Sounds good! Just curious of the choice of |
The CircleCI machine allocates us only 2 cores if I am not wrong. Here, it should be enough for the task that we want to do indeed. In practice, it would be a bit of a waste to take 40 cores of a big machine for this example :) In some way, this is more pedagogical to show a user that you should set the value to a meaningful value instead of just expanding to whatever is available. |
@glemaitre Thank you for the explanation! |
Here are the results of various experiments. In conclusion, Please look at the Scikit-learn example plot_face_recognition.py profiling.xlsx |
@pramodatre Thanks. Could you provide a markdown table with the results instead of an Excel spreadsheet? It would be easier to have a self-contained message in the PR. I push with the solution is as well a good thing because we will run the example and check the speed-up that we get on CircleCI compared to the documentation created for |
@glemaitre Thanks for the feedback! Yeah, did not realize that excel will hide all the details. Are screenshots of the evaluation tables an option? If not, for sure, I will create markdown. Please let me know. |
Both are fine but you can edit the markdown which could be handy
|
@glemaitre Yeah, good point! I will go ahead and create markdown tables then. Thanks! |
Here are expected precision, recall, and f-scores in the example docstring
Here are the results for average precision, recall, and f-score for five runs, i.e., each table presented below contains values that are averaged over five runs of the same experiment.
Note that the average precision, recall, and f-scores for the configuration |
…running time, was 21s and now it is around 6s
…ript using black now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very good! Thanks for reporting your experiment results with markdown tables in the comments of this PR.
While we are at it, can you please remove the table with the expected results (to avoid confusion if the values don't match the actual output).
And also: please replace lines like:
# #############################################################################
# Download the data, if not already on disk and load it as numpy arrays
lfw_people = fetch_lfw_people(min_faces_per_person=70, resize=0.4)
By:
# %%
# Download the data, if not already on disk and load it as numpy arrays
# %%
lfw_people = fetch_lfw_people(min_faces_per_person=70, resize=0.4)
This should split the example into rendered notebook cells that will render better with our modern version of sphinx-gallery. Feel free to also insert a line with:
# %%
at the top of the notebook before the from time import time
import statement so that all the code cells are easy to execute using the jupyter integration of VS Code or PyCharm for instance.
…n, added content for notebook rendering
By looking at the rendered page, the new cell-based layout is much nicer to read. Thanks! https://164173-843222-gh.circle-artifacts.com/0/doc/_changed.html I believe the last code cell is too big and it would be worth splitting it to put the eigenface gallery in its own cell. |
The confusion matrix could also be split in its own code cell. |
And actually the confusion matrix could better be displayed with this utility: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So usually we intent to use it as follow with sphinx-gallery
# %%
# Some markdown
print("we start directly the code without a new marker").
I made a couple of suggestions to correct it.
@@ -108,7 +108,7 @@ | |||
"C": [1e3, 5e3, 1e4, 5e4, 1e5], | |||
"gamma": [0.0001, 0.0005, 0.001, 0.005, 0.01, 0.1], | |||
} | |||
clf = GridSearchCV(SVC(kernel="rbf", class_weight="balanced"), param_grid) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition, I think that it could be good to add a StandardScaler
before applying the PCA
.
@@ -108,7 +108,7 @@ | |||
"C": [1e3, 5e3, 1e4, 5e4, 1e5], | |||
"gamma": [0.0001, 0.0005, 0.001, 0.005, 0.01, 0.1], | |||
} | |||
clf = GridSearchCV(SVC(kernel="rbf", class_weight="balanced"), param_grid) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The RandomizedSearch
looks like a good idea using the loguniform
to sample C
and gamma
.
# Download the data, if not already on disk and load it as numpy arrays | ||
|
||
# %% |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will need to keep the sphinx-gallery style that is slightly different from the vs code style
# %% |
# %% | ||
# Split into a training set and a test set using a stratified k fold | ||
|
||
# split into a training and testing set | ||
# %% |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# %% | |
# Split into a training set and a test set using a stratified k fold | |
# split into a training and testing set | |
# %% | |
# %% | |
# Split into a training set and a test and keep 25% of the data for testing. |
# Compute a PCA (eigenfaces) on the face dataset (treated as unlabeled | ||
# dataset): unsupervised feature extraction / dimensionality reduction | ||
# %% |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# %% |
# Train a SVM classification model | ||
|
||
# %% |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# %% |
# Quantitative evaluation of the model quality on the test set | ||
|
||
# %% |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# %% |
# Qualitative evaluation of the predictions using matplotlib | ||
# %% |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# %% |
param_grid = { | ||
"C": [1e3, 5e3, 1e4, 5e4, 1e5], | ||
"gamma": [0.0001, 0.0005, 0.001, 0.005, 0.01, 0.1], | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that it would make sense to change the following with distributions:
param_distribution = {
"C": loguniform(1e3, 1e5),
"gamma": loguniform(1e-4, 1e-1),
}
where loguniform
is imported as from sklearn.utils.fixes import loguniform
.
I checked locally and it provides the same results in average.
@ogrisel @glemaitre Do you have any suggestion for me to visualize this documentation locally? This way, I can check the documentation before I commit the changes. No worries if such a setup is not possible. I found that the CI provides this link once I push my commit. |
…Display, and accepted all sphinx-gallery related changes
@ogrisel @glemaitre Thank you for all the feedback, I have incorporated all your feedback with the last two commits. Please let me know if you want me to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking great thank!
Just a final comment suggestion below. Also could you please add a final comment at the end of the notebook that the face recognition problem would be much more efficiently addressed by training convolutional neural networks but this family of models is outside of the scope of the scikit-learn library. Interested readers should instead try to use pytorch or tensorflow.
@@ -124,10 +115,11 @@ | |||
print("done in %0.3fs" % (time() - t0)) | |||
|
|||
print(classification_report(y_test, y_pred, target_names=target_names)) | |||
print(confusion_matrix(y_test, y_pred, labels=range(n_classes))) | |||
ConfusionMatrixDisplay.from_estimator(clf, X_test_pca, y_test) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Could you please try with:
ConfusionMatrixDisplay.from_estimator(clf, X_test_pca, y_test) | |
ConfusionMatrixDisplay.from_estimator( | |
clf, X_test_pca, y_test, display_labels=target_names | |
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! This makes it easy to read the confusion matrix :)
# Display progress logs on stdout | ||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s") | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe those lines have no longer any effect:
# Display progress logs on stdout | |
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Removed these lines.
…lay, removed logging
…al nets are state-of-the-art for face recognition
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thanks @pramodatre
Reference Issues/PRs
References: #21598
What does this implement/fix? Explain your changes.
GridSearchCV
now leverages all the cores on the machine by specifyingn_jobs=-1
-- this is the most time consuming step in this example.This example runs in 6s now which used to take around 20s (3x speedup)
Before:
After: