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

Updates for MetaCAT #515

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open

Updates for MetaCAT #515

wants to merge 5 commits into from

Conversation

shubham-s-agarwal
Copy link
Collaborator

  • Addressing the multiple zero-division-error warnings per epoch while training
  • Accommodating the variations in category name and class name across NHS sites; this is done by introducing 2 new config parameters: category_names_map and class_names_map that store the possible variations

- Addressing the multiple zero-division-error warnings per epoch while training
- Accommodating the variations in category name and class name across NHS sites
@shubham-s-agarwal shubham-s-agarwal added the enhancement New feature or request label Jan 24, 2025
@shubham-s-agarwal shubham-s-agarwal self-assigned this Jan 24, 2025
Copy link
Collaborator

@mart-r mart-r left a comment

Choose a reason for hiding this comment

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

I think there's some naming issues there.

And (potentially) some (minor!) logic issues as well. After all, if the idea of this PR is to more elegantly catch user errors, then it makes sense to do so as much as we can.

Also, zero-division wise, does it make sense to set it to 1 if there's retrieved elements? I would think 0 would suit better, but am open to being persuaded otherwise.

@@ -27,8 +27,22 @@ class General(MixingConfig, BaseModel):
"""What category is this meta_cat model predicting/training.

NB! For these changes to take effect, the pipe would need to be recreated."""
category_names_map: List = []
Copy link
Collaborator

Choose a reason for hiding this comment

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

This not a mapping. It's a list. As such, the name doesn't make a lot of sense.
Perhaps this could be named alternative_category_names or something along those lines?

category_value2id: Dict = {}
"""Map from category values to ID, if empty it will be autocalculated during training"""
class_names_map: List[List] = [[]]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Again, not a map, but a list. At least in its current state.
Perhaps making it a Dict[str, str] could make sense? I.e a mapping from the presented class name to the expected one.
The example would then be:

{"Hypothetical (N/A)": "Hypothetical", "Not present (False)": "False", "Present (True)": "True"}

medcat/meta_cat.py Show resolved Hide resolved
category_name, " | ".join(list(data.keys()))))
category_matching = [cat for cat in category_name_options if cat in data.keys()]
if len(category_matching) > 0:
logger.warning("The category name provided in the config - '%s' is not present in the data. However, the corresponding name - '%s' from the category_name_mapping has been found. Updating the category name...",category_name,*category_matching)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd say this would be an info message since after this change it'll be expected behaviour.

category_name = g_config['category_name']
else:
raise Exception(
"The category name does not exist in this json file. You've provided '{}', while the possible options are: {}".format(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Perhaps mention the possibility of setting config.general.category_names_map for alternatives in here?

medcat/utils/meta_cat/data_utils.py Show resolved Hide resolved
if len(found_in) != 0:
class_name_matched = [label for label in found_in if label in category_values][0]
updated_category_value2id[class_name_matched] = category_value2id[_class]
logger.warning("Class name '%s' does not exist in the data; however a variation of it '%s' is present; updating it...",_class,class_name_matched)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Again, I feel like at this point, it'd be expected behaviour and better suited as an info message.

# Else throw an exception since the labels don't match
else:
raise Exception(
f"The classes set in the config are not the same as the one found in the data. The classes present in the config vs the ones found in the data - {set(category_value2id.keys())}, {category_values}")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Perhaps also mention the possibility of setting config.general.category_names_map for alternatives?

@@ -329,12 +329,12 @@ def initialize_model(classifier, data_, batch_size_, lr_, epochs=4):
print_report(epoch, running_loss_test, all_logits_test, y=y_test, name='Test')

_report = classification_report(y_test, np.argmax(np.concatenate(all_logits_test, axis=0), axis=1),
output_dict=True)
output_dict=True,zero_division=1)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does it make sense to have it return 1? I.e if there are no retrieved elements, then precision is undetermined. But if we set it to 1 and don't show the warning, the user might think everything is working great. But in reality, nothing was extracted.
Perhaps setting it to 0 would make more sense?

Though in an ideal world, I'd love to still show a warning to the user. But maybe limit it to once per 10 minutes or something. But I don't think they have API for something like that. And I don't think it'd make sense to spend the time implementing it, either.

if not winner_report or _report[config.train['metric']['base']][config.train['metric']['score']] > \
winner_report['report'][config.train['metric']['base']][config.train['metric']['score']]:

report = classification_report(y_test, np.argmax(np.concatenate(all_logits_test, axis=0), axis=1),
output_dict=True)
output_dict=True,zero_division=1)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same question here. Does it make sense to show 1 in case of no retrieved elements? Or would 0 be more appropriate?

@shubham-s-agarwal
Copy link
Collaborator Author

I've addressed all the changes!

Copy link
Collaborator

@mart-r mart-r left a comment

Choose a reason for hiding this comment

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

Looks good to me.

Though perhaps the alternative_class_names config option's doc strings could do with a small change to indicate that it's a list of lists of alternative class names, rather than a map. But I think it's fine since there's a clear example anyway.

Example: For Experiencer, the alternate name is Subject
alternative_category_names: ['Experiencer','Subject']

In the case that one specified in 'category_name' parameter does not match the data, this ensures no error is raised and it is automatically mapped
Copy link
Member

Choose a reason for hiding this comment

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

Can you also say that the model output will be the value configured in <whatever the config property> is

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I am not sure I completely understood this
Can you please explain this?

category_value2id: Dict = {}
"""Map from category values to ID, if empty it will be autocalculated during training"""
alternative_class_names: List[List] = [[]]
"""Map that stores the variations of possible class names for the given category (task)
Copy link
Member

Choose a reason for hiding this comment

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

Please change to List of lists that stores..., and again specify that the final output of the model will be the class name label specified in the the config <property>

self.config.model['nclasses'] = len(category_value2id)

# This is now handled in data_utils where an exception is raised when mismatch is found
# Make sure that the categoryvalue2id if present is same as the labels found
Copy link
Member

Choose a reason for hiding this comment

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

these commented lines are needed?

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

Successfully merging this pull request may close these issues.

3 participants