Added conv model for pytorch training.
This commit is contained in:
@@ -1,24 +1,42 @@
|
||||
# ComfyUI-Data-Analysis
|
||||
## Update (March 3, 2025)
|
||||
## Update (March 5, 2025)
|
||||
Training workflow trains the FashionMNIST model from scratch with a single click of a button.
|
||||
|
||||
Experimental PyTorch model training has been added (documentation coming soon).
|
||||
No coding required—training is as simple as a single click!
|
||||
|
||||
Try it out:
|
||||
It is designed to be modular. For example, to switch from a linear model to a conv model, you can just replace the model node (see screenshot below).
|
||||
|
||||
Training: examples/fashion_mnist_train.json
|
||||
Evaluation: examples/fashion_mnist_eval.json
|
||||
Or you can just drag and drop below workflows to ComfyUI to check it out. Model is downloaded automatically:
|
||||
|
||||
This workflow trains a FashionMNIST model from scratch effortlessly.
|
||||
Linear model
|
||||
* Training: examples/fashion_mnist_train.json
|
||||
* Evaluation: examples/fashion_mnist_eval.json
|
||||
|
||||
**Training workflow**
|
||||
Convolutional model
|
||||
* Training: examples/fashion_mnist_train_conv.json
|
||||
* Evaluation: examples/fashion_mnist_eval_conv.json
|
||||
|
||||
These training workflows train a FashionMNIST model from scratch effortlessly.
|
||||
|
||||
### Training workflow
|
||||
**Linear model**
|
||||

|
||||
|
||||
**Eval workflow**
|
||||
**Conv model**
|
||||

|
||||
|
||||
*Close up of the conv node*
|
||||

|
||||
|
||||
### Eval workflow
|
||||
**Linear model**
|
||||

|
||||
|
||||
Note that the training feature is a proof of concept and currently supports only the dense model. Additional models may be added based on demand.
|
||||
**Conv model**
|
||||

|
||||
|
||||
Note that the training feature is a proof of concept and currently supports only the linear and conv models. Additional models may be added based on demand.
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
@@ -214,6 +214,7 @@ from .modules.pytorch_wrapper.pt_var import PtVar
|
||||
from .modules.pytorch_wrapper.pt_view import PtView
|
||||
from .modules.pytorch_wrapper.pt_where import PtWhere
|
||||
from .modules.pytorch_wrapper.pt_zeros import PtZeros
|
||||
from .modules.pytorch_wrapper.ptn_conv_model import PtnConvModel
|
||||
from .modules.pytorch_wrapper.ptn_linear_model import PtnLinearModel
|
||||
from .modules.pytorch_wrapper.pto_adam import PtoAdam
|
||||
from .modules.pytorch_wrapper.ptv_dataset import PtvDataset
|
||||
@@ -446,6 +447,7 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = {
|
||||
"PtView": PtView,
|
||||
"PtWhere": PtWhere,
|
||||
"PtZeros": PtZeros,
|
||||
"PtnConvModel": PtnConvModel,
|
||||
"PtnLinearModel": PtnLinearModel,
|
||||
"PtoAdam": PtoAdam,
|
||||
"PtvDataset": PtvDataset,
|
||||
@@ -681,6 +683,7 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = {
|
||||
"PtView": "Pt View",
|
||||
"PtWhere": "Pt Where",
|
||||
"PtZeros": "Pt Zeros",
|
||||
"PtnConvModel": "Ptn Conv Model",
|
||||
"PtnLinearModel": "Ptn Linear Model",
|
||||
"PtoAdam": "Pto Adam",
|
||||
"PtvDataset": "Ptv Dataset",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 177 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 194 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
@@ -122,10 +122,6 @@
|
||||
| [Pandas Log](pandas_log.md) | Applying the natural logarithm function to a pandas DataFrame and converting non-numeric values to NaN. |
|
||||
| [Pandas Sin](pandas_sin.md) | Applies the sine function to a pandas DataFrame and converting non-numeric values to NaN. |
|
||||
| [Pandas Tan](pandas_tan.md) | Applies the tangent function to a pandas DataFrame and converting non-numeric values to NaN. |
|
||||
## Miscellaneous
|
||||
| Node | Description |
|
||||
| --- | --- |
|
||||
| [Pt Flatten](pt_flatten.md) | Flattens a PyTorch tensor into a 1D tensor. |
|
||||
## Numpy
|
||||
| Node | Description |
|
||||
| --- | --- |
|
||||
@@ -266,6 +262,7 @@
|
||||
| [Pt Evaluate Classification Model](pt_evaluate_classification_model.md) | Performs inference on test data and computes evaluation metrics. |
|
||||
| [Pt Load Model](pt_load_model.md) | A wrapper class for saving a PyTorch model. |
|
||||
| [Pt Train Classification Model](pt_train_classification_model.md) | Trains a classification model using a given dataset, optimizer, and number of epochs. |
|
||||
| [Ptn Conv Model](ptn_conv_model.md) | A convolutional model consisting of multiple convolutional layers. |
|
||||
| [Ptn Linear Model](ptn_linear_model.md) | A linear model consisting of dense layers. |
|
||||
| [Ptv Dataset](ptv_dataset.md) | A Torchvision Dataset class wrapper. |
|
||||
| [Ptv Dataset Loader](ptv_dataset_loader.md) | A node to combine the dataset and data loader into a single node. |
|
||||
@@ -273,6 +270,7 @@
|
||||
## PyTorch wrapper - Transform
|
||||
| Node | Description |
|
||||
| --- | --- |
|
||||
| [Pt Flatten](pt_flatten.md) | Flattens a PyTorch tensor into a 1D tensor. |
|
||||
| [Pt Permute](pt_permute.md) | Permutes the dimensions of a PyTorch tensor according to the specified order. |
|
||||
| [Pt Reshape](pt_reshape.md) | Reshapes a PyTorch tensor into a specified shape using `torch.reshape()`. |
|
||||
| [Pt Squeeze](pt_squeeze.md) | Removes a dimension at the specified position in the input tensor if it is of size 1. |
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# Pt Flatten
|
||||
Flattens a PyTorch tensor into a 1D tensor.
|
||||
|
||||
Category: PyTorch wrapper
|
||||
|
||||
## Input
|
||||
| Name | Data type |
|
||||
|---|---|
|
||||
@@ -14,6 +12,6 @@ Category: PyTorch wrapper
|
||||
| Tensor |
|
||||
|
||||
<HR>
|
||||
Category: Miscellaneous
|
||||
Category: PyTorch wrapper - Transform
|
||||
|
||||
ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# Ptn Conv Model
|
||||
A convolutional model consisting of multiple convolutional layers.
|
||||
|
||||
### Fields:
|
||||
- `input_dim`: A string representing the input dimensions in the format "(C,H,W)".
|
||||
Example: `"(3,28,28)"` for a 3-channel 28x28 image.
|
||||
|
||||
- `penultimate_dim`: An integer specifying the number of features before the output layer. If you specify 0, the number is computed internally.
|
||||
Default: `0`
|
||||
|
||||
- `output_dim`: An integer specifying the number of output features, which should match the number of target classes in classification.
|
||||
Default: `10`, Min: `1`, Max: `1e6`.
|
||||
|
||||
- `channel_list`: A string representing a Python list of integers specifying the number of channels per layer excluding the channel for the input (e.g. 3 for the color image).
|
||||
Example: `"[32,64,128,256,512]"`.
|
||||
|
||||
- `kernel_size_list`: A string representing a Python list of kernel sizes per layer.
|
||||
Example: `"[3,3,3,3,1]"`.
|
||||
|
||||
- `padding_list`: A string representing a Python list of padding values per layer.
|
||||
Example: `"[1,1,1,1,0]"`.
|
||||
|
||||
- `downsample_list`: A string representing a Python list of boolean values indicating whether to downsample at each layer.
|
||||
Example: `"[True,True,True,True,False]"`.
|
||||
|
||||
## Input
|
||||
| Name | Data type |
|
||||
|---|---|
|
||||
| input_dim | String |
|
||||
| penultimate_dim | Int |
|
||||
| output_dim | Int |
|
||||
| channel_list | String |
|
||||
| kernel_size_list | String |
|
||||
| padding_list | String |
|
||||
| downsample_list | String |
|
||||
|
||||
## Output
|
||||
| Data type |
|
||||
|---|
|
||||
| Ptmodel |
|
||||
|
||||
<HR>
|
||||
Category: PyTorch wrapper - Training
|
||||
|
||||
ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Conv Single Layer
|
||||
A single convolutional layer followed by an optional ReLU activation and optional downsampling.
|
||||
|
||||
Shape is tensor of shape `(batch_size, in_channels, height, width)`.
|
||||
|
||||
Args:
|
||||
in_channels (int): Number of input channels.
|
||||
out_channels (int): Number of output channels.
|
||||
kernel_size (int): Size of the convolutional kernel.
|
||||
padding (int): Padding size for convolution.
|
||||
downsample (bool): If True, applies a 2x2 MaxPooling layer.
|
||||
apply_activation (bool, optional): If True, applies ReLU activation. Defaults to True.
|
||||
|
||||
<HR>
|
||||
Category: Miscellaneous
|
||||
|
||||
ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
|
||||
@@ -2,7 +2,7 @@
|
||||
A linear model consisting of dense layers.
|
||||
|
||||
### Fields:
|
||||
- `dim_list`: A comma-separated string representing layer dimensions.
|
||||
- `dim_list`: A string representing a Python list of layer dimensions.
|
||||
It should contain the input dimension for the first layer and the output dimensions
|
||||
for all layers.
|
||||
|
||||
@@ -10,9 +10,10 @@ A linear model consisting of dense layers.
|
||||
- Input data: 784
|
||||
- Layer 1: 784 → 128
|
||||
- Layer 2: 128 → 10
|
||||
- Specify `"784, 128, 10"`
|
||||
- Specify `"[784,128,10]"`
|
||||
|
||||
- `bias_list`: A comma-separated string representing bias usage (True/False) per layer.
|
||||
- `bias_list`: A string representing a Python list of bias usage (True/False) per layer.
|
||||
Example: "[True,False]"
|
||||
|
||||
- `num_layers`: The number of layers in the model.
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"last_node_id":48,"last_link_id":62,"nodes":[{"id":47,"type":"PtEvaluateClassificationModel","pos":[3243.2138671875,1887.3746337890625],"size":[403.1999816894531,138],"flags":{},"order":3,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":55},{"name":"data_loader","type":"PTDATALOADER","link":56}],"outputs":[{"name":"accuracy","type":"FLOAT","links":[57],"slot_index":0},{"name":"precision","type":"FLOAT","links":[58],"slot_index":1},{"name":"recall","type":"FLOAT","links":[59],"slot_index":2},{"name":"f1","type":"FLOAT","links":[60],"slot_index":3},{"name":"num_samples","type":"INT","links":[61],"slot_index":4}],"properties":{"Node name for S&R":"PtEvaluateClassificationModel"},"widgets_values":[true]},{"id":33,"type":"CDAShowInt","pos":[3716.05078125,2073.464111328125],"size":[210,79.09933471679688],"flags":{},"order":8,"mode":0,"inputs":[{"name":"int_scalar","type":"INT","link":61,"widget":{"name":"int_scalar"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"title":"Num Samples","properties":{"Node name for S&R":"CDAShowInt"},"widgets_values":[0,"10000"]},{"id":31,"type":"CDAShowFloat","pos":[3937.552490234375,1932.3660888671875],"size":[210,76],"flags":{},"order":7,"mode":0,"inputs":[{"name":"float_scalar","type":"FLOAT","link":60,"widget":{"name":"float_scalar"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"title":"F1","properties":{"Node name for S&R":"CDAShowFloat"},"widgets_values":[0,"0.9085886169153918"]},{"id":29,"type":"CDAShowFloat","pos":[3937.552490234375,1808.5174560546875],"size":[210,76],"flags":{},"order":5,"mode":0,"inputs":[{"name":"float_scalar","type":"FLOAT","link":58,"widget":{"name":"float_scalar"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"title":"Precision","properties":{"Node name for S&R":"CDAShowFloat"},"widgets_values":[0,"0.9113484312788449"]},{"id":30,"type":"CDAShowFloat","pos":[3716.05078125,1932.3660888671875],"size":[210,76],"flags":{},"order":6,"mode":0,"inputs":[{"name":"float_scalar","type":"FLOAT","link":59,"widget":{"name":"float_scalar"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"title":"Recall","properties":{"Node name for S&R":"CDAShowFloat"},"widgets_values":[0,"0.908"]},{"id":28,"type":"CDAShowFloat","pos":[3716.05078125,1808.5174560546875],"size":[210,76],"flags":{},"order":4,"mode":0,"inputs":[{"name":"float_scalar","type":"FLOAT","link":57,"widget":{"name":"float_scalar"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"title":"Accuracy","properties":{"Node name for S&R":"CDAShowFloat"},"widgets_values":[0,"0.908"]},{"id":46,"type":"PtvDatasetLoader","pos":[2884.05078125,1908.8636474609375],"size":[266.8697814941406,246],"flags":{},"order":0,"mode":0,"inputs":[],"outputs":[{"name":"PTDATALOADER","type":"PTDATALOADER","links":[56],"slot_index":0}],"properties":{"Node name for S&R":"PtvDatasetLoader"},"widgets_values":["FashionMNIST",true,"",128,false,"{\"train\":False}","{\"num_workers\":1}"]},{"id":48,"type":"PtnConvModel","pos":[2846.147216796875,1629.3045654296875],"size":[315,202],"flags":{},"order":1,"mode":0,"inputs":[],"outputs":[{"name":"PTMODEL","type":"PTMODEL","links":[62],"slot_index":0}],"properties":{"Node name for S&R":"PtnConvModel"},"widgets_values":["(1,28,28)",0,10,"[32,64,128,256,512]","[3,3,3,3,1]","[1,1,1,1,0]","[True,True,True,True,False]"]},{"id":23,"type":"PtLoadModel","pos":[3243.927978515625,1718.1610107421875],"size":[397.5032653808594,67.37537384033203],"flags":{},"order":2,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":62}],"outputs":[{"name":"model","type":"PTMODEL","links":[55],"slot_index":0}],"properties":{"Node name for S&R":"PtLoadModel"},"widgets_values":["fashion_mnist_conv_ep5.pt"]}],"links":[[55,23,0,47,0,"PTMODEL"],[56,46,0,47,1,"PTDATALOADER"],[57,47,0,28,0,"FLOAT"],[58,47,1,29,0,"FLOAT"],[59,47,2,30,0,"FLOAT"],[60,47,3,31,0,"FLOAT"],[61,47,4,33,0,"INT"],[62,48,0,23,0,"PTMODEL"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.9652303192711613,"offset":[-2706.3348752184625,-1469.581964047439]},"node_versions":{"data-analysis":"11335e88b00ed3b4a5129f92d3d134854b6b4a92"},"ue_links":[]},"version":0.4}
|
||||
@@ -0,0 +1 @@
|
||||
{"last_node_id":57,"last_link_id":73,"nodes":[{"id":50,"type":"PtTrainClassificationModel","pos":[3170.615478515625,1736.97705078125],"size":[243.70773315429688,142],"flags":{},"order":9,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":73},{"name":"train_loader","type":"PTDATALOADER","link":66},{"name":"optimizer","type":"PTOPTIMIZER","link":68},{"name":"val_loader","type":"PTDATALOADER","link":67,"shape":7}],"outputs":[{"name":"Model","type":"PTMODEL","links":[71],"slot_index":0},{"name":"train loss","type":"TENSOR","links":null},{"name":"val loss","type":"TENSOR","links":null}],"properties":{"Node name for S&R":"PtTrainClassificationModel"},"widgets_values":[5,true]},{"id":11,"type":"PtoAdam","pos":[3170.615478515625,1445.9212646484375],"size":[265.8851013183594,108.32855224609375],"flags":{},"order":8,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":72}],"outputs":[{"name":"PTOPTIMIZER","type":"PTOPTIMIZER","links":[68],"slot_index":0}],"properties":{"Node name for S&R":"PtoAdam"},"widgets_values":[0.001,0.9,0.999]},{"id":55,"type":"MarkdownNote","pos":[3170.615478515625,1953.1151123046875],"size":[210,58.94380569458008],"flags":{},"order":0,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Train"],"color":"#432","bgcolor":"#653"},{"id":56,"type":"MarkdownNote","pos":[3468.277099609375,1806.20458984375],"size":[210,58.94380569458008],"flags":{},"order":1,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Save model"],"color":"#432","bgcolor":"#653"},{"id":51,"type":"PandasShowText","pos":[3469.39404296875,1921.830078125],"size":[576.9476318359375,81.15640258789062],"flags":{},"order":11,"mode":0,"inputs":[{"name":"text","type":"STRING","link":70,"widget":{"name":"text"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"properties":{"Node name for S&R":"PandasShowText"},"widgets_values":["","/home/dockeruser/comfy/ComfyUI/custom_nodes/data-analysis/models/fashion_mnist_conv_ep5.pt"]},{"id":53,"type":"MarkdownNote","pos":[2456.569091796875,1652.8607177734375],"size":[210,58.94380569458008],"flags":{},"order":2,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Load train data"],"color":"#432","bgcolor":"#653"},{"id":54,"type":"MarkdownNote","pos":[2456.569091796875,1961.218994140625],"size":[210,58.94380569458008],"flags":{},"order":3,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Load val data"],"color":"#432","bgcolor":"#653"},{"id":52,"type":"MarkdownNote","pos":[2456.569091796875,1384.5592041015625],"size":[210,58.94380569458008],"flags":{},"order":4,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Define model"],"color":"#432","bgcolor":"#653"},{"id":48,"type":"PtvDatasetLoader","pos":[2770.126708984375,1962.4345703125],"size":[309.6732482910156,246.00001525878906],"flags":{},"order":5,"mode":0,"inputs":[],"outputs":[{"name":"PTDATALOADER","type":"PTDATALOADER","links":[67],"slot_index":0}],"properties":{"Node name for S&R":"PtvDatasetLoader"},"widgets_values":["FashionMNIST",true,"",128,false,"{\"train\":False}","{\"num_workers\":1}"]},{"id":47,"type":"PtvDatasetLoader","pos":[2770.126708984375,1649.1856689453125],"size":[314.89654541015625,246],"flags":{},"order":6,"mode":0,"inputs":[],"outputs":[{"name":"PTDATALOADER","type":"PTDATALOADER","links":[66],"slot_index":0}],"properties":{"Node name for S&R":"PtvDatasetLoader"},"widgets_values":["FashionMNIST",true,"",128,true,"{\"train\":True}","{\"num_workers\":1}"]},{"id":19,"type":"PtSaveModel","pos":[3464.489501953125,1694.661376953125],"size":[280.30267333984375,68.31287384033203],"flags":{},"order":10,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":71}],"outputs":[{"name":"model full path","type":"STRING","links":[70],"slot_index":0}],"properties":{"Node name for S&R":"PtSaveModel"},"widgets_values":["fashion_mnist_conv_ep5.pt"]},{"id":57,"type":"PtnConvModel","pos":[2770.126708984375,1380.5203857421875],"size":[315,202],"flags":{},"order":7,"mode":0,"inputs":[],"outputs":[{"name":"PTMODEL","type":"PTMODEL","links":[72,73],"slot_index":0}],"properties":{"Node name for S&R":"PtnConvModel"},"widgets_values":["(1,28,28)",0,10,"[32,64,128,256,512]","[3,3,3,3,1]","[1,1,1,1,0]","[True,True,True,True,False]"]}],"links":[[66,47,0,50,1,"PTDATALOADER"],[67,48,0,50,3,"PTDATALOADER"],[68,11,0,50,2,"PTOPTIMIZER"],[70,19,0,51,0,"STRING"],[71,50,0,19,0,"PTMODEL"],[72,57,0,11,0,"PTMODEL"],[73,57,0,50,0,"PTMODEL"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.7251918251473778,"offset":[-2209.359108864266,-1203.1203019096897]},"node_versions":{"data-analysis":"11335e88b00ed3b4a5129f92d3d134854b6b4a92"},"ue_links":[]},"version":0.4}
|
||||
@@ -6,7 +6,7 @@ class PtFlatten:
|
||||
"""
|
||||
Flattens a PyTorch tensor into a 1D tensor.
|
||||
|
||||
Category: PyTorch wrapper
|
||||
category: PyTorch wrapper - Transform
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
from typing import Any, Dict, Tuple
|
||||
import ast
|
||||
import torch
|
||||
from .ptn_conv_model_def import ConvModel
|
||||
|
||||
|
||||
class PtnConvModel:
|
||||
"""
|
||||
A convolutional model consisting of multiple convolutional layers.
|
||||
|
||||
### Fields:
|
||||
- `input_dim`: A string representing the input dimensions in the format "(C,H,W)".
|
||||
Example: `"(3,28,28)"` for a 3-channel 28x28 image.
|
||||
|
||||
- `penultimate_dim`: An integer specifying the number of features before the output layer. If you specify 0, the number is computed internally.
|
||||
Default: `0`
|
||||
|
||||
- `output_dim`: An integer specifying the number of output features, which should match the number of target classes in classification.
|
||||
Default: `10`, Min: `1`, Max: `1e6`.
|
||||
|
||||
- `channel_list`: A string representing a Python list of integers specifying the number of channels per layer excluding the channel for the input (e.g. 3 for the color image).
|
||||
Example: `"[32,64,128,256,512]"`.
|
||||
|
||||
- `kernel_size_list`: A string representing a Python list of kernel sizes per layer.
|
||||
Example: `"[3,3,3,3,1]"`.
|
||||
|
||||
- `padding_list`: A string representing a Python list of padding values per layer.
|
||||
Example: `"[1,1,1,1,0]"`.
|
||||
|
||||
- `downsample_list`: A string representing a Python list of boolean values indicating whether to downsample at each layer.
|
||||
Example: `"[True,True,True,True,False]"`.
|
||||
|
||||
category: PyTorch wrapper - Training
|
||||
"""
|
||||
|
||||
@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": {
|
||||
"input_dim": ("STRING", {"default": "(3,28,28)"}),
|
||||
"penultimate_dim": ("INT", {"default": 0, "min": 0, "max": 1e6}),
|
||||
"output_dim": ("INT", {"default": 10, "min": 1, "max": 1e6}),
|
||||
"channel_list": ("STRING", {"default": "[32,64,128,256,512]"}),
|
||||
"kernel_size_list": ("STRING", {"default": "[3,3,3,3,1]"}),
|
||||
"padding_list": ("STRING", {"default": "[1,1,1,1,0]"}),
|
||||
"downsample_list": ("STRING", {"default": "[True,True,True,True,False]"})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES: Tuple[str] = ("PTMODEL",)
|
||||
FUNCTION: str = "f"
|
||||
CATEGORY: str = "Training"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kw):
|
||||
return float("NaN")
|
||||
|
||||
def f(self,
|
||||
input_dim:str,
|
||||
penultimate_dim: int,
|
||||
output_dim: int,
|
||||
channel_list: str,
|
||||
kernel_size_list: str,
|
||||
padding_list: str,
|
||||
downsample_list: str) -> None:
|
||||
"""
|
||||
Constructs a convolutional neural network model.
|
||||
|
||||
Args:
|
||||
TODO:Update
|
||||
|
||||
Returns:
|
||||
Tuple[nn.Module]: A tuple containing the instantiated PyTorch model.
|
||||
|
||||
Raises:
|
||||
TODO:Update
|
||||
"""
|
||||
input_dim_parsed = tuple(ast.literal_eval(input_dim))
|
||||
channel_list_parsed = ast.literal_eval(channel_list)
|
||||
kernel_size_list_parsed = ast.literal_eval(kernel_size_list)
|
||||
padding_list_parsed = ast.literal_eval(padding_list)
|
||||
downsample_list_parsed = ast.literal_eval(downsample_list)
|
||||
|
||||
with torch.inference_mode(False):
|
||||
model = ConvModel(
|
||||
input_dim_parsed,
|
||||
penultimate_dim,
|
||||
output_dim,
|
||||
channel_list_parsed,
|
||||
kernel_size_list_parsed,
|
||||
padding_list_parsed,
|
||||
downsample_list_parsed)
|
||||
return (model,)
|
||||
@@ -0,0 +1,151 @@
|
||||
from typing import List, Dict, Any, Tuple
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
class ConvSingleLayer(nn.Module):
|
||||
"""A single convolutional layer followed by an optional ReLU activation and optional downsampling.
|
||||
|
||||
Shape is tensor of shape `(batch_size, in_channels, height, width)`.
|
||||
|
||||
Args:
|
||||
in_channels (int): Number of input channels.
|
||||
out_channels (int): Number of output channels.
|
||||
kernel_size (int): Size of the convolutional kernel.
|
||||
padding (int): Padding size for convolution.
|
||||
downsample (bool): If True, applies a 2x2 MaxPooling layer.
|
||||
apply_activation (bool, optional): If True, applies ReLU activation. Defaults to True.
|
||||
|
||||
pragma: skip_doc
|
||||
"""
|
||||
|
||||
@classmethod # TODO: Dummy method to suppress ComfyUI warning. DO NOT REMOVE
|
||||
def INPUT_TYPES(cls) -> Dict[str, Any]:
|
||||
return {}
|
||||
|
||||
def __init__(self,
|
||||
in_channels: int,
|
||||
out_channels: int,
|
||||
kernel_size: int,
|
||||
padding: int,
|
||||
downsample: bool,
|
||||
apply_activation: bool = True) -> None:
|
||||
super().__init__()
|
||||
self.conv = nn.Conv2d(in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
kernel_size=kernel_size,
|
||||
padding=padding)
|
||||
self.apply_activation = apply_activation
|
||||
self.activation = nn.ReLU()
|
||||
self.downsample = downsample
|
||||
self.maxpool = nn.MaxPool2d(kernel_size=2, stride=2)
|
||||
|
||||
def forward(self, inputs: torch.Tensor) -> torch.Tensor:
|
||||
"""Forward pass of the convolutional layer.
|
||||
|
||||
Args:
|
||||
inputs (torch.Tensor): Input tensor of shape `(batch_size, in_channels, height, width)`.
|
||||
|
||||
Returns:
|
||||
torch.Tensor: Output tensor after convolution, optional activation, and optional pooling.
|
||||
"""
|
||||
x = self.conv(inputs)
|
||||
if self.downsample:
|
||||
x = self.maxpool(x)
|
||||
if self.apply_activation:
|
||||
x = self.activation(x)
|
||||
return x
|
||||
|
||||
|
||||
class ConvModel(nn.Module):
|
||||
"""A multi-layer convolutional neural network with ReLU activation after each convolutional layer, except for the final dense layer.
|
||||
|
||||
Args:
|
||||
input_dim (Tuple[int, int, int]): Shape of a single input sample (C, H, W).
|
||||
penultimate_dim (int): Number of features before the final dense layer. If 0 is specified, this value is automatically computed internally.
|
||||
output_dim (int): Number of output features.
|
||||
channel_list (List[int]): List of channel sizes for each layer. This should not include the inputs channel.
|
||||
Input channel is taken from the first element of input_dim.
|
||||
kernel_size_list (List[int]): List of kernel sizes for each convolutional layer.
|
||||
padding_list (List[int]): List of padding values for each convolutional layer.
|
||||
downsample_list (List[bool]): List indicating whether downsampling should be applied after each layer.
|
||||
|
||||
pragma: skip_doc
|
||||
"""
|
||||
|
||||
@classmethod # TODO: Dummy method to suppress ComfyUI warning
|
||||
def INPUT_TYPES(cls) -> Dict[str, Any]:
|
||||
return {}
|
||||
|
||||
def __init__(self,
|
||||
input_dim: Tuple[int, int, int],
|
||||
penultimate_dim: int,
|
||||
output_dim: int,
|
||||
channel_list: List[int],
|
||||
kernel_size_list: List[int],
|
||||
padding_list: List[int],
|
||||
downsample_list: List[bool]) -> None:
|
||||
super().__init__()
|
||||
|
||||
# Validate list lengths
|
||||
if (len(channel_list) != len(kernel_size_list) or
|
||||
len(kernel_size_list) != len(padding_list) or
|
||||
len(padding_list) != len(downsample_list)):
|
||||
raise ValueError("channel_list, kernel_size_list, padding_list, and downsample_list must have the same length.")
|
||||
|
||||
self.channel_list = [input_dim[0]] + channel_list
|
||||
|
||||
# Initialize convolutional layers
|
||||
layers = []
|
||||
for i in range(len(self.channel_list) - 1):
|
||||
layers.append(
|
||||
ConvSingleLayer(
|
||||
in_channels=self.channel_list[i],
|
||||
out_channels=self.channel_list[i + 1],
|
||||
kernel_size=kernel_size_list[i],
|
||||
padding=padding_list[i],
|
||||
downsample=downsample_list[i]
|
||||
)
|
||||
)
|
||||
|
||||
self.conv_layers = nn.ModuleList(layers)
|
||||
|
||||
if penultimate_dim == 0:
|
||||
# Try to auto compute penultimate_dim by processing the dummy input
|
||||
with torch.no_grad():
|
||||
dummy_input = torch.randn(1, *input_dim) # B, C, H, W
|
||||
x = dummy_input
|
||||
for layer in self.conv_layers:
|
||||
x = layer(x)
|
||||
checked_penultimate_dim = x.numel() # Number of elements
|
||||
else:
|
||||
checked_penultimate_dim = penultimate_dim
|
||||
|
||||
self.final_dense = nn.Linear(checked_penultimate_dim, output_dim, bias=True)
|
||||
|
||||
def forward(self, inputs: torch.Tensor) -> torch.Tensor:
|
||||
"""Forward pass of the model.
|
||||
|
||||
Args:
|
||||
inputs (torch.Tensor): Input tensor of shape `(batch_size, C, H, W)`.
|
||||
|
||||
Returns:
|
||||
torch.Tensor: Output tensor of shape `(batch_size, output_dim)`.
|
||||
|
||||
Raises:
|
||||
ValueError: If inputs is not a rank 4 tensor.
|
||||
"""
|
||||
if inputs.dim() not in [3, 4]:
|
||||
raise ValueError("Inputs is not a rank 3 or 4 tensor.")
|
||||
|
||||
if inputs.dim() == 3:
|
||||
x = torch.unsqueeze(inputs, 1)
|
||||
else:
|
||||
x = inputs
|
||||
|
||||
for layer in self.conv_layers:
|
||||
x = layer(x)
|
||||
|
||||
x = torch.flatten(x, start_dim=1) # Flatten except batch-axis
|
||||
x = self.final_dense(x)
|
||||
return x
|
||||
@@ -10,7 +10,7 @@ class PtnLinearModel:
|
||||
A linear model consisting of dense layers.
|
||||
|
||||
### Fields:
|
||||
- `dim_list`: A comma-separated string representing layer dimensions.
|
||||
- `dim_list`: A string representing a Python list of layer dimensions.
|
||||
It should contain the input dimension for the first layer and the output dimensions
|
||||
for all layers.
|
||||
|
||||
@@ -18,10 +18,11 @@ class PtnLinearModel:
|
||||
- Input data: 784
|
||||
- Layer 1: 784 → 128
|
||||
- Layer 2: 128 → 10
|
||||
- Specify `"784, 128, 10"`
|
||||
- Specify `"[784,128,10]"`
|
||||
|
||||
- `bias_list`: A string representing a Python list of bias usage (True/False) per layer.
|
||||
Example: "[True,False]"
|
||||
|
||||
- `bias_list`: A comma-separated string representing bias usage (True/False) per layer.
|
||||
|
||||
- `num_layers`: The number of layers in the model.
|
||||
|
||||
category: PyTorch wrapper - Training
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "data-analysis"
|
||||
description = "Data analysis custom modules for ComfyUI – Integrates Pandas, Seaborn and Matplotlib into ComfyUI, featuring extensive PyTorch wrapper nodes for seamless tensor operations. Experimental PyTorch model training was added in v2.0."
|
||||
version = "2.0.3"
|
||||
version = "2.0.4"
|
||||
license = {file = "LICENSE"}
|
||||
|
||||
[project.urls]
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
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_conv_model import PtnConvModel
|
||||
from pytorch_wrapper.ptv_dataset import PtvDataset
|
||||
from pytorch_wrapper.ptv_transforms_to_tensor import PtvTransformsToTensor
|
||||
from pytorch_wrapper.pt_data_loader import PtDataLoader
|
||||
from pytorch_wrapper.pt_evaluate_classification_model import PtEvaluateClassificationModel
|
||||
from pytorch_wrapper.pt_load_model import PtLoadModel
|
||||
|
||||
class TestPtEvaluate(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Set up test instance."""
|
||||
# Transform
|
||||
compose_node = PtvTransformsToTensor()
|
||||
transform = compose_node.f()[0]
|
||||
|
||||
# Dataset
|
||||
dataset_node = PtvDataset()
|
||||
dataset = dataset_node.f("FashionMNIST",
|
||||
True, # download
|
||||
"", # root
|
||||
'{"train": False}',
|
||||
transform=transform)[0]
|
||||
|
||||
# Data loader
|
||||
data_loader_node = PtDataLoader()
|
||||
self.data_loader = data_loader_node.f(
|
||||
dataset=dataset,
|
||||
batch_size=32,
|
||||
shuffle=True,
|
||||
parameters='{"num_workers":1}'
|
||||
)[0]
|
||||
|
||||
# Model
|
||||
self.model_node = PtnConvModel()
|
||||
self.model = self.model_node.f(
|
||||
input_dim="(1, 28, 28)",
|
||||
penultimate_dim=0,
|
||||
output_dim=10,
|
||||
channel_list="[32,64,128,256]",
|
||||
kernel_size_list="[3,3,3,3]",
|
||||
padding_list="[1,1,1,1]",
|
||||
downsample_list="[True,True,True,True]"
|
||||
)[0]
|
||||
|
||||
# Load weights
|
||||
self.load_model = PtLoadModel()
|
||||
epochs = 1
|
||||
self.loaded_model = self.load_model.f(self.model, f"fashion_mnist_{epochs}_epochs_conv.pt")
|
||||
|
||||
# Instntiates evaluation node
|
||||
self.evaluate = PtEvaluateClassificationModel()
|
||||
|
||||
def test_evaluation(self):
|
||||
"""
|
||||
Tests evaluation.
|
||||
|
||||
Note that this test requires a trained model file on the file system.
|
||||
Unit test will fail if you run this for the first time.
|
||||
Run the unit test for training to generate the weight, and re-run this unit test.
|
||||
"""
|
||||
accuracy, precision, recall, f1, num_samples = \
|
||||
self.evaluate.f(self.model, self.data_loader, use_gpu=True)
|
||||
print(f"""
|
||||
accuracy: {accuracy}
|
||||
precision: {precision}
|
||||
recall: {recall}
|
||||
f1: {f1}
|
||||
Number of samples: {num_samples}
|
||||
""")
|
||||
self.assertTrue(accuracy > 0.8)
|
||||
self.assertEqual(num_samples, 10000)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,63 @@
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
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_conv_model import PtnConvModel
|
||||
from pytorch_wrapper.pto_adam import PtoAdam
|
||||
from pytorch_wrapper.ptv_dataset import PtvDataset
|
||||
from pytorch_wrapper.ptv_transforms_to_tensor import PtvTransformsToTensor
|
||||
from pytorch_wrapper.pt_data_loader import PtDataLoader
|
||||
from pytorch_wrapper.pt_train_classification_model import PtTrainClassificationModel
|
||||
from pytorch_wrapper.pt_save_model import PtSaveModel
|
||||
|
||||
|
||||
class TestPtTrainConvModel(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Set up test instance."""
|
||||
compose_node = PtvTransformsToTensor()
|
||||
transform = compose_node.f()[0]
|
||||
dataset_node = PtvDataset()
|
||||
dataset = dataset_node.f("FashionMNIST",
|
||||
True, # download
|
||||
"", # root
|
||||
'{"train": True}',
|
||||
transform=transform)[0]
|
||||
data_loader_node = PtDataLoader()
|
||||
self.train_loader = data_loader_node.f(
|
||||
dataset=dataset,
|
||||
batch_size=32,
|
||||
shuffle=True,
|
||||
parameters='{"num_workers":1}'
|
||||
)[0]
|
||||
|
||||
self.model_node = PtnConvModel()
|
||||
self.model = self.model_node.f(
|
||||
input_dim="(1, 28, 28)",
|
||||
penultimate_dim=0,
|
||||
output_dim=10,
|
||||
channel_list="[32,64,128,256]",
|
||||
kernel_size_list="[3,3,3,3]",
|
||||
padding_list="[1,1,1,1]",
|
||||
downsample_list="[True,True,True,True]"
|
||||
)[0]
|
||||
|
||||
self.optimizer = PtoAdam().f(self.model, 0.001, 0.9, 0.999)[0]
|
||||
self.trainer = PtTrainClassificationModel()
|
||||
self.save_model = PtSaveModel()
|
||||
|
||||
def test_1(self):
|
||||
epochs = 1
|
||||
trained_model = self.trainer.f(self.model, self.train_loader, self.optimizer,
|
||||
epochs, # epochs
|
||||
use_gpu=True)[0]
|
||||
self.save_model.f(trained_model, f"fashion_mnist_{epochs}_epochs_conv.pt")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,158 @@
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
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_conv_model_def import ConvModel, ConvSingleLayer
|
||||
|
||||
|
||||
class TestPtnConvModelDef(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Setup method for initializing test conditions."""
|
||||
pass
|
||||
|
||||
def test_single_layer_1_with_padding(self):
|
||||
"""Test ConvSingleLayer with padding=1, no downsampling."""
|
||||
model = ConvSingleLayer(
|
||||
in_channels=4,
|
||||
out_channels=32,
|
||||
kernel_size=3,
|
||||
padding=1,
|
||||
downsample=False,
|
||||
apply_activation=True)
|
||||
x = torch.ones(1, 4, 28, 28)
|
||||
out = model(x)
|
||||
self.assertEqual(out.size(), torch.Size([1, 32, 28, 28]))
|
||||
|
||||
def test_single_layer_no_padding(self):
|
||||
"""Test ConvSingleLayer with padding=0, no downsampling."""
|
||||
model = ConvSingleLayer(
|
||||
in_channels=4,
|
||||
out_channels=32,
|
||||
kernel_size=3,
|
||||
padding=0,
|
||||
downsample=False,
|
||||
apply_activation=True)
|
||||
x = torch.ones(1, 4, 28, 28)
|
||||
out = model(x)
|
||||
self.assertEqual(out.size(), torch.Size([1, 32, 26, 26]))
|
||||
|
||||
def test_single_layer_3_no_padding_downsample(self):
|
||||
"""Test ConvSingleLayer with padding=0 and downsampling enabled."""
|
||||
model = ConvSingleLayer(
|
||||
in_channels=4,
|
||||
out_channels=32,
|
||||
kernel_size=3,
|
||||
padding=0,
|
||||
downsample=True,
|
||||
apply_activation=True)
|
||||
x = torch.ones(1, 4, 28, 28)
|
||||
out = model(x)
|
||||
self.assertEqual(out.size(), torch.Size([1, 32, 13, 13]))
|
||||
|
||||
def test_1(self):
|
||||
"""Test ConvModel with a single convolutional layer, checking output size."""
|
||||
model = ConvModel(
|
||||
input_dim=(4, 28, 28),
|
||||
penultimate_dim=0,
|
||||
output_dim=10,
|
||||
channel_list=[32],
|
||||
kernel_size_list=[3],
|
||||
padding_list=[1],
|
||||
downsample_list=[False])
|
||||
|
||||
x = torch.ones(1, 4, 28, 28)
|
||||
out = model(x)
|
||||
self.assertEqual(out.size(), torch.Size([1, 10]))
|
||||
|
||||
def test_2(self):
|
||||
"""Test ConvModel intermediate convolution layers' output size."""
|
||||
model = ConvModel(
|
||||
input_dim=(4, 28, 28),
|
||||
penultimate_dim=0,
|
||||
output_dim=10,
|
||||
channel_list=[32],
|
||||
kernel_size_list=[3],
|
||||
padding_list=[1],
|
||||
downsample_list=[False])
|
||||
|
||||
x = torch.ones(1, 4, 28, 28)
|
||||
for c in model.conv_layers:
|
||||
x = c(x)
|
||||
out = x
|
||||
self.assertEqual(out.size(), torch.Size([1, 32, 28, 28]))
|
||||
|
||||
def test_3(self):
|
||||
"""Test ConvModel with two convolutional layers and downsampling enabled."""
|
||||
model = ConvModel(
|
||||
input_dim=(4, 28, 28),
|
||||
penultimate_dim=0,
|
||||
output_dim=10,
|
||||
channel_list=[32,64],
|
||||
kernel_size_list=[3,3],
|
||||
padding_list=[1,1],
|
||||
downsample_list=[True,True])
|
||||
|
||||
x = torch.ones(1, 4, 28, 28)
|
||||
for c in model.conv_layers:
|
||||
x = c(x)
|
||||
out = x
|
||||
self.assertEqual(out.size(), torch.Size([1, 64, 7, 7]))
|
||||
|
||||
def test_4(self):
|
||||
"""Test ConvModel with three convolutional layers and downsampling enabled."""
|
||||
model = ConvModel(
|
||||
input_dim=(4, 28, 28),
|
||||
penultimate_dim=0,
|
||||
output_dim=10,
|
||||
channel_list=[32,64,128],
|
||||
kernel_size_list=[3,3,3],
|
||||
padding_list=[1,1,1],
|
||||
downsample_list=[True,True,True])
|
||||
|
||||
x = torch.ones(1, 4, 28, 28)
|
||||
for c in model.conv_layers:
|
||||
x = c(x)
|
||||
out = x
|
||||
self.assertEqual(out.size(), torch.Size([1, 128, 3, 3]))
|
||||
|
||||
def test_5(self):
|
||||
"""Test ConvModel with four convolutional layers and downsampling enabled."""
|
||||
model = ConvModel(
|
||||
input_dim=(4, 28, 28),
|
||||
penultimate_dim=0,
|
||||
output_dim=10,
|
||||
channel_list=[32,64,128,256],
|
||||
kernel_size_list=[3,3,3,3],
|
||||
padding_list=[1,1,1,1],
|
||||
downsample_list=[True,True,True,True])
|
||||
|
||||
x = torch.ones(1, 4, 28, 28)
|
||||
for c in model.conv_layers:
|
||||
x = c(x)
|
||||
out = x
|
||||
self.assertEqual(out.size(), torch.Size([1, 256, 1, 1]))
|
||||
|
||||
def test_6(self):
|
||||
"""Test ConvModel with five convolutional layers, last layer without downsampling."""
|
||||
model = ConvModel(
|
||||
input_dim=(4, 28, 28),
|
||||
penultimate_dim=0,
|
||||
output_dim=10,
|
||||
channel_list=[32,64,128,256,512],
|
||||
kernel_size_list=[3,3,3,3,1],
|
||||
padding_list=[1,1,1,1,0],
|
||||
downsample_list=[True,True,True,True,False])
|
||||
|
||||
x = torch.ones(1, 4, 28, 28)
|
||||
for c in model.conv_layers:
|
||||
x = c(x)
|
||||
out = x
|
||||
self.assertEqual(out.size(), torch.Size([1, 512, 1, 1]))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,64 @@
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
import torch
|
||||
|
||||
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_conv_model import PtnConvModel
|
||||
|
||||
|
||||
class TestPtnConvModel(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Set up test instance."""
|
||||
self.model_node = PtnConvModel()
|
||||
|
||||
def test_forward(self):
|
||||
model = self.model_node.f(
|
||||
input_dim="(4, 28, 28)",
|
||||
penultimate_dim=0,
|
||||
output_dim=10,
|
||||
channel_list="[32,64,128,256]",
|
||||
kernel_size_list="[3,3,3,3]",
|
||||
padding_list="[1,1,1,1]",
|
||||
downsample_list="[True,True,True,True]"
|
||||
)[0]
|
||||
bs = 2
|
||||
x = torch.ones(bs, 4, 28, 28)
|
||||
out = model(x)
|
||||
self.assertEqual(out.size(), torch.Size([bs, 10]))
|
||||
|
||||
def test_rank3_input(self):
|
||||
model = self.model_node.f(
|
||||
input_dim="(1, 28, 28)",
|
||||
penultimate_dim=0,
|
||||
output_dim=10,
|
||||
channel_list="[32,64,128,256]",
|
||||
kernel_size_list="[3,3,3,3]",
|
||||
padding_list="[1,1,1,1]",
|
||||
downsample_list="[True,True,True,True]"
|
||||
)[0]
|
||||
bs = 2
|
||||
x = torch.ones(bs, 28, 28)
|
||||
out = model(x)
|
||||
self.assertEqual(out.size(), torch.Size([bs, 10]))
|
||||
|
||||
def test_rank2_input(self):
|
||||
model = self.model_node.f(
|
||||
input_dim="(1, 28, 28)",
|
||||
penultimate_dim=0,
|
||||
output_dim=10,
|
||||
channel_list="[32,64,128,256]",
|
||||
kernel_size_list="[3,3,3,3]",
|
||||
padding_list="[1,1,1,1]",
|
||||
downsample_list="[True,True,True,True]"
|
||||
)[0]
|
||||
bs = 2
|
||||
x = torch.ones(bs, 28)
|
||||
with self.assertRaises(ValueError) as context:
|
||||
model(x)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user