Skip to content
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

updated pos_weight to be per class #8788

Closed
wants to merge 10 commits into from

Conversation

seermer
Copy link

@seermer seermer commented Jul 29, 2022

changed pos_weight as described to fix #8749

πŸ› οΈ PR Summary

Made with ❀️ by Ultralytics Actions

🌟 Summary

Improvements in class weight calculation for better loss computation in YOLOv5 training.

πŸ“Š Key Changes

  • Replaced labels_to_class_weights() with counts_to_class_weights() and counts_to_pos_weights(), offering a more granular approach.
  • Added count_samples() to count the number of samples for each class in the dataset.
  • Integrated new class weight calculations into train.py, changing how hyperparameters such as box, cls, and obj are scaled based on the dataset.
  • Updated ComputeLoss in utils/loss.py to use the new positional class weights directly.

🎯 Purpose & Impact

  • The PR aims to refine the way class weights and positional class weights are calculated, potentially improving the training stability and performance of the model.
  • The changes should provide a fairer training process across different classes, especially when there is class imbalance.
  • Users may expect more effective training out of the box, particularly for datasets with highly variable class representation.

These updates could lead to more accurate object detection models πŸš€, particularly when users are working with imbalanced datasets.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ‘‹ Hello @seermer, thank you for submitting a YOLOv5 πŸš€ PR! To allow your work to be integrated as seamlessly as possible, we advise you to:

  • βœ… Verify your PR is up-to-date with upstream/master. If your PR is behind upstream/master an automatic GitHub Actions merge may be attempted by writing /rebase in a new comment, or by running the following code, replacing 'feature' with the name of your local branch:
git remote add upstream https://github.com/ultralytics/yolov5.git
git fetch upstream
# git checkout feature  # <--- replace 'feature' with local branch name
git merge upstream/master
git push -u origin -f
  • βœ… Verify all Continuous Integration (CI) checks are passing.
  • βœ… Reduce changes to the absolute minimum required for your bug fix or feature addition. "It is not daily increase but daily decrease, hack away the unessential. The closer to the source, the less wastage there is." -Bruce Lee

@glenn-jocher
Copy link
Member

@seermer thanks for the PR! I reviewed it and your new function seems to do the same as our existing function here:

yolov5/utils/general.py

Lines 654 to 671 in 08c8c3e

def labels_to_class_weights(labels, nc=80):
# Get class weights (inverse frequency) from training labels
if labels[0] is None: # no labels loaded
return torch.Tensor()
labels = np.concatenate(labels, 0) # labels.shape = (866643, 5) for COCO
classes = labels[:, 0].astype(int) # labels = [class xywh]
weights = np.bincount(classes, minlength=nc) # occurrences per class
# Prepend gridpoint count (for uCE training)
# gpi = ((320 / 32 * np.array([1, 2, 4])) ** 2 * 3).sum() # gridpoints per image
# weights = np.hstack([gpi * len(labels) - weights.sum() * 9, weights * 9]) ** 0.5 # prepend gridpoints to start
weights[weights == 0] = 1 # replace empty bins with 1
weights = 1 / weights # number of targets per class
weights /= weights.sum() # normalize
return torch.from_numpy(weights).float()

Would it be better to modify or use the existing function instead of creating a similar one?

@seermer
Copy link
Author

seermer commented Aug 2, 2022

@seermer thanks for the PR! I reviewed it and your new function seems to do the same as our existing function here:

yolov5/utils/general.py

Lines 654 to 671 in 08c8c3e

def labels_to_class_weights(labels, nc=80):
# Get class weights (inverse frequency) from training labels
if labels[0] is None: # no labels loaded
return torch.Tensor()
labels = np.concatenate(labels, 0) # labels.shape = (866643, 5) for COCO
classes = labels[:, 0].astype(int) # labels = [class xywh]
weights = np.bincount(classes, minlength=nc) # occurrences per class
# Prepend gridpoint count (for uCE training)
# gpi = ((320 / 32 * np.array([1, 2, 4])) ** 2 * 3).sum() # gridpoints per image
# weights = np.hstack([gpi * len(labels) - weights.sum() * 9, weights * 9]) ** 0.5 # prepend gridpoints to start
weights[weights == 0] = 1 # replace empty bins with 1
weights = 1 / weights # number of targets per class
weights /= weights.sum() # normalize
return torch.from_numpy(weights).float()

Would it be better to modify or use the existing function instead of creating a similar one?

Hello sir, I think they do similar things but have different results. from what I understand, the existing one returns the normalized inverse frequency. for example, if we have 800 class A, and 200 class B, then it returns [0.2, 0.8].
My new function would return [0.625, 2.5].

Although it is true that mine is just a multiple of the existing one, it would still perform differently as weights.
I have considered initially using the existing function, but due to the normalization step of the existing function, I cannot come up with a good way to rescale the existing one into what I need outside the function.
would you mind providing some suggestions on modifying/merging functions? Thanks

@seermer
Copy link
Author

seermer commented Aug 2, 2022

@glenn-jocher Hello sir, I have just refactored the code so that the computation for counting samples will only perform once. However, the functions calculating pos_weight and class_weight are still separate since they do different things.

Another possibility to refactor is to simply put the pos_weight and class_weight outside the function, directly in train.py, but it looks messy if I do so. Do you think my current change works?

@glenn-jocher
Copy link
Member

@seermer looks better now! Have you tried training to see how this affects the results?

@seermer
Copy link
Author

seermer commented Aug 5, 2022

@seermer looks better now! Have you tried training to see how this affects the results?

sorry, I don't have sufficient resources to train on any decent dataset right now, I have tried a small number of epochs on a private dataset, and see very little difference (~0.3 map0.5).

@github-actions
Copy link
Contributor

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions YOLOv5 πŸš€ and Vision AI ⭐.

@github-actions github-actions bot added the Stale label Mar 22, 2023
@github-actions github-actions bot removed the Stale label Apr 10, 2023
@github-actions
Copy link
Contributor

github-actions bot commented Oct 3, 2023

πŸ‘‹ Hello there! We wanted to let you know that we've decided to close this pull request due to inactivity. We appreciate the effort you put into contributing to our project, but unfortunately, not all contributions are suitable or aligned with our product roadmap.

We hope you understand our decision, and please don't let it discourage you from contributing to open source projects in the future. We value all of our community members and their contributions, and we encourage you to keep exploring new projects and ways to get involved.

For additional resources and information, please see the links below:

Thank you for your contributions to YOLO πŸš€ and Vision AI ⭐

@github-actions github-actions bot added the Stale label Oct 3, 2023
@github-actions github-actions bot closed this Nov 3, 2023
@glenn-jocher
Copy link
Member

No worries, @seermer. Thanks for the update and for your efforts. Small datasets might not show the full potential of these changes. If you can, please monitor the issue for feedback from others who might be able to train on larger datasets. Your contribution is valuable, and we'll review it further. Keep up the good work! πŸš€

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

pos_weight should be a vector instead of a scalar
2 participants