socam: implement class balance weighting of loss#403
Conversation
|
@magrenimish does this look right? Would you be able to use this in your manuscript review experiments? We are running into the same thing in CAS... we probably need to try it. |
|
@fedorgrab how would we actually create this file? Is this already part of the extract metadata, or would we have to do a specific TileDB query to get this? |
|
when the cl_names_subset is expanded to include ancestors (that were not part of the training labels) during training, it looks like class_counts expects a count for these expanded classes too? |
|
Propagating the count is a very interesting concept |
Nexus currently doesn’t produce per-class cell counts. The easiest way would be, yes, I suppose, to count this via TileDB using the same filters that were used to create the extract. The extract metadata file does include those filters. |
|
Hmmmm, I had a thought today and ran it past gemini. The core of the thought is this: Gemini: By applying a standard class weight to your cross-entropy loss, you solved the problem of rare cell types being ignored. But you did not solve the problem of small datasets being ignored within those cell types. Here is exactly what is happening under the hood and how to fix it. The Mechanics of the "Dataset Vote" When your model calculates the loss for a batch, it applies that multiplier to every DC it sees. However: If Dataset A has 9,000 DCs and Dataset B has 100 DCs, Dataset A still provides 90x more examples to the model. During gradient descent, the model is updating its weights based on the sum (or average) of the loss. 98.9% of the gradients pushing the model to learn "what a DC looks like" are coming from Dataset A. The consequence: Your classifier isn't actually learning a universal representation of a Dendritic Cell. It is learning Dataset A's specific batch-effected version of a Dendritic Cell, alongside Dataset A's specific annotation biases. Dataset B's 100 DCs are treated as statistical noise and functionally ignored. |
|
For a given cell The Danger (and how to mitigate it): It's worth considering implementing two things (optional and mutually exclusive):
The latter is a bit harder to implement, but might be interesting. |
|
The EDIT: actually... if we manually set them to zero, they will never be accessed in the code since no cell is actually annotated that way. So it will not lead to a div-by-zero. I think the thing to do is accept a subset of the list from the user (just observed cells) and assume missing classes have zero counts. Allow zeros and test this is okay. |
|
TODO
|
|
Alright @magrenimish and @fedorgrab this is ready for another round of review. Since your last look, I've added optional propagation of class counts. The issue about weighting by |
Add class balancing weights to the cross_entropy loss. This is built into pytorch's cross_entropy already, so this is mostly plumbing to accept a
class_countsinput which is a pandas Series whose index is cell type IDs and whose values are cell counts across the whole training dataset. If theclass_countsinput is None (default), then there is no class balancing used (current behavior).This is a new input file the user would need to create from the training data manually.