diff --git a/__init__.py b/__init__.py index cfcd7d6..8e833cc 100644 --- a/__init__.py +++ b/__init__.py @@ -17,6 +17,7 @@ from .modules.pytorch_wrapper.pt_bitwise_right_shift import PtBitwiseRightShift from .modules.pytorch_wrapper.pt_bitwise_xor import PtBitwiseXor from .modules.pytorch_wrapper.pt_bmm import PtBmm from .modules.pytorch_wrapper.pt_bool_create import PtBoolCreate +from .modules.pytorch_wrapper.pt_compute_loss import PtComputeLoss from .modules.pytorch_wrapper.pt_concat import PtConcat from .modules.pytorch_wrapper.pt_cos import PtCos from .modules.pytorch_wrapper.pt_cosh import PtCosh @@ -147,17 +148,26 @@ from .modules.pytorch_wrapper.ptf_softplus import PtfSoftplus from .modules.pytorch_wrapper.ptf_tanh import PtfTanh from .modules.pytorch_wrapper.ptn_avg_pool2d import PtnAvgPool2d from .modules.pytorch_wrapper.ptn_batch_norm2d import PtnBatchNorm2d +from .modules.pytorch_wrapper.ptn_bce_loss import PtnBCELoss +from .modules.pytorch_wrapper.ptn_bce_with_logits_loss import PtnBCEWithLogitsLoss from .modules.pytorch_wrapper.ptn_chained_model import PtnChainedModel from .modules.pytorch_wrapper.ptn_conv2d import PtnConv2d from .modules.pytorch_wrapper.ptn_conv_model import PtnConvModel +from .modules.pytorch_wrapper.ptn_cross_entropy_loss import PtnCrossEntropyLoss +from .modules.pytorch_wrapper.ptn_huber_loss import PtnHuberLoss from .modules.pytorch_wrapper.ptn_instance_norm2d import PtnInstanceNorm2d +from .modules.pytorch_wrapper.ptn_kl_div_loss import PtnKLDivLoss +from .modules.pytorch_wrapper.ptn_l1_loss import PtnL1Loss from .modules.pytorch_wrapper.ptn_layer_norm import PtnLayerNorm from .modules.pytorch_wrapper.ptn_linear import PtnLinear from .modules.pytorch_wrapper.ptn_linear_model import PtnLinearModel from .modules.pytorch_wrapper.ptn_max_pool2d import PtnMaxPool2d +from .modules.pytorch_wrapper.ptn_mse_loss import PtnMSELoss +from .modules.pytorch_wrapper.ptn_nll_loss import PtnNLLLoss from .modules.pytorch_wrapper.ptn_pre_add_channel_axis import PtnPreAddChannelAxis from .modules.pytorch_wrapper.ptn_pre_flatten import PtnPreFlatten from .modules.pytorch_wrapper.ptn_resnet_model import PtnResnetModel +from .modules.pytorch_wrapper.ptn_smooth_l1_loss import PtnSmoothL1Loss from .modules.pytorch_wrapper.pto_adam import PtoAdam from .modules.pytorch_wrapper.pto_adamw import PtoAdamW from .modules.pytorch_wrapper.pto_lr_scheduler_cosine_annealing import PtoLrSchedulerCosineAnnealing @@ -198,6 +208,7 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = { "PtBitwiseXor": PtBitwiseXor, "PtBmm": PtBmm, "PtBoolCreate": PtBoolCreate, + "PtComputeLoss": PtComputeLoss, "PtConcat": PtConcat, "PtCos": PtCos, "PtCosh": PtCosh, @@ -327,18 +338,27 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = { "PtfSoftplus": PtfSoftplus, "PtfTanh": PtfTanh, "PtnAvgPool2d": PtnAvgPool2d, + "PtnBCELoss": PtnBCELoss, + "PtnBCEWithLogitsLoss": PtnBCEWithLogitsLoss, "PtnBatchNorm2d": PtnBatchNorm2d, "PtnChainedModel": PtnChainedModel, "PtnConv2d": PtnConv2d, "PtnConvModel": PtnConvModel, + "PtnCrossEntropyLoss": PtnCrossEntropyLoss, + "PtnHuberLoss": PtnHuberLoss, "PtnInstanceNorm2d": PtnInstanceNorm2d, + "PtnKLDivLoss": PtnKLDivLoss, + "PtnL1Loss": PtnL1Loss, "PtnLayerNorm": PtnLayerNorm, "PtnLinear": PtnLinear, "PtnLinearModel": PtnLinearModel, + "PtnMSELoss": PtnMSELoss, "PtnMaxPool2d": PtnMaxPool2d, + "PtnNLLLoss": PtnNLLLoss, "PtnPreAddChannelAxis": PtnPreAddChannelAxis, "PtnPreFlatten": PtnPreFlatten, "PtnResnetModel": PtnResnetModel, + "PtnSmoothL1Loss": PtnSmoothL1Loss, "PtoAdam": PtoAdam, "PtoAdamW": PtoAdamW, "PtoLrSchedulerCosineAnnealing": PtoLrSchedulerCosineAnnealing, @@ -379,6 +399,7 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = { "PtBitwiseXor": "Pt Bitwise Xor", "PtBmm": "Pt Bmm", "PtBoolCreate": "Pt Bool Create", + "PtComputeLoss": "Pt Compute Loss", "PtConcat": "Pt Concat", "PtCos": "Pt Cos", "PtCosh": "Pt Cosh", @@ -508,18 +529,27 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = { "PtfSoftplus": "Ptf Softplus", "PtfTanh": "Ptf Tanh", "PtnAvgPool2d": "Ptn Avg Pool 2d", + "PtnBCELoss": "Ptn BCE Loss", + "PtnBCEWithLogitsLoss": "Ptn BCE With Logits Loss", "PtnBatchNorm2d": "Ptn Batch Norm 2d", "PtnChainedModel": "Ptn Chained Model", "PtnConv2d": "Ptn Conv 2d", "PtnConvModel": "Ptn Conv Model", + "PtnCrossEntropyLoss": "Ptn Cross Entropy Loss", + "PtnHuberLoss": "Ptn Huber Loss", "PtnInstanceNorm2d": "Ptn Instance Norm 2d", + "PtnKLDivLoss": "Ptn KL Div Loss", + "PtnL1Loss": "Ptn L1 Loss", "PtnLayerNorm": "Ptn Layer Norm", "PtnLinear": "Ptn Linear", "PtnLinearModel": "Ptn Linear Model", + "PtnMSELoss": "Ptn MSE Loss", "PtnMaxPool2d": "Ptn Max Pool 2d", + "PtnNLLLoss": "Ptn NLL Loss", "PtnPreAddChannelAxis": "Ptn Pre Add Channel Axis", "PtnPreFlatten": "Ptn Pre Flatten", "PtnResnetModel": "Ptn Resnet Model", + "PtnSmoothL1Loss": "Ptn Smooth L1 Loss", "PtoAdam": "Pto Adam", "PtoAdamW": "Pto AdamW", "PtoLrSchedulerCosineAnnealing": "Pto Lr Scheduler Cosine Annealing", diff --git a/dev/scripts/init_py_generate.py b/dev/scripts/init_py_generate.py index 07d1f9b..020794b 100644 --- a/dev/scripts/init_py_generate.py +++ b/dev/scripts/init_py_generate.py @@ -83,6 +83,10 @@ def format_display_name(class_name): ("Si L U", "SiLU"), ("G E L U", "GELU"), ("S G D", "SGD"), + ("B C E", "BCE"), + ("K L", "KL"), + ("M S E", "MSE"), + ("N L L", "NLL"), ("Data Frame", "DataFrame"), ("Multi Index", "MultiIndex"), ("MPL Bar", "MPL Bar Chart"), diff --git a/docs/reference/node_reference.md b/docs/reference/node_reference.md index 076e81f..ecb0a63 100644 --- a/docs/reference/node_reference.md +++ b/docs/reference/node_reference.md @@ -111,6 +111,18 @@ | [Pt Logical Not](pt_logical_not.md) | Performs a logical NOT operation on a PyTorch tensor element-wise. | | [Pt Logical Or](pt_logical_or.md) | Performs a logical OR operation on two PyTorch tensors element-wise. | | [Pt Logical Xor](pt_logical_xor.md) | Performs a logical XOR operation on two PyTorch tensors element-wise. | +## PyTorch wrapper - Loss function +| Node | Description | +| --- | --- | +| [Ptn BCE Loss](ptn_bce_loss.md) | A class to compute the binary cross entropy loss. | +| [Ptn BCE With Logits Loss](ptn_bce_with_logits_loss.md) | A class to compute the sigmoid then binary cross entropy loss. | +| [Ptn Cross Entropy Loss](ptn_cross_entropy_loss.md) | A class to compute the cross entropy loss. | +| [Ptn Huber Loss](ptn_huber_loss.md) | | +| [Ptn KL Div Loss](ptn_kl_div_loss.md) | A class to compute the KL divergence loss. | +| [Ptn L1 Loss](ptn_l1_loss.md) | A class to compute the L1 loss. | +| [Ptn MSE Loss](ptn_mse_loss.md) | A class to compute the squared loss. | +| [Ptn NLL Loss](ptn_nll_loss.md) | A model to compute the negative log likelihood (NLL) loss. | +| [Ptn Smooth L1 Loss](ptn_smooth_l1_loss.md) | | ## PyTorch wrapper - Math operations | Node | Description | | --- | --- | @@ -204,6 +216,7 @@ | Node | Description | | --- | --- | | [Pt Apply Function](pt_apply_function.md) | Applies a function to the input tensor. | +| [Pt Compute Loss](pt_compute_loss.md) | Computes loss for the input with the target using the specified loss function. | | [Pt Data Loader](pt_data_loader.md) | Loads data from a dataset node and creates a PyTorch DataLoader. | | [Pt Data Loader From Tensors](pt_data_loader_from_tensors.md) | Creates a Torchvision Dataloader from a pair of tensors. | | [Pt Evaluate Classification Model](pt_evaluate_classification_model.md) | Performs inference on test data and computes evaluation metrics. | diff --git a/docs/reference/pt_compute_loss.md b/docs/reference/pt_compute_loss.md new file mode 100644 index 0000000..2945304 --- /dev/null +++ b/docs/reference/pt_compute_loss.md @@ -0,0 +1,21 @@ +# Pt Compute Loss +Computes loss for the input with the target using the specified loss function. + +The user does not normally need to use this node. Instead the user should use training nodes that accept a loss object. + +## Input +| Name | Data type | +|---|---| +| input_tens | Tensor | +| target_tens | Tensor | +| loss | Ptloss | + +## Output +| Data type | +|---| +| Tensor | + +
+Category: PyTorch wrapper - Training + +ComfyUI Pt Wrapper Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/ptn_bce_loss.md b/docs/reference/ptn_bce_loss.md new file mode 100644 index 0000000..7e1a432 --- /dev/null +++ b/docs/reference/ptn_bce_loss.md @@ -0,0 +1,25 @@ +# Ptn BCE Loss +A class to compute the binary cross entropy loss. + +Unlike the Ptn BCE Loss With Logits node, which takes logits as input, this node expects probabilities. If the input consists of logits, apply a Sigmoid function first or use the Ptn BCE Loss With Logits node instead. + + Args: + reduction (str): Reduction method. + `none`: no reduction will be applied done. + `mean`: computes the mean. + `sum`: computes the sum. + +## Input +| Name | Data type | +|---|---| +| reduction | Mean | + +## Output +| Data type | +|---| +| Ptloss | + +
+Category: PyTorch wrapper - Loss function + +ComfyUI Pt Wrapper Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/ptn_bce_with_logits_loss.md b/docs/reference/ptn_bce_with_logits_loss.md new file mode 100644 index 0000000..652d542 --- /dev/null +++ b/docs/reference/ptn_bce_with_logits_loss.md @@ -0,0 +1,23 @@ +# Ptn BCE With Logits Loss +A class to compute the sigmoid then binary cross entropy loss. + + Args: + reduction (str): Reduction method. + `none`: no reduction will be applied done. + `mean`: computes the mean. + `sum`: computes the sum. + +## Input +| Name | Data type | +|---|---| +| reduction | Mean | + +## Output +| Data type | +|---| +| Ptloss | + +
+Category: PyTorch wrapper - Loss function + +ComfyUI Pt Wrapper Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/ptn_cross_entropy_loss.md b/docs/reference/ptn_cross_entropy_loss.md new file mode 100644 index 0000000..fe8a00b --- /dev/null +++ b/docs/reference/ptn_cross_entropy_loss.md @@ -0,0 +1,25 @@ +# Ptn Cross Entropy Loss +A class to compute the cross entropy loss. + +Unlike Ptn NLL Loss node which takes log probabilities for input, input to this node needs to be logits. You can consider this node as a combination of log softmax and NLL. + + Args: + reduction: (str): Reduction method. + `none`: no reduction will be applied done. + `mean`: computes the mean. + `sum`: computes the sum. + +## Input +| Name | Data type | +|---|---| +| reduction | Mean | + +## Output +| Data type | +|---| +| Ptloss | + +
+Category: PyTorch wrapper - Loss function + +ComfyUI Pt Wrapper Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/ptn_huber_loss.md b/docs/reference/ptn_huber_loss.md new file mode 100644 index 0000000..d718fd7 --- /dev/null +++ b/docs/reference/ptn_huber_loss.md @@ -0,0 +1,28 @@ +# Ptn Huber Loss +A class to compute the Huber loss. + +Please see [PyTorch documentation](https://pytorch.org/docs/stable/generated/torch.nn.HuberLoss.html) for details. + + + Args: + reduction (str): Reduction method. + `none`: no reduction will be applied done. + `mean`: computes the mean. + `sum`: computes the sum. + delta (float): The threshold used to switch between adjusted MSE and L1 loss. + +## Input +| Name | Data type | +|---|---| +| reduction | Mean | +| delta | Float | + +## Output +| Data type | +|---| +| Ptloss | + +
+Category: PyTorch wrapper - Loss function + +ComfyUI Pt Wrapper Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/ptn_kl_div_loss.md b/docs/reference/ptn_kl_div_loss.md new file mode 100644 index 0000000..24fbc29 --- /dev/null +++ b/docs/reference/ptn_kl_div_loss.md @@ -0,0 +1,28 @@ +# Ptn KL Div Loss +A class to compute the KL divergence loss. + + Args: + reduction (str): Reduction method. + `none`: no reduction will be applied done. + `batchmean`: computes the sum then divides by the input size (recommended in the PyTorch document). + `mean`: computes the mean. + `sum`: computes the sum. + + log_target (bool): Set to `True` if the target is in log probability instead of probability. + The input (y_hat) must always be in log-probability form, regardless of this flag. + +## Input +| Name | Data type | +|---|---| +| reduction | None | +| log_target | Boolean | + +## Output +| Data type | +|---| +| Ptloss | + +
+Category: PyTorch wrapper - Loss function + +ComfyUI Pt Wrapper Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/ptn_l1_loss.md b/docs/reference/ptn_l1_loss.md new file mode 100644 index 0000000..1fc8073 --- /dev/null +++ b/docs/reference/ptn_l1_loss.md @@ -0,0 +1,23 @@ +# Ptn L1 Loss +A class to compute the L1 loss. + + Args: + reduction: (str): Reduction method. + `none`: no reduction will be applied done. + `mean`: computes the mean. + `sum`: computes the sum. + +## Input +| Name | Data type | +|---|---| +| reduction | Mean | + +## Output +| Data type | +|---| +| Ptloss | + +
+Category: PyTorch wrapper - Loss function + +ComfyUI Pt Wrapper Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/ptn_mse_loss.md b/docs/reference/ptn_mse_loss.md new file mode 100644 index 0000000..6668235 --- /dev/null +++ b/docs/reference/ptn_mse_loss.md @@ -0,0 +1,23 @@ +# Ptn MSE Loss +A class to compute the squared loss. + + Args: + reduction: (str): Reduction method. + `none`: no reduction will be applied done. + `sum`: computes the sum. + `mean`: computes the mean. + +## Input +| Name | Data type | +|---|---| +| reduction | Mean | + +## Output +| Data type | +|---| +| Ptloss | + +
+Category: PyTorch wrapper - Loss function + +ComfyUI Pt Wrapper Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/ptn_nll_loss.md b/docs/reference/ptn_nll_loss.md new file mode 100644 index 0000000..ec0a11a --- /dev/null +++ b/docs/reference/ptn_nll_loss.md @@ -0,0 +1,25 @@ +# Ptn NLL Loss +A model to compute the negative log likelihood (NLL) loss. + +Unlike Ptn Cross Entropy which takes input in logits, input to this nodes needs to be in log-probabilities. + + Args: + reduction: (str): Reduction method. + `none`: no reduction will be applied done. + `mean`: computes the mean. + `sum`: computes the sum. + +## Input +| Name | Data type | +|---|---| +| reduction | Mean | + +## Output +| Data type | +|---| +| Ptloss | + +
+Category: PyTorch wrapper - Loss function + +ComfyUI Pt Wrapper Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/ptn_smooth_l1_loss.md b/docs/reference/ptn_smooth_l1_loss.md new file mode 100644 index 0000000..d591879 --- /dev/null +++ b/docs/reference/ptn_smooth_l1_loss.md @@ -0,0 +1,28 @@ +# Ptn Smooth L1 Loss +A class to compute the Smooth L1 loss. + +Please see [PyTorch documentation](https://pytorch.org/docs/stable/generated/torch.nn.SmoothL1Loss.html) for details and exact formulation. + + + Args: + reduction (str): Reduction method. + `none`: no reduction will be applied done. + `mean`: computes the mean. + `sum`: computes the sum. + beta (float): The threshold used to switch between adjusted L2 and L1 loss. + +## Input +| Name | Data type | +|---|---| +| reduction | Mean | +| beta | Float | + +## Output +| Data type | +|---| +| Ptloss | + +
+Category: PyTorch wrapper - Loss function + +ComfyUI Pt Wrapper Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/modules/pytorch_wrapper/pt_apply_function.py b/modules/pytorch_wrapper/pt_apply_function.py index e48f6de..81cd17f 100644 --- a/modules/pytorch_wrapper/pt_apply_function.py +++ b/modules/pytorch_wrapper/pt_apply_function.py @@ -43,6 +43,6 @@ class PtApplyFunction: closure (Callable): Differentiable function to apply to the tensor. Returns: - Tuple[nn.Module]: A tuple containing the instantiated model. + Tuple[nn.Module]: A tuple containing the tensor. """ return (closure(tens),) diff --git a/modules/pytorch_wrapper/pt_compute_loss.py b/modules/pytorch_wrapper/pt_compute_loss.py new file mode 100644 index 0000000..e1b587c --- /dev/null +++ b/modules/pytorch_wrapper/pt_compute_loss.py @@ -0,0 +1,49 @@ +from typing import Any, Dict, Tuple +import torch +import torch.nn as nn + +class PtComputeLoss: + """ + Computes loss for the input with the target using the specified loss function. + + The user does not normally need to use this node. Instead the user should use training nodes that accept a loss object. + + category: PyTorch wrapper - Training + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the expected input types. + + Returns: + Dict[str, Any]: A dictionary specifying required and optional input types. + """ + return { + "required": { + "input_tens": ("TENSOR", {}), + "target_tens": ("TENSOR", {}), + "loss": ("PTLOSS", {}) + } + } + + RETURN_TYPES: Tuple[str, ...] = ("TENSOR",) + FUNCTION: str = "f" + CATEGORY: str = "Training" + + def f(self, + input_tens: torch.Tensor, + target_tens: torch.Tensor, + loss: nn.Module) -> Tuple[torch.Tensor]: + """ + Computes loss for the input with the target using the specified loss function. + + Args: + input_tens (torch.Tensor): Tensor containing the model output or y_hat. + target_tens (torch.Tensor): Tensor containing the ground truth or the label. + loss (nn.Module): Loss object instance. + + Returns: + Tuple[nn.Module]: A tuple containing the computed loss. + """ + return (loss(input_tens, target_tens),) diff --git a/modules/pytorch_wrapper/ptn_bce_loss.py b/modules/pytorch_wrapper/ptn_bce_loss.py new file mode 100644 index 0000000..71f6399 --- /dev/null +++ b/modules/pytorch_wrapper/ptn_bce_loss.py @@ -0,0 +1,63 @@ +from typing import Any, Dict, Tuple +import torch +import torch.nn as nn + + +class PtnBCELoss: + """ + Ptn BCE Loss: + A class to compute the binary cross entropy loss. + + Unlike the Ptn BCE Loss With Logits node, which takes logits as input, this node expects probabilities. If the input consists of logits, apply a Sigmoid function first or use the Ptn BCE Loss With Logits node instead. + + Args: + reduction (str): Reduction method. + `none`: no reduction will be applied done. + `mean`: computes the mean. + `sum`: computes the sum. + + + category: PyTorch wrapper - Loss function + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "reduction": (("mean", "sum", "none"),) + } + } + + RETURN_TYPES: Tuple[str] = ("PTLOSS",) + FUNCTION: str = "f" + CATEGORY: str = "Training" + + @classmethod + def IS_CHANGED(cls, **kw): + return float("NaN") + + def f(self, + reduction: str + ) -> Tuple[nn.Module]: + """ + A model to compute the cross entropy loss. + + Args: + reduction: (str): Reduction method. + `none`: no reduction will be applied done. + `sum`: computes the sum. + `mean`: computes the mean. + + Returns: + Tuple[nn.Module]: A tuple containing the instantiated PyTorch model. + """ + with torch.inference_mode(False): + model = nn.BCELoss(reduction=reduction) + + return (model,) diff --git a/modules/pytorch_wrapper/ptn_bce_with_logits_loss.py b/modules/pytorch_wrapper/ptn_bce_with_logits_loss.py new file mode 100644 index 0000000..5d15bb2 --- /dev/null +++ b/modules/pytorch_wrapper/ptn_bce_with_logits_loss.py @@ -0,0 +1,61 @@ +from typing import Any, Dict, Tuple +import torch +import torch.nn as nn + + +class PtnBCEWithLogitsLoss: + """ + Ptn BCE With Logits Loss: + A class to compute the sigmoid then binary cross entropy loss. + + Args: + reduction (str): Reduction method. + `none`: no reduction will be applied done. + `mean`: computes the mean. + `sum`: computes the sum. + + + category: PyTorch wrapper - Loss function + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "reduction": (("mean", "sum", "none"),) + } + } + + RETURN_TYPES: Tuple[str] = ("PTLOSS",) + FUNCTION: str = "f" + CATEGORY: str = "Training" + + @classmethod + def IS_CHANGED(cls, **kw): + return float("NaN") + + def f(self, + reduction: str + ) -> Tuple[nn.Module]: + """ + A class to compute the sigmoid then binary cross entropy loss. + + Args: + reduction: (str): Reduction method. + `none`: no reduction will be applied done. + `sum`: computes the sum. + `mean`: computes the mean. + + Returns: + Tuple[nn.Module]: A tuple containing the instantiated PyTorch model. + """ + with torch.inference_mode(False): + model = nn.BCEWithLogitsLoss(reduction=reduction) + + return (model,) diff --git a/modules/pytorch_wrapper/ptn_cross_entropy_loss.py b/modules/pytorch_wrapper/ptn_cross_entropy_loss.py new file mode 100644 index 0000000..2dbe40a --- /dev/null +++ b/modules/pytorch_wrapper/ptn_cross_entropy_loss.py @@ -0,0 +1,63 @@ +from typing import Any, Dict, Tuple +import torch +import torch.nn as nn + + +class PtnCrossEntropyLoss: + """ + Ptn Cross Entropy Loss: + A class to compute the cross entropy loss. + + Unlike Ptn NLL Loss node which takes log probabilities for input, input to this node needs to be logits. You can consider this node as a combination of log softmax and NLL. + + Args: + reduction: (str): Reduction method. + `none`: no reduction will be applied done. + `mean`: computes the mean. + `sum`: computes the sum. + + + category: PyTorch wrapper - Loss function + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "reduction": (("mean", "sum", "none"),) + } + } + + RETURN_TYPES: Tuple[str] = ("PTLOSS",) + FUNCTION: str = "f" + CATEGORY: str = "Training" + + @classmethod + def IS_CHANGED(cls, **kw): + return float("NaN") + + def f(self, + reduction: str + ) -> Tuple[nn.Module]: + """ + A model to compute the cross entropy loss. + + Args: + reduction: (str): Reduction method. + `none`: no reduction will be applied done. + `sum`: computes the sum. + `mean`: computes the mean. + + Returns: + Tuple[nn.Module]: A tuple containing the instantiated PyTorch model. + """ + with torch.inference_mode(False): + model = nn.CrossEntropyLoss(reduction=reduction) + + return (model,) diff --git a/modules/pytorch_wrapper/ptn_huber_loss.py b/modules/pytorch_wrapper/ptn_huber_loss.py new file mode 100644 index 0000000..ddacc58 --- /dev/null +++ b/modules/pytorch_wrapper/ptn_huber_loss.py @@ -0,0 +1,68 @@ +from typing import Any, Dict, Tuple +import torch +import torch.nn as nn + + +class PtnHuberLoss: + """ + Ptn Huber Loss: + + A class to compute the Huber loss. + + Please see [PyTorch documentation](https://pytorch.org/docs/stable/generated/torch.nn.HuberLoss.html) for details. + + + Args: + reduction (str): Reduction method. + `none`: no reduction will be applied done. + `mean`: computes the mean. + `sum`: computes the sum. + delta (float): The threshold used to switch between adjusted MSE and L1 loss. + + category: PyTorch wrapper - Loss function + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "reduction": (("mean", "sum", "none"),), + "delta": ("FLOAT", {"default":1.0, "min":0, "max":1e9, "step":0.0000000001}), + } + } + + RETURN_TYPES: Tuple[str] = ("PTLOSS",) + FUNCTION: str = "f" + CATEGORY: str = "Training" + + @classmethod + def IS_CHANGED(cls, **kw): + return float("NaN") + + def f(self, + reduction: str, + delta: float + ) -> Tuple[nn.Module]: + """ + A model to compute the Huber loss. + + Args: + reduction: (str): Reduction method. + `none`: no reduction will be applied done. + `mean`: computes the mean. + `sum`: computes the sum. + beta (float): The threshold used to switch between adjusted MSE and L1 loss. + + Returns: + Tuple[nn.Module]: A tuple containing the instantiated PyTorch model. + """ + with torch.inference_mode(False): + model = nn.HuberLoss(reduction=reduction, delta=delta) + + return (model,) diff --git a/modules/pytorch_wrapper/ptn_kl_div_loss.py b/modules/pytorch_wrapper/ptn_kl_div_loss.py new file mode 100644 index 0000000..ac7aa1a --- /dev/null +++ b/modules/pytorch_wrapper/ptn_kl_div_loss.py @@ -0,0 +1,71 @@ +from typing import Any, Dict, Tuple +import torch +import torch.nn as nn + + +class PtnKLDivLoss: + """ + Ptn KL Div Loss: + A class to compute the KL divergence loss. + + Args: + reduction (str): Reduction method. + `none`: no reduction will be applied done. + `batchmean`: computes the sum then divides by the input size (recommended in the PyTorch document). + `mean`: computes the mean. + `sum`: computes the sum. + + log_target (bool): Set to `True` if the target is in log probability instead of probability. + The input (y_hat) must always be in log-probability form, regardless of this flag. + + category: PyTorch wrapper - Loss function + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "reduction": (("batchmean", "mean", "sum", "none"),), + "log_target": ("BOOLEAN", {"default": False}) + } + } + + RETURN_TYPES: Tuple[str] = ("PTLOSS",) + FUNCTION: str = "f" + CATEGORY: str = "Training" + + @classmethod + def IS_CHANGED(cls, **kw): + return float("NaN") + + def f(self, + reduction: str, + log_target: bool + ) -> Tuple[nn.Module]: + """ + A model to compute the cross entropy loss. + + Args: + reduction: (str): Reduction method. + `none`: no reduction will be applied done. + `batchmean`: computes the sum then divides by the input size (recommended in the PyTorch document). + `sum`: computes the sum. + `mean`: computes the mean. + log_target (bool): Set to `True` if the target is in log probability instead of probability. + The input (y_hat) must always be in log-probability form, regardless of this flag. + + Returns: + Tuple[nn.Module]: A tuple containing the instantiated PyTorch model. + """ + with torch.inference_mode(False): + model = nn.KLDivLoss( + reduction=reduction, + log_target=log_target) + + return (model,) diff --git a/modules/pytorch_wrapper/ptn_l1_loss.py b/modules/pytorch_wrapper/ptn_l1_loss.py new file mode 100644 index 0000000..9ad6f2e --- /dev/null +++ b/modules/pytorch_wrapper/ptn_l1_loss.py @@ -0,0 +1,62 @@ +from typing import Any, Dict, Tuple +import torch +import torch.nn as nn + + +class PtnL1Loss: + """ + Ptn L1 Loss: + A class to compute the L1 loss. + + Args: + reduction: (str): Reduction method. + `none`: no reduction will be applied done. + `mean`: computes the mean. + `sum`: computes the sum. + + + category: PyTorch wrapper - Loss function + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "reduction": (("mean", "sum", "none"),) + } + } + + RETURN_TYPES: Tuple[str] = ("PTLOSS",) + FUNCTION: str = "f" + CATEGORY: str = "Training" + + @classmethod + def IS_CHANGED(cls, **kw): + return float("NaN") + + def f(self, + reduction: str + ) -> Tuple[nn.Module]: + """ + A model to compute the L1 loss. + + Args: + reduction: (str): Reduction method. + `none`: no reduction will be applied done. + `mean`: computes the mean. + `sum`: computes the sum. + + + Returns: + Tuple[nn.Module]: A tuple containing the instantiated PyTorch model. + """ + with torch.inference_mode(False): + model = nn.L1Loss(reduction=reduction) + + return (model,) diff --git a/modules/pytorch_wrapper/ptn_mse_loss.py b/modules/pytorch_wrapper/ptn_mse_loss.py new file mode 100644 index 0000000..6c474bb --- /dev/null +++ b/modules/pytorch_wrapper/ptn_mse_loss.py @@ -0,0 +1,60 @@ +from typing import Any, Dict, Tuple +import torch +import torch.nn as nn + + +class PtnMSELoss: + """ + Ptn MSE Loss: + A class to compute the squared loss. + + Args: + reduction: (str): Reduction method. + `none`: no reduction will be applied done. + `sum`: computes the sum. + `mean`: computes the mean. + + category: PyTorch wrapper - Loss function + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "reduction": (("mean", "sum", "none"),) + } + } + + RETURN_TYPES: Tuple[str] = ("PTLOSS",) + FUNCTION: str = "f" + CATEGORY: str = "Training" + + @classmethod + def IS_CHANGED(cls, **kw): + return float("NaN") + + def f(self, + reduction: str + ) -> Tuple[nn.Module]: + """ + A model to compute the squared loss. + + Args: + reduction: (str): Reduction method. + `none`: no reduction will be applied done. + `sum`: computes the sum. + `mean`: computes the mean. + + Returns: + Tuple[nn.Module]: A tuple containing the instantiated PyTorch model. + """ + with torch.inference_mode(False): + model = nn.MSELoss(reduction=reduction) + + return (model,) diff --git a/modules/pytorch_wrapper/ptn_nll_loss.py b/modules/pytorch_wrapper/ptn_nll_loss.py new file mode 100644 index 0000000..0968275 --- /dev/null +++ b/modules/pytorch_wrapper/ptn_nll_loss.py @@ -0,0 +1,63 @@ +from typing import Any, Dict, Tuple +import torch +import torch.nn as nn + + +class PtnNLLLoss: + """ + Ptn NLL Loss: + A model to compute the negative log likelihood (NLL) loss. + + Unlike Ptn Cross Entropy which takes input in logits, input to this nodes needs to be in log-probabilities. + + Args: + reduction: (str): Reduction method. + `none`: no reduction will be applied done. + `mean`: computes the mean. + `sum`: computes the sum. + + + category: PyTorch wrapper - Loss function + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "reduction": (("mean", "sum", "none"),) + } + } + + RETURN_TYPES: Tuple[str] = ("PTLOSS",) + FUNCTION: str = "f" + CATEGORY: str = "Training" + + @classmethod + def IS_CHANGED(cls, **kw): + return float("NaN") + + def f(self, + reduction: str + ) -> Tuple[nn.Module]: + """ + A model to compute the NLL loss. + + Args: + reduction: (str): Reduction method. + `none`: no reduction will be applied done. + `sum`: computes the sum. + `mean`: computes the mean. + + Returns: + Tuple[nn.Module]: A tuple containing the instantiated PyTorch model. + """ + with torch.inference_mode(False): + model = nn.NLLLoss(reduction=reduction) + + return (model,) diff --git a/modules/pytorch_wrapper/ptn_smooth_l1_loss.py b/modules/pytorch_wrapper/ptn_smooth_l1_loss.py new file mode 100644 index 0000000..b6b75ab --- /dev/null +++ b/modules/pytorch_wrapper/ptn_smooth_l1_loss.py @@ -0,0 +1,68 @@ +from typing import Any, Dict, Tuple +import torch +import torch.nn as nn + + +class PtnSmoothL1Loss: + """ + Ptn Smooth L1 Loss: + + A class to compute the Smooth L1 loss. + + Please see [PyTorch documentation](https://pytorch.org/docs/stable/generated/torch.nn.SmoothL1Loss.html) for details and exact formulation. + + + Args: + reduction (str): Reduction method. + `none`: no reduction will be applied done. + `mean`: computes the mean. + `sum`: computes the sum. + beta (float): The threshold used to switch between adjusted L2 and L1 loss. + + category: PyTorch wrapper - Loss function + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "reduction": (("mean", "sum", "none"),), + "beta": ("FLOAT", {"default":1.0, "min":0, "max":1e9, "step":0.0000000001}), + } + } + + RETURN_TYPES: Tuple[str] = ("PTLOSS",) + FUNCTION: str = "f" + CATEGORY: str = "Training" + + @classmethod + def IS_CHANGED(cls, **kw): + return float("NaN") + + def f(self, + reduction: str, + beta: float + ) -> Tuple[nn.Module]: + """ + A model to compute the Smooth L1 loss. + + Args: + reduction: (str): Reduction method. + `none`: no reduction will be applied done. + `mean`: computes the mean. + `sum`: computes the sum. + beta (float): The threshold used to switch between adjusted L2 and L1 loss. + + Returns: + Tuple[nn.Module]: A tuple containing the instantiated PyTorch model. + """ + with torch.inference_mode(False): + model = nn.SmoothL1Loss(reduction=reduction, beta=beta) + + return (model,) diff --git a/pyproject.toml b/pyproject.toml index 607d206..eb83abe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "pt-wrapper" description = "PyTorch extension for ComfyUI featuring extensive PyTorch wrapper nodes for seamless tensor operations and PyTorch model training." -version = "3.1.4" +version = "3.2.0" license = {file = "LICENSE"} [project.urls] diff --git a/tests/pytorch_wrapper/pt_abs_test.py b/tests/pytorch_wrapper/pt_abs_test.py index 046e88a..7fc8588 100644 --- a/tests/pytorch_wrapper/pt_abs_test.py +++ b/tests/pytorch_wrapper/pt_abs_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_abs import PtAbs # Updated import to PtAbs +from pytorch_wrapper.pt_abs import PtAbs class TestPtAbs(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_acos_test.py b/tests/pytorch_wrapper/pt_acos_test.py index 528acf8..6cc132f 100644 --- a/tests/pytorch_wrapper/pt_acos_test.py +++ b/tests/pytorch_wrapper/pt_acos_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_acos import PtAcos # Updated import to PtAcos +from pytorch_wrapper.pt_acos import PtAcos class TestPtAcos(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_asin_test.py b/tests/pytorch_wrapper/pt_asin_test.py index 92f8905..65706f7 100644 --- a/tests/pytorch_wrapper/pt_asin_test.py +++ b/tests/pytorch_wrapper/pt_asin_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_asin import PtAsin # Updated import to PtAsin +from pytorch_wrapper.pt_asin import PtAsin class TestPtAsin(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_atan_test.py b/tests/pytorch_wrapper/pt_atan_test.py index 551d7a5..687c1dc 100644 --- a/tests/pytorch_wrapper/pt_atan_test.py +++ b/tests/pytorch_wrapper/pt_atan_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_atan import PtAtan # Updated import to PtAtan +from pytorch_wrapper.pt_atan import PtAtan class TestPtAtan(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_bitwise_and_test.py b/tests/pytorch_wrapper/pt_bitwise_and_test.py index 630d459..43ad8de 100644 --- a/tests/pytorch_wrapper/pt_bitwise_and_test.py +++ b/tests/pytorch_wrapper/pt_bitwise_and_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_bitwise_and import PtBitwiseAnd # Updated import to PtBitwiseAnd +from pytorch_wrapper.pt_bitwise_and import PtBitwiseAnd class TestPtBitwiseAnd(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_bitwise_left_shift_test.py b/tests/pytorch_wrapper/pt_bitwise_left_shift_test.py index 124b8bf..bf46ff5 100644 --- a/tests/pytorch_wrapper/pt_bitwise_left_shift_test.py +++ b/tests/pytorch_wrapper/pt_bitwise_left_shift_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_bitwise_left_shift import PtBitwiseLeftShift # Updated import to PtBitwiseLeftShift +from pytorch_wrapper.pt_bitwise_left_shift import PtBitwiseLeftShift class TestPtBitwiseLeftShift(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_bitwise_not_test.py b/tests/pytorch_wrapper/pt_bitwise_not_test.py index a7d87d8..bad501c 100644 --- a/tests/pytorch_wrapper/pt_bitwise_not_test.py +++ b/tests/pytorch_wrapper/pt_bitwise_not_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_bitwise_not import PtBitwiseNot # Updated import to PtBitwiseNot +from pytorch_wrapper.pt_bitwise_not import PtBitwiseNot class TestPtBitwiseNot(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_bitwise_or_test.py b/tests/pytorch_wrapper/pt_bitwise_or_test.py index 9b606fd..ad18a8d 100644 --- a/tests/pytorch_wrapper/pt_bitwise_or_test.py +++ b/tests/pytorch_wrapper/pt_bitwise_or_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_bitwise_or import PtBitwiseOr # Updated import to PtBitwiseOr +from pytorch_wrapper.pt_bitwise_or import PtBitwiseOr class TestPtBitwiseOr(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_bitwise_right_shift_test.py b/tests/pytorch_wrapper/pt_bitwise_right_shift_test.py index 69a9b9b..26c97c5 100644 --- a/tests/pytorch_wrapper/pt_bitwise_right_shift_test.py +++ b/tests/pytorch_wrapper/pt_bitwise_right_shift_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_bitwise_right_shift import PtBitwiseRightShift # Updated import to PtBitwiseRightShift +from pytorch_wrapper.pt_bitwise_right_shift import PtBitwiseRightShift class TestPtBitwiseRightShift(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_bitwise_xor_test.py b/tests/pytorch_wrapper/pt_bitwise_xor_test.py index 24ce242..4a63908 100644 --- a/tests/pytorch_wrapper/pt_bitwise_xor_test.py +++ b/tests/pytorch_wrapper/pt_bitwise_xor_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_bitwise_xor import PtBitwiseXor # Updated import to PtBitwiseXor +from pytorch_wrapper.pt_bitwise_xor import PtBitwiseXor class TestPtBitwiseXor(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_bmm_test.py b/tests/pytorch_wrapper/pt_bmm_test.py index e1e1a87..ec58d49 100644 --- a/tests/pytorch_wrapper/pt_bmm_test.py +++ b/tests/pytorch_wrapper/pt_bmm_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_bmm import PtBmm # Updated import to PtBmm +from pytorch_wrapper.pt_bmm import PtBmm class TestPtBmm(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_compute_loss_test.py b/tests/pytorch_wrapper/pt_compute_loss_test.py new file mode 100644 index 0000000..da2d7e4 --- /dev/null +++ b/tests/pytorch_wrapper/pt_compute_loss_test.py @@ -0,0 +1,86 @@ +import os +import sys +import unittest +import torch +from torch import Tensor +from typing import List, Tuple + +PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "..")) +MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") +sys.path.append(MODULE_ROOT) + +from pytorch_wrapper.pt_compute_loss import PtComputeLoss +from pytorch_wrapper.ptn_l1_loss import PtnL1Loss + + +class TestComputeLoss(unittest.TestCase): + def setUp(self) -> None: + """ + Set up the test instance by initializing the loss functions and test cases. + """ + self.compute_node = PtComputeLoss() + self.loss_node = PtnL1Loss() + self.loss_fun_none = self.loss_node.f("none")[0] + self.loss_fun_mean = self.loss_node.f("mean")[0] + self.loss_fun_sum = self.loss_node.f("sum")[0] + + # Test cases: (predicted values y_hat, ground truth y) + self.test_cases: List[Tuple[Tensor, Tensor]] = [ + ( + torch.tensor([0.1, -1.0, 10, -100, 1000], dtype=torch.float32), + torch.tensor([0.09, -1.1, 9, -101, 999], dtype=torch.float32), + ) + ] + + def unreduced_loss(self, y_hat: Tensor, y: Tensor) -> Tensor: + """ + Compute the unreduced L1 loss. + + Args: + y_hat (Tensor): Predicted values. + y (Tensor): Ground truth values. + + Returns: + Tensor: Unreduced element-wise L1 loss. + """ + return torch.abs(y_hat - y) + + def test_none(self) -> None: + """ + Test L1 loss with reduction='none'. + """ + for y_hat, y in self.test_cases: + with self.subTest(y_hat=y_hat): + actual = self.compute_node.f(y_hat, y, self.loss_fun_none)[0] + expected = self.unreduced_loss(y_hat, y) + actual = self.loss_fun_none(y_hat, y) + self.assertTrue(torch.allclose(actual, expected, atol=1e-6), + f"Expected {expected}, got {actual}") + + def test_mean(self) -> None: + """ + Test L1 loss with reduction='mean'. + """ + for y_hat, y in self.test_cases: + with self.subTest(y_hat=y_hat): + loss = self.unreduced_loss(y_hat, y) + expected = loss.mean(dim=0) + actual = self.compute_node.f(y_hat, y, self.loss_fun_mean)[0] + self.assertTrue(torch.equal(actual, expected), + f"Expected {expected}, got {actual}") + + def test_sum(self) -> None: + """ + Test L1 loss with reduction='sum'. + """ + for y_hat, y in self.test_cases: + with self.subTest(y_hat=y_hat): + loss = self.unreduced_loss(y_hat, y) + expected = loss.sum(dim=0) + actual = self.compute_node.f(y_hat, y, self.loss_fun_sum)[0] + self.assertTrue(torch.equal(actual, expected), + f"Expected {expected}, got {actual}") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pytorch_wrapper/pt_cos_test.py b/tests/pytorch_wrapper/pt_cos_test.py index 9d03217..f853ed5 100644 --- a/tests/pytorch_wrapper/pt_cos_test.py +++ b/tests/pytorch_wrapper/pt_cos_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_cos import PtCos # Updated import to PtCos +from pytorch_wrapper.pt_cos import PtCos class TestPtCos(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_cosh_test.py b/tests/pytorch_wrapper/pt_cosh_test.py index 299fec0..4963f41 100644 --- a/tests/pytorch_wrapper/pt_cosh_test.py +++ b/tests/pytorch_wrapper/pt_cosh_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_cosh import PtCosh # Updated import to PtCosh +from pytorch_wrapper.pt_cosh import PtCosh class TestPtCosh(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_div_test.py b/tests/pytorch_wrapper/pt_div_test.py index 7e6e022..a7239a8 100644 --- a/tests/pytorch_wrapper/pt_div_test.py +++ b/tests/pytorch_wrapper/pt_div_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_div import PtDiv # Updated import to PtDiv +from pytorch_wrapper.pt_div import PtDiv class TestPtDiv(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_einsum_test.py b/tests/pytorch_wrapper/pt_einsum_test.py index ef93fcb..f44bea8 100644 --- a/tests/pytorch_wrapper/pt_einsum_test.py +++ b/tests/pytorch_wrapper/pt_einsum_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_einsum import PtEinsum # Updated import to PtEinsum +from pytorch_wrapper.pt_einsum import PtEinsum class TestPtEinsum(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_eq_test.py b/tests/pytorch_wrapper/pt_eq_test.py index 0943a8f..6d2c927 100644 --- a/tests/pytorch_wrapper/pt_eq_test.py +++ b/tests/pytorch_wrapper/pt_eq_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_eq import PtEq # Updated import to PtEq +from pytorch_wrapper.pt_eq import PtEq class TestPtEq(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_exp_test.py b/tests/pytorch_wrapper/pt_exp_test.py index b88551c..51a491e 100644 --- a/tests/pytorch_wrapper/pt_exp_test.py +++ b/tests/pytorch_wrapper/pt_exp_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_exp import PtExp # Updated import to PtExp +from pytorch_wrapper.pt_exp import PtExp class TestPtExp(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_ge_test.py b/tests/pytorch_wrapper/pt_ge_test.py index 92ab581..9165e63 100644 --- a/tests/pytorch_wrapper/pt_ge_test.py +++ b/tests/pytorch_wrapper/pt_ge_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_ge import PtGe # Updated import to PtGe +from pytorch_wrapper.pt_ge import PtGe class TestPtGe(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_gt_test.py b/tests/pytorch_wrapper/pt_gt_test.py index 29603bf..a4fc56a 100644 --- a/tests/pytorch_wrapper/pt_gt_test.py +++ b/tests/pytorch_wrapper/pt_gt_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_gt import PtGt # Updated import to PtGt +from pytorch_wrapper.pt_gt import PtGt class TestPtGt(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_le_test.py b/tests/pytorch_wrapper/pt_le_test.py index 4cb062d..7f32553 100644 --- a/tests/pytorch_wrapper/pt_le_test.py +++ b/tests/pytorch_wrapper/pt_le_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_le import PtLe # Updated import to PtLe +from pytorch_wrapper.pt_le import PtLe class TestPtLe(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_log_test.py b/tests/pytorch_wrapper/pt_log_test.py index a2b644a..186212e 100644 --- a/tests/pytorch_wrapper/pt_log_test.py +++ b/tests/pytorch_wrapper/pt_log_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_log import PtLog # Updated import to PtLog +from pytorch_wrapper.pt_log import PtLog class TestPtLog(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_logical_and_test.py b/tests/pytorch_wrapper/pt_logical_and_test.py index c5227fb..7def74f 100644 --- a/tests/pytorch_wrapper/pt_logical_and_test.py +++ b/tests/pytorch_wrapper/pt_logical_and_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_logical_and import PtLogicalAnd # Updated import to PtLogicalAnd +from pytorch_wrapper.pt_logical_and import PtLogicalAnd class TestPtLogicalAnd(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_logical_not_test.py b/tests/pytorch_wrapper/pt_logical_not_test.py index de18c94..dc3917b 100644 --- a/tests/pytorch_wrapper/pt_logical_not_test.py +++ b/tests/pytorch_wrapper/pt_logical_not_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_logical_not import PtLogicalNot # Updated import to PtLogicalNot +from pytorch_wrapper.pt_logical_not import PtLogicalNot class TestPtLogicalNot(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_logical_or_test.py b/tests/pytorch_wrapper/pt_logical_or_test.py index 97ae602..fd2ba26 100644 --- a/tests/pytorch_wrapper/pt_logical_or_test.py +++ b/tests/pytorch_wrapper/pt_logical_or_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_logical_or import PtLogicalOr # Updated import to PtLogicalOr +from pytorch_wrapper.pt_logical_or import PtLogicalOr class TestPtLogicalOr(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_logical_xor_test.py b/tests/pytorch_wrapper/pt_logical_xor_test.py index 7756330..a93d186 100644 --- a/tests/pytorch_wrapper/pt_logical_xor_test.py +++ b/tests/pytorch_wrapper/pt_logical_xor_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_logical_xor import PtLogicalXor # Updated import to PtLogicalXor +from pytorch_wrapper.pt_logical_xor import PtLogicalXor class TestPtLogicalXor(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_lt_test.py b/tests/pytorch_wrapper/pt_lt_test.py index 027c478..79ddfb0 100644 --- a/tests/pytorch_wrapper/pt_lt_test.py +++ b/tests/pytorch_wrapper/pt_lt_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_lt import PtLt # Updated import to PtLt +from pytorch_wrapper.pt_lt import PtLt class TestPtLt(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_matmul_test.py b/tests/pytorch_wrapper/pt_matmul_test.py index d3f5245..9213716 100644 --- a/tests/pytorch_wrapper/pt_matmul_test.py +++ b/tests/pytorch_wrapper/pt_matmul_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_matmul import PtMatMul # Updated import to PtMatMul +from pytorch_wrapper.pt_matmul import PtMatMul class TestPtMatMul(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_mm_test.py b/tests/pytorch_wrapper/pt_mm_test.py index 33fb78f..9fa9caa 100644 --- a/tests/pytorch_wrapper/pt_mm_test.py +++ b/tests/pytorch_wrapper/pt_mm_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_mm import PtMm # Updated import to PtMm +from pytorch_wrapper.pt_mm import PtMm class TestPtMm(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_mul_test.py b/tests/pytorch_wrapper/pt_mul_test.py index 8bff3f9..22acd75 100644 --- a/tests/pytorch_wrapper/pt_mul_test.py +++ b/tests/pytorch_wrapper/pt_mul_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_mul import PtMul # Updated import to PtMul +from pytorch_wrapper.pt_mul import PtMul class TestPtMul(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_ne_test.py b/tests/pytorch_wrapper/pt_ne_test.py index 4816b21..6cb1cd4 100644 --- a/tests/pytorch_wrapper/pt_ne_test.py +++ b/tests/pytorch_wrapper/pt_ne_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_ne import PtNe # Updated import to PtNe +from pytorch_wrapper.pt_ne import PtNe class TestPtNe(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_neg_test.py b/tests/pytorch_wrapper/pt_neg_test.py index 57c66e1..6845822 100644 --- a/tests/pytorch_wrapper/pt_neg_test.py +++ b/tests/pytorch_wrapper/pt_neg_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_neg import PtNeg # Updated import to PtNeg +from pytorch_wrapper.pt_neg import PtNeg class TestPtNeg(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_rand_int_test.py b/tests/pytorch_wrapper/pt_rand_int_test.py index 57c66e1..6845822 100644 --- a/tests/pytorch_wrapper/pt_rand_int_test.py +++ b/tests/pytorch_wrapper/pt_rand_int_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_neg import PtNeg # Updated import to PtNeg +from pytorch_wrapper.pt_neg import PtNeg class TestPtNeg(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_sin_test.py b/tests/pytorch_wrapper/pt_sin_test.py index 25d0b3f..6f04aff 100644 --- a/tests/pytorch_wrapper/pt_sin_test.py +++ b/tests/pytorch_wrapper/pt_sin_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_sin import PtSin # Updated import to PtSin +from pytorch_wrapper.pt_sin import PtSin class TestPtSin(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_sinh_test.py b/tests/pytorch_wrapper/pt_sinh_test.py index 9e9a360..23db120 100644 --- a/tests/pytorch_wrapper/pt_sinh_test.py +++ b/tests/pytorch_wrapper/pt_sinh_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_sinh import PtSinh # Updated import to PtSinh +from pytorch_wrapper.pt_sinh import PtSinh class TestPtSinh(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_sqrt_test.py b/tests/pytorch_wrapper/pt_sqrt_test.py index 1254d07..11c00c1 100644 --- a/tests/pytorch_wrapper/pt_sqrt_test.py +++ b/tests/pytorch_wrapper/pt_sqrt_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_sqrt import PtSqrt # Updated import to PtSqrt +from pytorch_wrapper.pt_sqrt import PtSqrt class TestPtSqrt(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_tan_test.py b/tests/pytorch_wrapper/pt_tan_test.py index 1346872..1590301 100644 --- a/tests/pytorch_wrapper/pt_tan_test.py +++ b/tests/pytorch_wrapper/pt_tan_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_tan import PtTan # Updated import to PtTan +from pytorch_wrapper.pt_tan import PtTan class TestPtTan(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_tanh_test.py b/tests/pytorch_wrapper/pt_tanh_test.py index e2f0fc8..13d5e68 100644 --- a/tests/pytorch_wrapper/pt_tanh_test.py +++ b/tests/pytorch_wrapper/pt_tanh_test.py @@ -7,7 +7,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pt_tanh import PtTanh # Updated import to PtTanh +from pytorch_wrapper.pt_tanh import PtTanh class TestPtTanh(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pt_train_classification_conv_model_lr_test.py b/tests/pytorch_wrapper/pt_train_classification_conv_model_lr_test.py index 28f6012..6df86d0 100644 --- a/tests/pytorch_wrapper/pt_train_classification_conv_model_lr_test.py +++ b/tests/pytorch_wrapper/pt_train_classification_conv_model_lr_test.py @@ -15,7 +15,7 @@ from pytorch_wrapper.ptv_transforms_to_tensor import PtvTransformsToTensor from pytorch_wrapper.pt_data_loader import PtDataLoader from pytorch_wrapper.pt_train_classification_model_lr import PtTrainClassificationModelLr from pytorch_wrapper.pt_save_model import PtSaveModel -from pytorch_wrapper.pto_lr_scheduler_step import PtoLrSchedulerStep # Updated import +from pytorch_wrapper.pto_lr_scheduler_step import PtoLrSchedulerStep class TestPtTrainClassificationConvModelLr(unittest.TestCase): diff --git a/tests/pytorch_wrapper/ptn_batch_norm2d_test.py b/tests/pytorch_wrapper/ptn_batch_norm2d_test.py index 5618cef..1c38075 100644 --- a/tests/pytorch_wrapper/ptn_batch_norm2d_test.py +++ b/tests/pytorch_wrapper/ptn_batch_norm2d_test.py @@ -29,6 +29,7 @@ class TestPtnBatchNorm2d(unittest.TestCase): mean_after = out.mean(dim=(0, 2, 3)) # Ensure std is not one before normalization + # Reference value: [2.2758, 2.4911, 2.2218] self.assertFalse( torch.allclose( std_before, @@ -36,12 +37,14 @@ class TestPtnBatchNorm2d(unittest.TestCase): atol=1e-1)) # Ensure normalization happened (std should be 1 unless elements are very close) + # Reference value: [1.0190, 1.0190, 1.0190] self.assertTrue( torch.allclose( std_after, torch.ones_like(std_after), atol=1e-1)) - + + # Reference value: [0.0000e+00, 0.0000e+00, 2.2076e-09] self.assertTrue( torch.allclose( mean_after, diff --git a/tests/pytorch_wrapper/ptn_bce_loss_test.py b/tests/pytorch_wrapper/ptn_bce_loss_test.py new file mode 100644 index 0000000..d1cf103 --- /dev/null +++ b/tests/pytorch_wrapper/ptn_bce_loss_test.py @@ -0,0 +1,83 @@ +import os +import sys +import unittest +import torch +from torch import Tensor +from typing import List, Tuple + +PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "..")) +MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") +sys.path.append(MODULE_ROOT) + +from pytorch_wrapper.ptn_bce_loss import PtnBCELoss + + +class TestPtnBCELoss(unittest.TestCase): + def setUp(self) -> None: + """ + Set up the test instance by initializing the loss functions and test cases. + """ + self.node = PtnBCELoss() + self.loss_fun_none = self.node.f("none")[0] + self.loss_fun_mean = self.node.f("mean")[0] + self.loss_fun_sum = self.node.f("sum")[0] + + # Test cases: (predicted probabilities y_hat, ground truth y) + self.test_cases: List[Tuple[Tensor, Tensor]] = [ + ( + torch.tensor([0.1, 0.9], dtype=torch.float32), + torch.tensor([1, 0], dtype=torch.float32), + ) + ] + + def unreduced_loss(self, y_hat: Tensor, y: Tensor) -> Tensor: + """ + Compute the unreduced binary cross-entropy loss. + + Args: + y_hat (Tensor): Predicted probabilities. + y (Tensor): Ground truth binary labels. + + Returns: + Tensor: Unreduced element-wise BCE loss. + """ + return -(y * torch.log(y_hat) + (1 - y) * torch.log(1 - y_hat)) + + def test_none(self) -> None: + """ + Test BCE loss with reduction='none'. + """ + for y_hat, y in self.test_cases: + with self.subTest(y_hat=y_hat): + expected = self.unreduced_loss(y_hat, y) + actual = self.loss_fun_none(y_hat, y) + self.assertTrue(torch.allclose(actual, expected, atol=1e-4), + f"Expected {expected}, got {actual}") + + def test_mean(self) -> None: + """ + Test BCE loss with reduction='mean'. + """ + for y_hat, y in self.test_cases: + with self.subTest(y_hat=y_hat): + loss = self.unreduced_loss(y_hat, y) + expected = loss.mean(dim=0) + actual = self.loss_fun_mean(y_hat, y) + self.assertTrue(torch.equal(actual, expected), + f"Expected {expected}, got {actual}") + + def test_sum(self) -> None: + """ + Test BCE loss with reduction='sum'. + """ + for y_hat, y in self.test_cases: + with self.subTest(y_hat=y_hat): + loss = self.unreduced_loss(y_hat, y) + expected = loss.sum(dim=0) + actual = self.loss_fun_sum(y_hat, y) + self.assertTrue(torch.equal(actual, expected), + f"Expected {expected}, got {actual}") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pytorch_wrapper/ptn_bce_with_logits_loss_test.py b/tests/pytorch_wrapper/ptn_bce_with_logits_loss_test.py new file mode 100644 index 0000000..e81f2b0 --- /dev/null +++ b/tests/pytorch_wrapper/ptn_bce_with_logits_loss_test.py @@ -0,0 +1,84 @@ +import os +import sys +import unittest +import torch +from torch import Tensor +from typing import List, Tuple + +PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "..")) +MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") +sys.path.append(MODULE_ROOT) + +from pytorch_wrapper.ptn_bce_with_logits_loss import PtnBCEWithLogitsLoss + + +class TestPtnBCEWithLogits(unittest.TestCase): + def setUp(self) -> None: + """ + Set up the test instance by initializing the loss functions and test cases. + """ + self.node = PtnBCEWithLogitsLoss() + self.loss_fun_none = self.node.f("none")[0] + self.loss_fun_mean = self.node.f("mean")[0] + self.loss_fun_sum = self.node.f("sum")[0] + + # Probabilities: 0.1, 0.8 → Logits: log(0.1/0.9), log(0.8/0.2) + logits = torch.log(torch.tensor([0.1, 0.8]) / torch.tensor([0.9, 0.2])) + self.test_cases: List[Tuple[Tensor, Tensor]] = [ + ( + logits.to(dtype=torch.float32), + torch.tensor([1, 0], dtype=torch.float32), + ) + ] + + def unreduced_loss(self, logits: Tensor, y: Tensor) -> Tensor: + """ + Compute the unreduced BCEWithLogits loss. + + Args: + logits (Tensor): Raw logits. + y (Tensor): Ground truth binary labels. + + Returns: + Tensor: Unreduced element-wise BCEWithLogits loss. + """ + return torch.nn.functional.binary_cross_entropy_with_logits(logits, y, reduction='none') + + def test_none(self) -> None: + """ + Test BCEWithLogits loss with reduction='none'. + """ + for logits, y in self.test_cases: + with self.subTest(logits=logits): + expected = self.unreduced_loss(logits, y) + actual = self.loss_fun_none(logits, y) + self.assertTrue(torch.allclose(actual, expected, atol=1e-4), + f"Expected {expected}, got {actual}") + + def test_mean(self) -> None: + """ + Test BCEWithLogits loss with reduction='mean'. + """ + for logits, y in self.test_cases: + with self.subTest(logits=logits): + loss = self.unreduced_loss(logits, y) + expected = loss.mean(dim=0) + actual = self.loss_fun_mean(logits, y) + self.assertTrue(torch.equal(actual, expected), + f"Expected {expected}, got {actual}") + + def test_sum(self) -> None: + """ + Test BCEWithLogits loss with reduction='sum'. + """ + for logits, y in self.test_cases: + with self.subTest(logits=logits): + loss = self.unreduced_loss(logits, y) + expected = loss.sum(dim=0) + actual = self.loss_fun_sum(logits, y) + self.assertTrue(torch.equal(actual, expected), + f"Expected {expected}, got {actual}") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pytorch_wrapper/ptn_cross_entropy_loss_test.py b/tests/pytorch_wrapper/ptn_cross_entropy_loss_test.py new file mode 100644 index 0000000..30904b7 --- /dev/null +++ b/tests/pytorch_wrapper/ptn_cross_entropy_loss_test.py @@ -0,0 +1,85 @@ +import os +import sys +import unittest +import torch +from torch import Tensor +from typing import List, Tuple + +PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "..")) +MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") +sys.path.append(MODULE_ROOT) + +from pytorch_wrapper.ptn_cross_entropy_loss import PtnCrossEntropyLoss + + +class TestPtCrossEntropyLoss(unittest.TestCase): + def setUp(self) -> None: + """ + Set up the test instance by initializing the loss functions and test cases. + """ + self.node = PtnCrossEntropyLoss() + self.loss_fun_none = self.node.f("none")[0] + self.loss_fun_mean = self.node.f("mean")[0] + self.loss_fun_sum = self.node.f("sum")[0] + + self.test_cases: List[Tuple[Tensor, Tensor]] = [ + ( + torch.tensor([ + [-2.0, -1.0, 0.0, 1.0, 2.0], + [-40.0, -20.0, 0.0, 10.0, 20.0] + ], dtype=torch.float32), + torch.tensor([0, 2], dtype=torch.int64) + ) + ] + + def unreduced_loss(self, logits: Tensor, targets: Tensor) -> Tensor: + """ + Compute the unreduced cross entropy loss. + + Args: + logits (Tensor): Raw, unnormalized class scores (logits). + targets (Tensor): Ground truth class indices. + + Returns: + Tensor: Unreduced element-wise cross entropy loss. + """ + return torch.nn.functional.cross_entropy(logits, targets, reduction='none') + + def test_none(self) -> None: + """ + Test cross entropy loss with reduction='none'. + """ + for logits, targets in self.test_cases: + with self.subTest(logits=logits): + expected = self.unreduced_loss(logits, targets) + actual = self.loss_fun_none(logits, targets) + self.assertTrue(torch.allclose(actual, expected, atol=1e-4), + f"Expected {expected}, got {actual}") + + def test_mean(self) -> None: + """ + Test cross entropy loss with reduction='mean'. + """ + for logits, targets in self.test_cases: + with self.subTest(logits=logits): + loss = self.unreduced_loss(logits, targets) + expected = loss.mean(dim=0) + actual = self.loss_fun_mean(logits, targets) + self.assertTrue(torch.equal(actual, expected), + f"Expected {expected}, got {actual}") + + def test_sum(self) -> None: + """ + Test cross entropy loss with reduction='sum'. + """ + for logits, targets in self.test_cases: + with self.subTest(logits=logits): + loss = self.unreduced_loss(logits, targets) + expected = loss.sum(dim=0) + actual = self.loss_fun_sum(logits, targets) + self.assertTrue(torch.equal(actual, expected), + f"Expected {expected}, got {actual}") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pytorch_wrapper/ptn_huber_loss.py b/tests/pytorch_wrapper/ptn_huber_loss.py new file mode 100644 index 0000000..147dc8e --- /dev/null +++ b/tests/pytorch_wrapper/ptn_huber_loss.py @@ -0,0 +1,107 @@ +import os +import sys +import unittest +import torch +from torch import Tensor +from typing import List, Tuple + +PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "..")) +MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") +sys.path.append(MODULE_ROOT) + +from pytorch_wrapper.ptn_huber_loss import PtnHuberLoss + + +class TestPtnHuberLoss(unittest.TestCase): + def setUp(self) -> None: + """ + Set up the test instance by initializing the loss functions and test cases. + """ + self.node = PtnHuberLoss() + + self.loss_fun_none_1 = self.node.f("none", 0.01)[0] + self.loss_fun_none_2 = self.node.f("none", 1)[0] + self.loss_fun_none_3 = self.node.f("none", 100)[0] + + self.loss_fun_mean_1 = self.node.f("mean", 0.01)[0] + self.loss_fun_mean_2 = self.node.f("mean", 1)[0] + self.loss_fun_mean_3 = self.node.f("mean", 100)[0] + + self.loss_fun_sum_1 = self.node.f("sum", 0.01)[0] + self.loss_fun_sum_2 = self.node.f("sum", 1)[0] + self.loss_fun_sum_3 = self.node.f("sum", 100)[0] + + # Test cases: (predicted values y_hat, ground truth y) + self.test_cases: List[Tuple[Tensor, Tensor]] = [ + ( + torch.tensor([0.1, -1.0, 10, -100, 1000], dtype=torch.float32), + torch.tensor([0.09, -1.1, 9, -101, 999], dtype=torch.float32), + ) + ] + + def unreduced_loss(self, y_hat: Tensor, y: Tensor, delta: float) -> Tensor: + return torch.nn.functional.huber_loss(y_hat, y, delta=delta, reduction="none") + + def test_none_delta_001(self) -> None: + for y_hat, y in self.test_cases: + expected = self.unreduced_loss(y_hat, y, delta=0.01) + actual = self.loss_fun_none_1(y_hat, y) + self.assertTrue(torch.allclose(actual, expected, atol=1e-6)) + + def test_none_delta_1(self) -> None: + for y_hat, y in self.test_cases: + expected = self.unreduced_loss(y_hat, y, delta=1) + actual = self.loss_fun_none_2(y_hat, y) + self.assertTrue(torch.allclose(actual, expected, atol=1e-6)) + + def test_none_delta_100(self) -> None: + for y_hat, y in self.test_cases: + expected = self.unreduced_loss(y_hat, y, delta=100) + actual = self.loss_fun_none_3(y_hat, y) + self.assertTrue(torch.allclose(actual, expected, atol=1e-6)) + + def test_mean_delta_001(self) -> None: + for y_hat, y in self.test_cases: + loss = self.unreduced_loss(y_hat, y, delta=0.01) + expected = loss.mean(dim=0) + actual = self.loss_fun_mean_1(y_hat, y) + self.assertTrue(torch.equal(actual, expected)) + + def test_mean_delta_1(self) -> None: + for y_hat, y in self.test_cases: + loss = self.unreduced_loss(y_hat, y, delta=1) + expected = loss.mean(dim=0) + actual = self.loss_fun_mean_2(y_hat, y) + self.assertTrue(torch.equal(actual, expected)) + + def test_mean_delta_100(self) -> None: + for y_hat, y in self.test_cases: + loss = self.unreduced_loss(y_hat, y, delta=100) + expected = loss.mean(dim=0) + actual = self.loss_fun_mean_3(y_hat, y) + self.assertTrue(torch.equal(actual, expected)) + + def test_sum_delta_001(self) -> None: + for y_hat, y in self.test_cases: + loss = self.unreduced_loss(y_hat, y, delta=0.01) + expected = loss.sum(dim=0) + actual = self.loss_fun_sum_1(y_hat, y) + self.assertTrue(torch.equal(actual, expected)) + + def test_sum_delta_1(self) -> None: + for y_hat, y in self.test_cases: + loss = self.unreduced_loss(y_hat, y, delta=1) + expected = loss.sum(dim=0) + actual = self.loss_fun_sum_2(y_hat, y) + self.assertTrue(torch.equal(actual, expected)) + + def test_sum_delta_100(self) -> None: + for y_hat, y in self.test_cases: + loss = self.unreduced_loss(y_hat, y, delta=100) + expected = loss.sum(dim=0) + actual = self.loss_fun_sum_3(y_hat, y) + self.assertTrue(torch.equal(actual, expected)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pytorch_wrapper/ptn_kl_div_loss_test.py b/tests/pytorch_wrapper/ptn_kl_div_loss_test.py new file mode 100644 index 0000000..2b3e5d5 --- /dev/null +++ b/tests/pytorch_wrapper/ptn_kl_div_loss_test.py @@ -0,0 +1,130 @@ +import os +import sys +import unittest +import torch +from torch import Tensor +from typing import List, Tuple + +PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "..")) +MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") +sys.path.append(MODULE_ROOT) + +from pytorch_wrapper.ptn_kl_div_loss import PtnKLDivLoss + + +class TestPtnKLDivLoss(unittest.TestCase): + def setUp(self) -> None: + """ + Set up the test instance by initializing the loss functions and test cases. + """ + self.node = PtnKLDivLoss() + self.loss_fun_none = self.node.f("none", log_target=False)[0] + self.loss_fun_batchmean = self.node.f("batchmean", log_target=False)[0] + self.loss_fun_mean = self.node.f("mean", log_target=False)[0] + self.loss_fun_sum = self.node.f("sum", log_target=False)[0] + + self.loss_fun_none_log = self.node.f("none", log_target=True)[0] + self.loss_fun_batchmean_log = self.node.f("batchmean", log_target=True)[0] + self.loss_fun_mean_log = self.node.f("mean", log_target=True)[0] + self.loss_fun_sum_log = self.node.f("sum", log_target=True)[0] + + # log_probs: log_softmax of logits + self.test_cases: List[Tuple[Tensor, Tensor]] = [ + ( + # input (log-probabilities) + torch.log_softmax( + torch.tensor([ + [-2.0, -1.0, 0.0, 1.0, 2.0], + [-40.0, -20.0, 0.0, 10.0, 20.0] + ], dtype=torch.float32), dim=-1 + ), + # target (probabilities for log_target=False) + torch.tensor([ + [0.04, 0.06, 0.2, 0.3, 0.4], + [0.4, 0.3, 0.2, 0.06, 0.04] + ], dtype=torch.float32) + ) + ] + + self.log_target_cases: List[Tuple[Tensor, Tensor]] = [ + ( + # input + torch.log_softmax( + torch.tensor([ + [-2.0, -1.0, 0.0, 1.0, 2.0], + [-40.0, -20.0, 0.0, 10.0, 20.0] + ], dtype=torch.float32), dim=-1 + ), + # target (log-probabilities) + torch.log_softmax( + torch.tensor([ + [0.04, 0.06, 0.2, 0.3, 0.4], + [0.4, 0.3, 0.2, 0.06, 0.04] + ], dtype=torch.float32), dim=-1 + ) + ) + ] + + def unreduced_loss(self, log_probs: Tensor, probs: Tensor) -> Tensor: + return torch.nn.functional.kl_div(log_probs, probs, reduction='none', log_target=False) + + def batchmean_loss(self, log_probs: Tensor, probs: Tensor) -> Tensor: + return torch.nn.functional.kl_div(log_probs, probs, reduction='batchmean', log_target=False) + + def unreduced_loss_log_target(self, log_probs: Tensor, log_targets: Tensor) -> Tensor: + return torch.nn.functional.kl_div(log_probs, log_targets, reduction='none', log_target=True) + + def batchmean_loss_log_target(self, log_probs: Tensor, log_targets: Tensor) -> Tensor: + return torch.nn.functional.kl_div(log_probs, log_targets, reduction='batchmean', log_target=True) + + def test_none(self) -> None: + for log_probs, probs in self.test_cases: + expected = self.unreduced_loss(log_probs, probs) + actual = self.loss_fun_none(log_probs, probs) + self.assertTrue(torch.allclose(actual, expected, atol=1e-4)) + + def test_mean(self) -> None: + for log_probs, probs in self.test_cases: + expected = self.unreduced_loss(log_probs, probs).mean() + actual = self.loss_fun_mean(log_probs, probs) + self.assertTrue(torch.allclose(actual, expected, atol=1e-6)) + + def test_batchmean(self) -> None: + for log_probs, probs in self.test_cases: + expected = self.batchmean_loss(log_probs, probs) + actual = self.loss_fun_batchmean(log_probs, probs) + self.assertTrue(torch.allclose(actual, expected, atol=1e-6)) + + def test_sum(self) -> None: + for log_probs, probs in self.test_cases: + expected = self.unreduced_loss(log_probs, probs).sum() + actual = self.loss_fun_sum(log_probs, probs) + self.assertTrue(torch.allclose(actual, expected, atol=1e-6)) + + def test_none_log_target(self) -> None: + for log_probs, log_targets in self.log_target_cases: + expected = self.unreduced_loss_log_target(log_probs, log_targets) + actual = self.loss_fun_none_log(log_probs, log_targets) + self.assertTrue(torch.allclose(actual, expected, atol=1e-4)) + + def test_mean_log_target(self) -> None: + for log_probs, log_targets in self.log_target_cases: + expected = self.unreduced_loss_log_target(log_probs, log_targets).mean() + actual = self.loss_fun_mean_log(log_probs, log_targets) + self.assertTrue(torch.allclose(actual, expected, atol=1e-6)) + + def test_batchmean_log_target(self) -> None: + for log_probs, log_targets in self.log_target_cases: + expected = self.batchmean_loss_log_target(log_probs, log_targets) + actual = self.loss_fun_batchmean_log(log_probs, log_targets) + self.assertTrue(torch.allclose(actual, expected, atol=1e-6)) + + def test_sum_log_target(self) -> None: + for log_probs, log_targets in self.log_target_cases: + expected = self.unreduced_loss_log_target(log_probs, log_targets).sum() + actual = self.loss_fun_sum_log(log_probs, log_targets) + self.assertTrue(torch.allclose(actual, expected, atol=1e-6)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pytorch_wrapper/ptn_l1_loss_test.py b/tests/pytorch_wrapper/ptn_l1_loss_test.py new file mode 100644 index 0000000..9a0de85 --- /dev/null +++ b/tests/pytorch_wrapper/ptn_l1_loss_test.py @@ -0,0 +1,83 @@ +import os +import sys +import unittest +import torch +from torch import Tensor +from typing import List, Tuple + +PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "..")) +MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") +sys.path.append(MODULE_ROOT) + +from pytorch_wrapper.ptn_l1_loss import PtnL1Loss + + +class TestPtnL1Loss(unittest.TestCase): + def setUp(self) -> None: + """ + Set up the test instance by initializing the loss functions and test cases. + """ + self.node = PtnL1Loss() + self.loss_fun_none = self.node.f("none")[0] + self.loss_fun_mean = self.node.f("mean")[0] + self.loss_fun_sum = self.node.f("sum")[0] + + # Test cases: (predicted values y_hat, ground truth y) + self.test_cases: List[Tuple[Tensor, Tensor]] = [ + ( + torch.tensor([0.1, -1.0, 10, -100, 1000], dtype=torch.float32), + torch.tensor([0.09, -1.1, 9, -101, 999], dtype=torch.float32), + ) + ] + + def unreduced_loss(self, y_hat: Tensor, y: Tensor) -> Tensor: + """ + Compute the unreduced L1 loss. + + Args: + y_hat (Tensor): Predicted values. + y (Tensor): Ground truth values. + + Returns: + Tensor: Unreduced element-wise L1 loss. + """ + return torch.abs(y_hat - y) + + def test_none(self) -> None: + """ + Test L1 loss with reduction='none'. + """ + for y_hat, y in self.test_cases: + with self.subTest(y_hat=y_hat): + expected = self.unreduced_loss(y_hat, y) + actual = self.loss_fun_none(y_hat, y) + self.assertTrue(torch.allclose(actual, expected, atol=1e-6), + f"Expected {expected}, got {actual}") + + def test_mean(self) -> None: + """ + Test L1 loss with reduction='mean'. + """ + for y_hat, y in self.test_cases: + with self.subTest(y_hat=y_hat): + loss = self.unreduced_loss(y_hat, y) + expected = loss.mean(dim=0) + actual = self.loss_fun_mean(y_hat, y) + self.assertTrue(torch.equal(actual, expected), + f"Expected {expected}, got {actual}") + + def test_sum(self) -> None: + """ + Test L1 loss with reduction='sum'. + """ + for y_hat, y in self.test_cases: + with self.subTest(y_hat=y_hat): + loss = self.unreduced_loss(y_hat, y) + expected = loss.sum(dim=0) + actual = self.loss_fun_sum(y_hat, y) + self.assertTrue(torch.equal(actual, expected), + f"Expected {expected}, got {actual}") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pytorch_wrapper/ptn_mse_loss_test.py b/tests/pytorch_wrapper/ptn_mse_loss_test.py new file mode 100644 index 0000000..fead392 --- /dev/null +++ b/tests/pytorch_wrapper/ptn_mse_loss_test.py @@ -0,0 +1,83 @@ +import os +import sys +import unittest +import torch +from torch import Tensor +from typing import List, Tuple + +PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "..")) +MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") +sys.path.append(MODULE_ROOT) + +from pytorch_wrapper.ptn_mse_loss import PtnMSELoss + + +class TestPtnMSELoss(unittest.TestCase): + def setUp(self) -> None: + """ + Set up the test instance by initializing the loss functions and test cases. + """ + self.node = PtnMSELoss() + self.loss_fun_none = self.node.f("none")[0] + self.loss_fun_mean = self.node.f("mean")[0] + self.loss_fun_sum = self.node.f("sum")[0] + + # Test cases: (predicted values y_hat, ground truth y) + self.test_cases: List[Tuple[Tensor, Tensor]] = [ + ( + torch.tensor([0.1, -1.0, 10, -100, 1000], dtype=torch.float32), + torch.tensor([0.09, -1.1, 9, -101, 999], dtype=torch.float32), + ) + ] + + def unreduced_loss(self, y_hat: Tensor, y: Tensor) -> Tensor: + """ + Compute the unreduced mean squared error loss. + + Args: + y_hat (Tensor): Predicted values. + y (Tensor): Ground truth values. + + Returns: + Tensor: Unreduced element-wise MSE loss. + """ + return (y_hat - y) ** 2 + + def test_none(self) -> None: + """ + Test MSE loss with reduction='none'. + """ + for y_hat, y in self.test_cases: + with self.subTest(y_hat=y_hat): + expected = self.unreduced_loss(y_hat, y) + actual = self.loss_fun_none(y_hat, y) + self.assertTrue(torch.allclose(actual, expected, atol=1e-6), + f"Expected {expected}, got {actual}") + + def test_mean(self) -> None: + """ + Test MSE loss with reduction='mean'. + """ + for y_hat, y in self.test_cases: + with self.subTest(y_hat=y_hat): + loss = self.unreduced_loss(y_hat, y) + expected = loss.mean(dim=0) + actual = self.loss_fun_mean(y_hat, y) + self.assertTrue(torch.equal(actual, expected), + f"Expected {expected}, got {actual}") + + def test_sum(self) -> None: + """ + Test MSE loss with reduction='sum'. + """ + for y_hat, y in self.test_cases: + with self.subTest(y_hat=y_hat): + loss = self.unreduced_loss(y_hat, y) + expected = loss.sum(dim=0) + actual = self.loss_fun_sum(y_hat, y) + self.assertTrue(torch.equal(actual, expected), + f"Expected {expected}, got {actual}") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pytorch_wrapper/ptn_nll_loss_test.py b/tests/pytorch_wrapper/ptn_nll_loss_test.py new file mode 100644 index 0000000..e62a3c8 --- /dev/null +++ b/tests/pytorch_wrapper/ptn_nll_loss_test.py @@ -0,0 +1,89 @@ +import os +import sys +import unittest +import torch +from torch import Tensor +from typing import List, Tuple + +PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "..")) +MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") +sys.path.append(MODULE_ROOT) + +from pytorch_wrapper.ptn_nll_loss import PtnNLLLoss + + +class TestPtnNLLLoss(unittest.TestCase): + def setUp(self) -> None: + """ + Set up the test instance by initializing the loss functions and test cases. + """ + self.node = PtnNLLLoss() + self.loss_fun_none = self.node.f("none")[0] + self.loss_fun_mean = self.node.f("mean")[0] + self.loss_fun_sum = self.node.f("sum")[0] + + logits = torch.tensor([ + [-2.0, -1.0, 0.0, 1.0, 2.0], + [-40.0, -20.0, 0.0, 10.0, 20.0] + ], dtype=torch.float32) + + log_probs = torch.nn.functional.log_softmax(logits, dim=1) + + self.test_cases: List[Tuple[Tensor, Tensor]] = [ + ( + log_probs, + torch.tensor([0, 2], dtype=torch.int64) + ) + ] + + def unreduced_loss(self, log_probs: Tensor, targets: Tensor) -> Tensor: + """ + Compute the unreduced negative log-likelihood loss. + + Args: + log_probs (Tensor): Log-probabilities from log_softmax. + targets (Tensor): Ground truth class indices. + + Returns: + Tensor: Unreduced element-wise NLL loss. + """ + return torch.nn.functional.nll_loss(log_probs, targets, reduction='none') + + def test_none(self) -> None: + """ + Test NLL loss with reduction='none'. + """ + for log_probs, targets in self.test_cases: + with self.subTest(log_probs=log_probs): + expected = self.unreduced_loss(log_probs, targets) + actual = self.loss_fun_none(log_probs, targets) + self.assertTrue(torch.allclose(actual, expected, atol=1e-4), + f"Expected {expected}, got {actual}") + + def test_mean(self) -> None: + """ + Test NLL loss with reduction='mean'. + """ + for log_probs, targets in self.test_cases: + with self.subTest(log_probs=log_probs): + loss = self.unreduced_loss(log_probs, targets) + expected = loss.mean(dim=0) + actual = self.loss_fun_mean(log_probs, targets) + self.assertTrue(torch.equal(actual, expected), + f"Expected {expected}, got {actual}") + + def test_sum(self) -> None: + """ + Test NLL loss with reduction='sum'. + """ + for log_probs, targets in self.test_cases: + with self.subTest(log_probs=log_probs): + loss = self.unreduced_loss(log_probs, targets) + expected = loss.sum(dim=0) + actual = self.loss_fun_sum(log_probs, targets) + self.assertTrue(torch.equal(actual, expected), + f"Expected {expected}, got {actual}") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pytorch_wrapper/ptn_smooth_l1_loss_test.py b/tests/pytorch_wrapper/ptn_smooth_l1_loss_test.py new file mode 100644 index 0000000..45ad84b --- /dev/null +++ b/tests/pytorch_wrapper/ptn_smooth_l1_loss_test.py @@ -0,0 +1,116 @@ +import os +import sys +import unittest +import torch +from torch import Tensor +from typing import List, Tuple + +PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "..")) +MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") +sys.path.append(MODULE_ROOT) + +from pytorch_wrapper.ptn_smooth_l1_loss import PtnSmoothL1Loss + + +class TestPtnSmoothL1Loss(unittest.TestCase): + def setUp(self) -> None: + """ + Set up the test instance by initializing the loss functions and test cases. + """ + self.node = PtnSmoothL1Loss() + + self.loss_fun_none_1 = self.node.f("none", 0.01)[0] + self.loss_fun_none_2 = self.node.f("none", 1)[0] + self.loss_fun_none_3 = self.node.f("none", 100)[0] + + self.loss_fun_mean_1 = self.node.f("mean", 0.01)[0] + self.loss_fun_mean_2 = self.node.f("mean", 1)[0] + self.loss_fun_mean_3 = self.node.f("mean", 100)[0] + + self.loss_fun_sum_1 = self.node.f("sum", 0.01)[0] + self.loss_fun_sum_2 = self.node.f("sum", 1)[0] + self.loss_fun_sum_3 = self.node.f("sum", 100)[0] + + # Test cases: (predicted values y_hat, ground truth y) + self.test_cases: List[Tuple[Tensor, Tensor]] = [ + ( + torch.tensor([0.1, -1.0, 10, -100, 1000], dtype=torch.float32), + torch.tensor([0.09, -1.1, 9, -101, 999], dtype=torch.float32), + ) + ] + + def unreduced_loss(self, y_hat: Tensor, y: Tensor, beta: float) -> Tensor: + return torch.nn.functional.smooth_l1_loss(y_hat, y, beta=beta, reduction="none") + + def test_none_beta_001(self) -> None: + for y_hat, y in self.test_cases: + expected = self.unreduced_loss(y_hat, y, beta=0.01) + actual = self.loss_fun_none_1(y_hat, y) + self.assertTrue(torch.allclose(actual, expected, atol=1e-6), + f"Expected {expected}, got {actual}") + + def test_none_beta_1(self) -> None: + for y_hat, y in self.test_cases: + expected = self.unreduced_loss(y_hat, y, beta=1) + actual = self.loss_fun_none_2(y_hat, y) + self.assertTrue(torch.allclose(actual, expected, atol=1e-6), + f"Expected {expected}, got {actual}") + + def test_none_beta_100(self) -> None: + for y_hat, y in self.test_cases: + expected = self.unreduced_loss(y_hat, y, beta=100) + actual = self.loss_fun_none_3(y_hat, y) + self.assertTrue(torch.allclose(actual, expected, atol=1e-6), + f"Expected {expected}, got {actual}") + + def test_mean_beta_001(self) -> None: + for y_hat, y in self.test_cases: + loss = self.unreduced_loss(y_hat, y, beta=0.01) + expected = loss.mean(dim=0) + actual = self.loss_fun_mean_1(y_hat, y) + self.assertTrue(torch.equal(actual, expected), + f"Expected {expected}, got {actual}") + + def test_mean_beta_1(self) -> None: + for y_hat, y in self.test_cases: + loss = self.unreduced_loss(y_hat, y, beta=1) + expected = loss.mean(dim=0) + actual = self.loss_fun_mean_2(y_hat, y) + self.assertTrue(torch.equal(actual, expected), + f"Expected {expected}, got {actual}") + + def test_mean_beta_100(self) -> None: + for y_hat, y in self.test_cases: + loss = self.unreduced_loss(y_hat, y, beta=100) + expected = loss.mean(dim=0) + actual = self.loss_fun_mean_3(y_hat, y) + self.assertTrue(torch.equal(actual, expected), + f"Expected {expected}, got {actual}") + + def test_sum_beta_001(self) -> None: + for y_hat, y in self.test_cases: + loss = self.unreduced_loss(y_hat, y, beta=0.01) + expected = loss.sum(dim=0) + actual = self.loss_fun_sum_1(y_hat, y) + self.assertTrue(torch.equal(actual, expected), + f"Expected {expected}, got {actual}") + + def test_sum_beta_1(self) -> None: + for y_hat, y in self.test_cases: + loss = self.unreduced_loss(y_hat, y, beta=1) + expected = loss.sum(dim=0) + actual = self.loss_fun_sum_2(y_hat, y) + self.assertTrue(torch.equal(actual, expected), + f"Expected {expected}, got {actual}") + + def test_sum_beta_100(self) -> None: + for y_hat, y in self.test_cases: + loss = self.unreduced_loss(y_hat, y, beta=100) + expected = loss.sum(dim=0) + actual = self.loss_fun_sum_3(y_hat, y) + self.assertTrue(torch.equal(actual, expected), + f"Expected {expected}, got {actual}") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pytorch_wrapper/pto_lr_scheduler_reduce_on_plateau_test.py b/tests/pytorch_wrapper/pto_lr_scheduler_reduce_on_plateau_test.py index 2a4ed34..ddeefdd 100644 --- a/tests/pytorch_wrapper/pto_lr_scheduler_reduce_on_plateau_test.py +++ b/tests/pytorch_wrapper/pto_lr_scheduler_reduce_on_plateau_test.py @@ -8,7 +8,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pto_lr_scheduler_reduce_on_plateau import PtoLrSchedulerReduceOnPlateau # Updated import +from pytorch_wrapper.pto_lr_scheduler_reduce_on_plateau import PtoLrSchedulerReduceOnPlateau class TestPtoLrSchedulerReduceOnPlateau(unittest.TestCase): diff --git a/tests/pytorch_wrapper/pto_lr_scheduler_step_test.py b/tests/pytorch_wrapper/pto_lr_scheduler_step_test.py index 56ee104..c63bde7 100644 --- a/tests/pytorch_wrapper/pto_lr_scheduler_step_test.py +++ b/tests/pytorch_wrapper/pto_lr_scheduler_step_test.py @@ -8,7 +8,7 @@ PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ". MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") sys.path.append(MODULE_ROOT) -from pytorch_wrapper.pto_lr_scheduler_step import PtoLrSchedulerStep # Updated import +from pytorch_wrapper.pto_lr_scheduler_step import PtoLrSchedulerStep class TestPtoLrSchedulerStep(unittest.TestCase):