Added Hugging Face pretrained model support.
This commit is contained in:
@@ -5,6 +5,7 @@ models/
|
||||
/datasets/
|
||||
examples/datasets/.~lock*
|
||||
.vscode
|
||||
output.txt
|
||||
|
||||
# But don't ignore these paths:
|
||||
!models/sentencepiece/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# This file is auto-generated. Do not edit manually.
|
||||
from typing import Dict, Type, TypeVar
|
||||
from .modules.hugging_face_wrapper.hf_tokenizer_encode import HfTokenizerEncode
|
||||
from .modules.pytorch_wrapper.pt_abs import PtAbs
|
||||
from .modules.pytorch_wrapper.pt_acos import PtAcos
|
||||
from .modules.pytorch_wrapper.pt_add import PtAdd
|
||||
@@ -111,6 +112,7 @@ from .modules.pytorch_wrapper.pt_tokenizer import PtTokenizer
|
||||
from .modules.pytorch_wrapper.pt_train_classification_model import PtTrainClassificationModel
|
||||
from .modules.pytorch_wrapper.pt_train_classification_model_lr import PtTrainClassificationModelLr
|
||||
from .modules.pytorch_wrapper.pt_train_classification_transformer_model import PtTrainClassificationTransformerModel
|
||||
from .modules.pytorch_wrapper.pt_train_fine_tune_classification_transformer_model import PtTrainFineTuneClassificationTransformerModel
|
||||
from .modules.pytorch_wrapper.pt_train_model import PtTrainModel
|
||||
from .modules.pytorch_wrapper.pt_train_regression_model import PtTrainRegressionModel
|
||||
from .modules.pytorch_wrapper.pt_train_rnn_model import PtTrainRNNModel
|
||||
@@ -163,6 +165,7 @@ from .modules.pytorch_wrapper.ptn_embedding_rnn_linear import PtnEmbeddingRNNLin
|
||||
from .modules.pytorch_wrapper.ptn_embedding_transformer_linear import PtnEmbeddingTransformerLinear
|
||||
from .modules.pytorch_wrapper.ptn_gru import PtnGRU
|
||||
from .modules.pytorch_wrapper.ptn_gru_linear import PtnGRULinear
|
||||
from .modules.pytorch_wrapper.ptn_hf_fine_tuned_classification_model import PtnHfFineTunedClassificationModel
|
||||
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
|
||||
@@ -209,6 +212,7 @@ NODE_CLASS_MAPPINGS (Dict[str, Type[T]]):
|
||||
"""
|
||||
|
||||
NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = {
|
||||
"HfTokenizerEncode": HfTokenizerEncode,
|
||||
"PtAbs": PtAbs,
|
||||
"PtAcos": PtAcos,
|
||||
"PtAdd": PtAdd,
|
||||
@@ -320,6 +324,7 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = {
|
||||
"PtTrainClassificationModel": PtTrainClassificationModel,
|
||||
"PtTrainClassificationModelLr": PtTrainClassificationModelLr,
|
||||
"PtTrainClassificationTransformerModel": PtTrainClassificationTransformerModel,
|
||||
"PtTrainFineTuneClassificationTransformerModel": PtTrainFineTuneClassificationTransformerModel,
|
||||
"PtTrainModel": PtTrainModel,
|
||||
"PtTrainRNNModel": PtTrainRNNModel,
|
||||
"PtTrainRegressionModel": PtTrainRegressionModel,
|
||||
@@ -372,6 +377,7 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = {
|
||||
"PtnEmbeddingTransformerLinear": PtnEmbeddingTransformerLinear,
|
||||
"PtnGRU": PtnGRU,
|
||||
"PtnGRULinear": PtnGRULinear,
|
||||
"PtnHfFineTunedClassificationModel": PtnHfFineTunedClassificationModel,
|
||||
"PtnHuberLoss": PtnHuberLoss,
|
||||
"PtnInstanceNorm2d": PtnInstanceNorm2d,
|
||||
"PtnKLDivLoss": PtnKLDivLoss,
|
||||
@@ -418,6 +424,7 @@ NODE_DISPLAY_NAME_MAPPINGS (Dict[str, str]):
|
||||
"""
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = {
|
||||
"HfTokenizerEncode": "Hf Tokenizer Encode",
|
||||
"PtAbs": "Pt Abs",
|
||||
"PtAcos": "Pt Acos",
|
||||
"PtAdd": "Pt Add",
|
||||
@@ -529,6 +536,7 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = {
|
||||
"PtTrainClassificationModel": "Pt Train Classification Model",
|
||||
"PtTrainClassificationModelLr": "Pt Train Classification Model Lr",
|
||||
"PtTrainClassificationTransformerModel": "Pt Train Classification Transformer Model",
|
||||
"PtTrainFineTuneClassificationTransformerModel": "Pt Train Fine Tune Classification Transformer Model",
|
||||
"PtTrainModel": "Pt Train Model",
|
||||
"PtTrainRNNModel": "Pt Train RNN Model",
|
||||
"PtTrainRegressionModel": "Pt Train Regression Model",
|
||||
@@ -581,6 +589,7 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = {
|
||||
"PtnEmbeddingTransformerLinear": "Ptn Embedding Transformer Linear",
|
||||
"PtnGRU": "Ptn GRU",
|
||||
"PtnGRULinear": "Ptn GRU Linear",
|
||||
"PtnHfFineTunedClassificationModel": "Ptn Hf Fine Tuned Classification Model",
|
||||
"PtnHuberLoss": "Ptn Huber Loss",
|
||||
"PtnInstanceNorm2d": "Ptn Instance Norm 2d",
|
||||
"PtnKLDivLoss": "Ptn KL Div Loss",
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# Hf Tokenizer Encode
|
||||
Hugging Face tokenizer wrapper for encoding text into token ID tensors.
|
||||
|
||||
Args:
|
||||
model_name (str): Name of the model.
|
||||
padding (bool): Whether to pad sequences to equal length.
|
||||
padding_method (str): 'longest' or 'max_length'.
|
||||
- 'longest': pad to the longest sequence length after truncation in this batch.
|
||||
- 'max_length': pad all sequences to the fixed length `max_length`.
|
||||
truncation (bool): Whether to truncate sequences to `max_length`.
|
||||
max_length (int): The target length for truncation or padding.
|
||||
If you specify 0, it uses model's maximum input length.
|
||||
|
||||
Returns:
|
||||
PTCALLABLE: A tuple containing a callable that accepts a sentence or list of
|
||||
sentences and returns token ID tensor(s) and attention mask(s).
|
||||
|
||||
## Input
|
||||
| Name | Data type |
|
||||
|---|---|
|
||||
| model_name | String |
|
||||
| padding | Boolean |
|
||||
| padding_method | |
|
||||
| truncation | Boolean |
|
||||
| max_length | Int |
|
||||
|
||||
## Output
|
||||
| Data type |
|
||||
|---|
|
||||
| Ptcallable |
|
||||
|
||||
<HR>
|
||||
Category: PyTorch wrapper - Training
|
||||
|
||||
ComfyUI Pt Wrapper Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
|
||||
@@ -160,6 +160,7 @@
|
||||
| [Ptn Embedding Transformer Linear](ptn_embedding_transformer_linear.md) | A Transformer encoder model with a linear head. |
|
||||
| [Ptn GRU](ptn_gru.md) | A gated recurrent unit (GRU) model consisting of one or more of a recurrent layer. |
|
||||
| [Ptn GRU Linear](ptn_gru_linear.md) | A recurrent neural network (GRU) model with a linear head. |
|
||||
| [Ptn Hf Fine Tuned Classification Model](ptn_hf_fine_tuned_classification_model.md) | A binary classification model containing a Hugging Face pretrained Transformer model. |
|
||||
| [Ptn Instance Norm 2d](ptn_instance_norm2d.md) | A normalization model to normalize elements over spatial axes within each channel for each sample. |
|
||||
| [Ptn LSTM](ptn_lstm.md) | A long short-term memory (LSTM) model consisting of one or more of a recurrent layer. |
|
||||
| [Ptn LSTM Linear](ptn_lstm_linear.md) | A recurrent neural network (LSTM) model with a linear head. |
|
||||
@@ -225,6 +226,7 @@
|
||||
## PyTorch wrapper - Training
|
||||
| Node | Description |
|
||||
| --- | --- |
|
||||
| [Hf Tokenizer Encode](hf_tokenizer_encode.md) | Hugging Face tokenizer wrapper for encoding text into token ID tensors. |
|
||||
| [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. |
|
||||
@@ -236,6 +238,7 @@
|
||||
| [Pt Train Classification Model](pt_train_classification_model.md) | Trains a classification model using a given dataset, optimizer, and number of epochs. |
|
||||
| [Pt Train Classification Model Lr](pt_train_classification_model_lr.md) | Trains a classification model using a given dataset, optimizer, and number of epochs with learning rate decay. |
|
||||
| [Pt Train Classification Transformer Model](pt_train_classification_transformer_model.md) | Trains a classification Transformer model using a given dataset, loss function, optimizer, and number of epochs with learning rate decay. |
|
||||
| [Pt Train Fine Tune Classification Transformer Model](pt_train_fine_tune_classification_transformer_model.md) | Fine-tunes a classification Transformer model using a given dataset, loss function, optimizer, and number of epochs with learning rate decay. |
|
||||
| [Pt Train Model](pt_train_model.md) | Trains a model using a given dataset, loss function, optimizer, and number of epochs with learning rate decay. |
|
||||
| [Pt Train RNN Model](pt_train_rnn_model.md) | Trains an RNN model using a given dataset, loss function, optimizer, and number of epochs with learning rate decay. |
|
||||
| [Pt Train Regression Model](pt_train_regression_model.md) | Trains a regression model using a given dataset, optimizer, and number of epochs. |
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# Pt Train Fine Tune Classification Transformer Model
|
||||
Fine-tunes a classification Transformer model using a given dataset, loss function, optimizer, and number of epochs with learning rate decay.
|
||||
|
||||
Args:
|
||||
model (PTMODEL): The PyTorch model to be trained.
|
||||
train_loader (torch.utils.data.DataLoader): DataLoader containing the training dataset.
|
||||
optimizer (PTOPTIMIZER): Optimizer used for training. Only Adam and AdamW are supported.
|
||||
loss (PTLOSS): The loss class.
|
||||
epochs (int): Number of training epochs.
|
||||
freeze_pretrained_module_epochs (int) : Number of epochs to freeze pretrained module.
|
||||
use_gpu (bool): Whether to use GPU for training.
|
||||
early_stopping (bool): Whether to turn on early stopping
|
||||
early_stopping_rounds (int): Specifies the number of rounds to monitor the evaluation loss when early_stopping is enabled and validation data is specified. If the loss does not decrease compared to the current best loss within this period, training will be stopped.
|
||||
output_best_val_model (bool): Whether to outputs the model with the best val loss or the model from the last epoch.
|
||||
classification_metrics (bool): If True and if val_loader is set, print out classification metrics. This is only valid for classification model training.
|
||||
val_loader (PTDATALOADER): DataLoader for validation dataset (optional).
|
||||
|
||||
## Input
|
||||
| Name | Data type |
|
||||
|---|---|
|
||||
| model | Ptmodel |
|
||||
| train_loader | Ptdataloader |
|
||||
| optimizer | Ptoptimizer |
|
||||
| loss_function | Ptloss |
|
||||
| epochs | Int |
|
||||
| freeze_pretrained_module_epochs | Int |
|
||||
| use_gpu | Boolean |
|
||||
| early_stopping | Boolean |
|
||||
| early_stopping_rounds | Int |
|
||||
| output_best_val_model | Boolean |
|
||||
| classification_metrics | Boolean |
|
||||
| scheduler | Ptlrscheduler |
|
||||
| val_loader | Ptdataloader |
|
||||
|
||||
## Output
|
||||
| Data type |
|
||||
|---|
|
||||
| Ptmodel |
|
||||
| Tensor |
|
||||
| Tensor |
|
||||
|
||||
<HR>
|
||||
Category: PyTorch wrapper - Training
|
||||
|
||||
ComfyUI Pt Wrapper Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Ptn Hf Fine Tuned Classification Model
|
||||
A binary classification model containing a Hugging Face pretrained Transformer model.
|
||||
|
||||
Args:
|
||||
model_name (str): Name or path of the pretrained Hugging Face model.
|
||||
use_mean_pooling (bool): If True, average token embeddings using the attention mask.
|
||||
Otherwise, use pooler output or CLS token embedding.
|
||||
dropout (float): Dropout rate applied before the final linear layer.
|
||||
Set to 0.0 to disable dropout. Must be ≥ 0.
|
||||
|
||||
## Input
|
||||
| Name | Data type |
|
||||
|---|---|
|
||||
| model_name | String |
|
||||
| use_mean_pooling | Boolean |
|
||||
| dropout | Float |
|
||||
|
||||
## Output
|
||||
| Data type |
|
||||
|---|
|
||||
| Ptmodel |
|
||||
|
||||
<HR>
|
||||
Category: PyTorch wrapper - Model
|
||||
|
||||
ComfyUI Pt Wrapper Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
|
||||
@@ -8,6 +8,7 @@ A PyTorch Dataset class wrapper for a Hugging Face dataset that converts text to
|
||||
label_field_name (str): Field name for labels.
|
||||
encode (PTCALLABLE): The reference to a token encoder function.
|
||||
remove_html_tags (bool): Remove html tags in text if True.
|
||||
encode_return_dict (bool): Encode function returns a Dict instead of a Tuple.
|
||||
|
||||
## Input
|
||||
| Name | Data type |
|
||||
@@ -18,6 +19,7 @@ A PyTorch Dataset class wrapper for a Hugging Face dataset that converts text to
|
||||
| label_field_name | String |
|
||||
| encode | Ptcallable |
|
||||
| remove_html_tags | Boolean |
|
||||
| encode_return_dict | Boolean |
|
||||
|
||||
## Output
|
||||
| Data type |
|
||||
|
||||
@@ -46,7 +46,7 @@ You can also enable `classification_metrics` to print validation accuracy to the
|
||||
|
||||
## Expected Accuracy
|
||||
|
||||
Using this workflow, you should get around **83% validation accuracy** on the Hugging Face version of the IMDB dataset, which is a solid result for an RNN.
|
||||
Using this workflow, you should get around **85% validation accuracy** on the Hugging Face version of the IMDB dataset, which is a solid result for an RNN.
|
||||
Both GRU & LSTM workflows achieve approximately **87% validation accuracy**.
|
||||
|
||||
# RNN Workflow with Training Embedding from Scratch
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,89 @@
|
||||
from typing import Tuple, Callable, Optional
|
||||
import re
|
||||
from html import unescape
|
||||
import torch
|
||||
from torch.utils.data import Dataset
|
||||
from datasets import load_dataset # Hugging Face (not PyTorch)
|
||||
|
||||
|
||||
class HfDatasetWithTokenEncode(Dataset):
|
||||
"""
|
||||
Hugging Face Dataset wrapper with custom token encode function.
|
||||
|
||||
pragma: skip_doc
|
||||
"""
|
||||
def __init__(self,
|
||||
dataset_name: str,
|
||||
split: str,
|
||||
sample_field_name: str = "text",
|
||||
label_field_name: str = "label",
|
||||
encode: Optional[Callable]=None,
|
||||
remove_html_tags:Optional[bool]=False,
|
||||
encode_return_dict:Optional[bool]=False) -> None:
|
||||
"""
|
||||
Initializes the dataset by loading Hugging Face dataset.
|
||||
|
||||
Args:
|
||||
dataset_name (str): The name of the Hugging Face dataset.
|
||||
split (str): The dataset split (e.g., 'train').
|
||||
sample_field_name (str): Field name for text samples.
|
||||
label_field_name (str): Field name for labels.
|
||||
encode (Optional[Callable]): Token encode function
|
||||
remove_html_tags (Optional[bool]): Remove html tags in text if True.
|
||||
encode_return_dict (Optional[bool]): Encode function returns a Dict instead of a Tuple.
|
||||
"""
|
||||
self.dataset = load_dataset(dataset_name, split=split)
|
||||
self.sample_field_name = sample_field_name
|
||||
self.label_field_name = label_field_name
|
||||
self.encode = encode
|
||||
self.remove_html_tags = remove_html_tags
|
||||
self.encode_return_dict = encode_return_dict
|
||||
|
||||
def __getitem__(self, idx: int) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
"""
|
||||
Retrieves the (tokens, label) pair at the given index.
|
||||
|
||||
Args:
|
||||
idx (int): Index of the item.
|
||||
|
||||
Returns:
|
||||
Tuple[Tuple[torch.Tensor, torch.Tensor], torch.Tensor]: Tuple of the following:
|
||||
- Tokens tensor and masks tensor.
|
||||
- Label tensor
|
||||
"""
|
||||
# Get a text sample from database and convert to a word embedding
|
||||
sample = self.dataset[idx]
|
||||
if self.remove_html_tags:
|
||||
# Decode HTML entities like "&", "<", etc. to their actual characters
|
||||
text = unescape(sample[self.sample_field_name])
|
||||
|
||||
# Remove HTML tags (like <br />, <p>, etc.)
|
||||
# The regex "<.*?>" uses .*? (non-greedy match) to ensure it matches one tag at a time,
|
||||
# instead of greedily removing everything between the first '<' and last '>'
|
||||
text = re.sub(r"<.*?>", " ", text)
|
||||
|
||||
# Replace consecutive white space to a single white space
|
||||
text = re.sub(r" +", " ", text)
|
||||
else:
|
||||
text = sample[self.sample_field_name]
|
||||
|
||||
if self.encode_return_dict:
|
||||
output = self.encode(text)
|
||||
tokens = output["input_ids"]
|
||||
masks = output["attention_mask"]
|
||||
else:
|
||||
tokens, masks = self.encode(text)
|
||||
|
||||
# Get a corresponding label
|
||||
label = torch.tensor(sample[self.label_field_name], dtype=torch.long)
|
||||
return (tokens.squeeze(0), masks.squeeze(0)), label.squeeze(0)
|
||||
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""
|
||||
Returns the number of samples in the dataset.
|
||||
|
||||
Returns:
|
||||
int: Dataset length.
|
||||
"""
|
||||
return len(self.dataset)
|
||||
@@ -0,0 +1,132 @@
|
||||
from typing import Tuple
|
||||
import re
|
||||
from html import unescape
|
||||
import torch
|
||||
from torch.utils.data import Dataset
|
||||
from datasets import load_dataset # Hugging Face (not PyTorch)
|
||||
import scipy
|
||||
import numpy as np
|
||||
# Monkey-patch gensim as it relies on scipy's deprecated triu method
|
||||
# /gensim/matutils.py has this line which causes ImportError:
|
||||
# from scipy.linalg import get_blas_funcs, triu
|
||||
# TODO: Monitor gensim and fix this if upgrade is made
|
||||
scipy.linalg.triu = np.triu
|
||||
import gensim.downloader as api
|
||||
|
||||
class HfGloveDataset(Dataset):
|
||||
"""
|
||||
Hugging Face Dataset wrapper with word embedding support.
|
||||
|
||||
pragma: skip_doc
|
||||
"""
|
||||
def __init__(self,
|
||||
dataset_name: str,
|
||||
split: str,
|
||||
glove_dim: int = 100,
|
||||
max_seq_len: int = 256,
|
||||
sample_field_name: str = "text",
|
||||
label_field_name: str = "label") -> None:
|
||||
"""
|
||||
Initializes the dataset by loading Hugging Face dataset and GloVe vectors.
|
||||
|
||||
Args:
|
||||
dataset_name (str): The name of the Hugging Face dataset.
|
||||
split (str): The dataset split (e.g., 'train').
|
||||
glove_dim (int): Dimensionality of GloVe embeddings.
|
||||
max_seq_len (int): Maximum sequence length.
|
||||
sample_field_name (str): Field name for text samples.
|
||||
label_field_name (str): Field name for labels.
|
||||
"""
|
||||
self.dataset = load_dataset(dataset_name, split=split)
|
||||
self.glove_dim = glove_dim
|
||||
self.max_seq_len = max_seq_len
|
||||
self.sample_field_name = sample_field_name
|
||||
self.label_field_name = label_field_name
|
||||
|
||||
# Load GloVe from gensim
|
||||
glove_name = f"glove-wiki-gigaword-{glove_dim}"
|
||||
self.glove = api.load(glove_name)
|
||||
self.unk_vector = torch.zeros(glove_dim)
|
||||
|
||||
def tokenize(self, text: str) -> list[str]:
|
||||
"""
|
||||
Tokenizes raw text into a list of lowercase word tokens.
|
||||
It removes HTML tags, decodes HTML entities, strips punctuation
|
||||
(while keeping letters and numbers), and splits on whitespace.
|
||||
|
||||
Args:
|
||||
text (str): Raw text input (may include HTML and punctuation).
|
||||
|
||||
Returns:
|
||||
list[str]: List of clean, lowercase word tokens.
|
||||
"""
|
||||
# Decode HTML entities like "&", "<", etc. to their actual characters
|
||||
text = unescape(text)
|
||||
|
||||
# Remove HTML tags (like <br />, <p>, etc.)
|
||||
# The regex "<.*?>" uses .*? (non-greedy match) to ensure it matches one tag at a time,
|
||||
# instead of greedily removing everything between the first '<' and last '>'
|
||||
text = re.sub(r"<.*?>", " ", text)
|
||||
|
||||
# Remove all characters except letters, digits, and whitespace
|
||||
# This strips punctuation like "!", ".", "(", but keeps words and numbers
|
||||
text = re.sub(r"[^a-zA-Z0-9\s]", " ", text)
|
||||
|
||||
# Replace consecutive white space to a single white space
|
||||
text = re.sub(r" +", " ", text)
|
||||
|
||||
# Convert to lowercase and split by whitespace to get token list
|
||||
return text.lower().split()
|
||||
|
||||
|
||||
def vectorize(self, tokens: list[str]) -> torch.Tensor:
|
||||
"""
|
||||
Converts tokens to a fixed-length tensor of word vectors.
|
||||
|
||||
Args:
|
||||
tokens (list[str]): A list of word tokens.
|
||||
|
||||
Returns:
|
||||
torch.Tensor: A tensor of shape (max_seq_len, glove_dim).
|
||||
"""
|
||||
vectors = []
|
||||
for token in tokens[:self.max_seq_len]:
|
||||
if token in self.glove:
|
||||
vec = torch.tensor(self.glove[token], dtype=torch.float32)
|
||||
else:
|
||||
vec = self.unk_vector
|
||||
vectors.append(vec)
|
||||
|
||||
if len(vectors) < self.max_seq_len:
|
||||
pad = [torch.zeros(self.glove_dim) for _ in range(self.max_seq_len - len(vectors))]
|
||||
vectors.extend(pad)
|
||||
|
||||
return torch.stack(vectors)
|
||||
|
||||
def __getitem__(self, idx: int) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
"""
|
||||
Retrieves the (embedded sequence, label) pair at the given index.
|
||||
|
||||
Args:
|
||||
idx (int): Index of the item.
|
||||
|
||||
Returns:
|
||||
Tuple[torch.Tensor, torch.Tensor]: Embedding tensor and label tensor.
|
||||
"""
|
||||
# Get a text sample from database and convert to a word embedding
|
||||
sample = self.dataset[idx]
|
||||
tokens = self.tokenize(sample[self.sample_field_name])
|
||||
emb_tensor = self.vectorize(tokens)
|
||||
|
||||
# Get a corresponding label
|
||||
label = torch.tensor(sample[self.label_field_name], dtype=torch.long)
|
||||
return emb_tensor, label
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""
|
||||
Returns the number of samples in the dataset.
|
||||
|
||||
Returns:
|
||||
int: Dataset length.
|
||||
"""
|
||||
return len(self.dataset)
|
||||
@@ -0,0 +1,134 @@
|
||||
from typing import Any, Dict, Union, List, Tuple, Callable
|
||||
import torch
|
||||
from transformers import AutoTokenizer
|
||||
|
||||
|
||||
class HfTokenizerEncode:
|
||||
"""
|
||||
Hugging Face tokenizer wrapper for encoding text into token ID tensors.
|
||||
|
||||
Args:
|
||||
model_name (str): Name of the model.
|
||||
padding (bool): Whether to pad sequences to equal length.
|
||||
padding_method (str): 'longest' or 'max_length'.
|
||||
- 'longest': pad to the longest sequence length after truncation in this batch.
|
||||
- 'max_length': pad all sequences to the fixed length `max_length`.
|
||||
truncation (bool): Whether to truncate sequences to `max_length`.
|
||||
max_length (int): The target length for truncation or padding.
|
||||
If you specify 0, it uses model's maximum input length.
|
||||
|
||||
Returns:
|
||||
PTCALLABLE: A tuple containing a callable that accepts a sentence or list of
|
||||
sentences and returns token ID tensor(s) and attention mask(s).
|
||||
|
||||
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": {
|
||||
"model_name": ("STRING", {"default":"", "multiline":False}),
|
||||
"padding": ("BOOLEAN", {"default": True}),
|
||||
"padding_method": (("max_length", "longest"),),
|
||||
"truncation": ("BOOLEAN", {"default": True}),
|
||||
"max_length": ("INT", {"default": 512, "min": 0, "max": 1_000_000}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES: Tuple[str] = ("PTCALLABLE",)
|
||||
FUNCTION: str = "f"
|
||||
CATEGORY: str = "Data Analysis"
|
||||
|
||||
def encode(
|
||||
self,
|
||||
sentence: Union[str, List[str]],
|
||||
) -> Dict[str, torch.Tensor]:
|
||||
|
||||
"""
|
||||
Encodes a string or list of strings into token ID tensors.
|
||||
|
||||
Args:
|
||||
sentence (Union[str, List[str]]): Input sentence(s).
|
||||
|
||||
Returns:
|
||||
Dict:
|
||||
input_ids: token IDs
|
||||
attention_mask: Masks. 1 is non-padding, 0 is padding.
|
||||
"""
|
||||
if self.padding:
|
||||
if self.max_length == 0:
|
||||
retval = self.tokenizer(
|
||||
sentence,
|
||||
padding=self.padding_method,
|
||||
truncation=self.truncation,
|
||||
return_tensors="pt"
|
||||
)
|
||||
else:
|
||||
retval = self.tokenizer(
|
||||
sentence,
|
||||
padding=self.padding_method,
|
||||
truncation=self.truncation,
|
||||
max_length=self.max_length,
|
||||
return_tensors="pt"
|
||||
)
|
||||
else: # no padding
|
||||
if self.max_length == 0:
|
||||
retval = self.tokenizer(
|
||||
sentence,
|
||||
padding=False,
|
||||
truncation=self.truncation,
|
||||
return_tensors="pt"
|
||||
)
|
||||
else:
|
||||
retval = self.tokenizer(
|
||||
sentence,
|
||||
padding=False,
|
||||
truncation=self.truncation,
|
||||
return_tensors="pt"
|
||||
)
|
||||
return retval
|
||||
|
||||
def f(
|
||||
self,
|
||||
model_name: str,
|
||||
padding: bool,
|
||||
padding_method: str,
|
||||
truncation: bool,
|
||||
max_length: int
|
||||
) -> Tuple[Callable[[Union[str, List[str]]], Tuple[Union[torch.Tensor, List[torch.Tensor]], Union[torch.Tensor, List[torch.Tensor]]]]]:
|
||||
"""
|
||||
Instantiates a Hugging Face tokenizer, binds configuration and returns a callable encoder function.
|
||||
- Supports includes:
|
||||
- BertTokenizer
|
||||
- RobertaTokenizer
|
||||
- DistilBertTokenizer
|
||||
- AlbertTokenizer
|
||||
- ElectraTokenizer
|
||||
|
||||
Args:
|
||||
model_name (str): Name of the model.
|
||||
padding (bool): Whether to pad sequences to equal length.
|
||||
padding_method (str): 'longest' or 'max_length'.
|
||||
- 'longest': pad to the longest sequence length after truncation in this batch.
|
||||
- 'max_length': pad all sequences to the fixed length `max_length`.
|
||||
truncation (bool): Whether to truncate sequences to `max_length`.
|
||||
max_length (int): The target length for truncation or padding.
|
||||
If you specify 0, it uses model's maximum input length.
|
||||
|
||||
Returns:
|
||||
Tuple[Callable]: A tuple containing a callable for the tokenizer function.
|
||||
"""
|
||||
|
||||
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
|
||||
self.padding = padding
|
||||
self.padding_method = padding_method
|
||||
self.truncation = truncation
|
||||
self.max_length = max_length
|
||||
return (self.encode,)
|
||||
@@ -114,9 +114,6 @@ class PtTrainClassificationTransformerModel:
|
||||
best_val_loss = None
|
||||
rounds_since_best_val_loss = 0
|
||||
|
||||
all_preds = []
|
||||
all_labels = []
|
||||
|
||||
with torch.inference_mode(False):
|
||||
train_loss = []
|
||||
val_loss = []
|
||||
@@ -157,6 +154,8 @@ class PtTrainClassificationTransformerModel:
|
||||
|
||||
if val_loader:
|
||||
with torch.no_grad():
|
||||
all_preds = []
|
||||
all_labels = []
|
||||
model.train(False) # TODO: Change to model.*e*v*a*l() once Comfy's security checker is fixed.
|
||||
total_loss = 0
|
||||
total_samples = len(val_loader.dataset)
|
||||
@@ -180,7 +179,7 @@ class PtTrainClassificationTransformerModel:
|
||||
|
||||
if classification_metrics and y.squeeze().dim() == 1 :
|
||||
# Accuracy collection for binary classification
|
||||
preds = (y_hat > 0.5).int().detach().cpu().numpy()
|
||||
preds = (F.sigmoid(y_hat) > 0.5).int().detach().cpu().numpy()
|
||||
labels = y.int().detach().cpu().numpy()
|
||||
all_preds.extend(preds)
|
||||
all_labels.extend(labels)
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
import os
|
||||
import sys
|
||||
from typing import Any, Dict, Optional, Tuple, Union
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from sklearn.metrics import accuracy_score
|
||||
from .utils import set_requires_grad
|
||||
|
||||
class PtTrainFineTuneClassificationTransformerModel:
|
||||
"""
|
||||
Fine-tunes a classification Transformer model using a given dataset, loss function, optimizer, and number of epochs with learning rate decay.
|
||||
|
||||
Args:
|
||||
model (PTMODEL): The PyTorch model to be trained.
|
||||
train_loader (torch.utils.data.DataLoader): DataLoader containing the training dataset.
|
||||
optimizer (PTOPTIMIZER): Optimizer used for training. Only Adam and AdamW are supported.
|
||||
loss (PTLOSS): The loss class.
|
||||
epochs (int): Number of training epochs.
|
||||
freeze_pretrained_module_epochs (int) : Number of epochs to freeze pretrained module.
|
||||
use_gpu (bool): Whether to use GPU for training.
|
||||
early_stopping (bool): Whether to turn on early stopping
|
||||
early_stopping_rounds (int): Specifies the number of rounds to monitor the evaluation loss when early_stopping is enabled and validation data is specified. If the loss does not decrease compared to the current best loss within this period, training will be stopped.
|
||||
output_best_val_model (bool): Whether to outputs the model with the best val loss or the model from the last epoch.
|
||||
classification_metrics (bool): If True and if val_loader is set, print out classification metrics. This is only valid for classification model training.
|
||||
val_loader (PTDATALOADER): DataLoader for validation dataset (optional).
|
||||
|
||||
category: PyTorch wrapper - Training
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> Dict[str, Any]:
|
||||
"""
|
||||
Defines the input types required for training the model.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: A dictionary specifying required and optional input types.
|
||||
"""
|
||||
return {
|
||||
"required": {
|
||||
"model": ("PTMODEL", {}),
|
||||
"train_loader": ("PTDATALOADER", {}),
|
||||
"optimizer": ("PTOPTIMIZER", {}),
|
||||
"loss_function": ("PTLOSS", {}),
|
||||
"epochs": ("INT", {"default":2, "min":1, "max":1e6}),
|
||||
"freeze_pretrained_module_epochs": ("INT", {"default":1, "min":1, "max":1e6}),
|
||||
"use_gpu": ("BOOLEAN", {"default":False}),
|
||||
"early_stopping": ("BOOLEAN", {"default":False}),
|
||||
"early_stopping_rounds": ("INT", {"default":10, "min":1, "max":1000}),
|
||||
"output_best_val_model": ("BOOLEAN", {"default":True}),
|
||||
"classification_metrics": ("BOOLEAN", {"default":True})
|
||||
},
|
||||
"optional": {
|
||||
"scheduler": ("PTLRSCHEDULER", {}),
|
||||
"val_loader": ("PTDATALOADER", {})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_NAMES: tuple = ("Model", "train loss", "val loss")
|
||||
RETURN_TYPES: tuple = ("PTMODEL","TENSOR","TENSOR")
|
||||
FUNCTION: str = "f"
|
||||
CATEGORY: str = "Training"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kw):
|
||||
return float("NaN")
|
||||
|
||||
def f(self,
|
||||
model: nn.Module,
|
||||
train_loader: torch.utils.data.DataLoader,
|
||||
optimizer: torch.optim.Optimizer,
|
||||
loss_function: nn.Module,
|
||||
epochs: int,
|
||||
freeze_pretrained_module_epochs,
|
||||
use_gpu: bool,
|
||||
early_stopping: bool,
|
||||
early_stopping_rounds: int,
|
||||
output_best_val_model:bool,
|
||||
classification_metrics:bool,
|
||||
val_loader: Optional[torch.utils.data.DataLoader] = None,
|
||||
) -> Tuple[nn.Module, torch.Tensor, torch.Tensor]:
|
||||
"""
|
||||
Trains an Transformer model.
|
||||
|
||||
Args:
|
||||
model (torch.nn.Module): The PyTorch model to be trained.
|
||||
train_loader (torch.utils.data.DataLoader): DataLoader containing the training dataset.
|
||||
optimizer (PTOPTIMIZER): Optimizer used for training. Only Adam and AdamW are supported.
|
||||
loss (torch.nn.Module): The loss class.
|
||||
epochs (int): Number of training epochs.
|
||||
freeze_pretrained_module_epochs (int) : Number of epochs to freeze pretrained module.
|
||||
use_gpu (bool): Whether to use GPU for training.
|
||||
early_stopping (bool): Whether to turn on early stopping
|
||||
early_stopping_rounds (int): Specifies the number of rounds to monitor the evaluation loss when early_stopping is enabled and validation data is specified. If the loss does not decrease compared to the current best loss within this period, training will be stopped.
|
||||
output_best_val_model (bool): Whether to outputs the model with the best val loss or the model from the last epoch.
|
||||
classification_metrics (bool): If True and if val_loader is set, print out classification metrics. This is only valid for classification model training.
|
||||
val_loader (Optional[torch.utils.data.DataLoader]): DataLoader for validation dataset (optional).
|
||||
|
||||
Returns:
|
||||
Tuple[torch.nn.Module, torch.Tensor, torch.Tensor]: A tuple containing the trained model,
|
||||
training loss history, and validation loss history.
|
||||
"""
|
||||
checkpoint_dir = os.path.realpath(
|
||||
os.path.join(os.path.dirname(__file__), "..", "..", "models", "tmp"))
|
||||
print(f"Checkpoint tmp directory is set to {checkpoint_dir}")
|
||||
if os.path.exists(checkpoint_dir) is False:
|
||||
os.makedirs(checkpoint_dir)
|
||||
print(f"Created {checkpoint_dir}")
|
||||
best_checkpoint_path = os.path.join(checkpoint_dir, "best_model.pt")
|
||||
|
||||
best_val_loss = None
|
||||
rounds_since_best_val_loss = 0
|
||||
|
||||
with torch.inference_mode(False):
|
||||
train_loss = []
|
||||
val_loss = []
|
||||
if use_gpu:
|
||||
model.to("cuda")
|
||||
|
||||
# Train the model
|
||||
for epoch in range(epochs):
|
||||
model.train()
|
||||
total_loss = 0
|
||||
total_samples = len(train_loader.dataset)
|
||||
|
||||
if epoch < freeze_pretrained_module_epochs:
|
||||
set_requires_grad(model.llm_model, False) # freeze encoder
|
||||
elif epoch == freeze_pretrained_module_epochs:
|
||||
set_requires_grad(model.llm_model, True) # unfreeze encoder
|
||||
|
||||
# Rebuild optimizer with separate LR for pretrained encoder and classifier head
|
||||
if isinstance(optimizer, torch.optim.Adam):
|
||||
lr = optimizer.defaults.get("lr", 1e-3)
|
||||
betas = optimizer.defaults.get("betas", (0.9, 0.999))
|
||||
eps = optimizer.defaults.get("eps", 1e-8)
|
||||
weight_decay = optimizer.defaults.get("weight_decay", 0)
|
||||
|
||||
optimizer = torch.optim.Adam([
|
||||
{"params": model.llm_model.parameters(), "lr": lr * 0.2},
|
||||
{"params": model.linear.parameters(), "lr": lr}
|
||||
], lr=lr, betas=betas, eps=eps, weight_decay=weight_decay)
|
||||
|
||||
elif isinstance(optimizer, torch.optim.AdamW):
|
||||
lr = optimizer.defaults.get("lr", 1e-3)
|
||||
betas = optimizer.defaults.get("betas", (0.9, 0.999))
|
||||
eps = optimizer.defaults.get("eps", 1e-8)
|
||||
weight_decay = optimizer.defaults.get("weight_decay", 0)
|
||||
|
||||
optimizer = torch.optim.AdamW([
|
||||
{"params": model.llm_model.parameters(), "lr": lr * 0.2},
|
||||
{"params": model.linear.parameters(), "lr": lr}
|
||||
], lr=lr, betas=betas, eps=eps, weight_decay=weight_decay)
|
||||
|
||||
else:
|
||||
raise ValueError("Only Adam and AdamW are supported for optimizer re-instantiation.")
|
||||
|
||||
for x, y in train_loader:
|
||||
if use_gpu:
|
||||
if isinstance(x, list):
|
||||
x = [e.to("cuda") for e in x]
|
||||
else:
|
||||
x = x.to("cuda")
|
||||
y = y.to("cuda")
|
||||
optimizer.zero_grad()
|
||||
|
||||
y_hat = model(x)
|
||||
if y.dim() == 1:
|
||||
y = torch.unsqueeze(y, dim=-1)
|
||||
y = y.to(torch.float32)
|
||||
|
||||
loss = loss_function(y_hat, y)
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
batch_size = y.size(0)
|
||||
batch_loss = loss.item() * batch_size
|
||||
total_loss += batch_loss
|
||||
|
||||
epoch_loss = total_loss / total_samples
|
||||
print(f"Epoch (train) {epoch+1}/{epochs}, Loss: {epoch_loss:.4f}")
|
||||
train_loss.append(epoch_loss)
|
||||
|
||||
if val_loader:
|
||||
with torch.no_grad():
|
||||
all_preds = []
|
||||
all_labels = []
|
||||
|
||||
model.train(False) # TODO: Change to model.*e*v*a*l() once Comfy's security checker is fixed.
|
||||
total_loss = 0
|
||||
total_samples = len(val_loader.dataset)
|
||||
for x, y in val_loader:
|
||||
if use_gpu:
|
||||
if isinstance(x, list):
|
||||
x = [e.to("cuda") for e in x]
|
||||
else:
|
||||
x = x.to("cuda")
|
||||
y = y.to("cuda")
|
||||
|
||||
y_hat = model(x)
|
||||
if y.dim() == 1:
|
||||
y = torch.unsqueeze(y, dim=-1)
|
||||
y = y.to(torch.float32)
|
||||
|
||||
loss = loss_function(y_hat, y)
|
||||
batch_size = y.size(0)
|
||||
batch_loss = loss.item() * batch_size
|
||||
total_loss += batch_loss
|
||||
|
||||
if classification_metrics and y.squeeze().dim() == 1 :
|
||||
# Accuracy collection for binary classification
|
||||
preds = (F.sigmoid(y_hat) > 0.5).int().detach().cpu().numpy()
|
||||
labels = y.int().detach().cpu().numpy()
|
||||
all_preds.extend(preds)
|
||||
all_labels.extend(labels)
|
||||
|
||||
epoch_loss = total_loss / total_samples
|
||||
print(f"Epoch (val) {epoch+1}/{epochs}, Loss: {epoch_loss:.4f}")
|
||||
val_loss.append(epoch_loss)
|
||||
|
||||
if classification_metrics:
|
||||
if len(all_labels):
|
||||
# Compute accuracy
|
||||
acc = accuracy_score(all_labels, all_preds)
|
||||
print(f"Validation Accuracy: {acc:.4f}")
|
||||
else:
|
||||
print("Accuracy was not computed as the label is not scalar.")
|
||||
|
||||
if best_val_loss is None or epoch_loss < best_val_loss:
|
||||
best_val_loss = epoch_loss
|
||||
rounds_since_best_val_loss = 0
|
||||
torch.save(model.state_dict(), best_checkpoint_path)
|
||||
print(f"Updated best val loss. Saved the model as {best_checkpoint_path}")
|
||||
else:
|
||||
rounds_since_best_val_loss += 1
|
||||
if early_stopping:
|
||||
if rounds_since_best_val_loss >= early_stopping_rounds:
|
||||
print(f"Val loss did not improve in {early_stopping_rounds} rounds. Stopping training.")
|
||||
break
|
||||
|
||||
if use_gpu:
|
||||
model.to("cpu")
|
||||
|
||||
if val_loader:
|
||||
if epoch_loss > best_val_loss and output_best_val_model:
|
||||
sd = torch.load(best_checkpoint_path, weights_only=True)
|
||||
model.load_state_dict(sd)
|
||||
print("Loaded the best model for output")
|
||||
else:
|
||||
print("Using the final epoch model for output.")
|
||||
|
||||
return (model,
|
||||
torch.tensor(train_loss, dtype=torch.float32),
|
||||
torch.tensor(val_loss, dtype=torch.float32))
|
||||
@@ -127,9 +127,6 @@ class PtTrainRNNModel:
|
||||
best_val_loss = None
|
||||
rounds_since_best_val_loss = 0
|
||||
|
||||
all_preds = []
|
||||
all_labels = []
|
||||
|
||||
with torch.inference_mode(False):
|
||||
train_loss = []
|
||||
val_loss = []
|
||||
@@ -190,6 +187,8 @@ class PtTrainRNNModel:
|
||||
|
||||
if val_loader:
|
||||
with torch.no_grad():
|
||||
all_preds = []
|
||||
all_labels = []
|
||||
model.train(False) # TODO: Change to model.*e*v*a*l() once Comfy's security checker is fixed.
|
||||
total_loss = 0
|
||||
total_samples = len(val_loader.dataset)
|
||||
@@ -228,7 +227,7 @@ class PtTrainRNNModel:
|
||||
|
||||
if classification_metrics and y.squeeze().dim() == 1 :
|
||||
# Accuracy collection for binary classification
|
||||
preds = (y_hat > 0.5).int().detach().cpu().numpy()
|
||||
preds = (F.sigmoid(y_hat) > 0.5).int().detach().cpu().numpy()
|
||||
labels = y.int().detach().cpu().numpy()
|
||||
all_preds.extend(preds)
|
||||
all_labels.extend(labels)
|
||||
|
||||
@@ -35,7 +35,7 @@ class PtnGRU:
|
||||
"num_layers": ("INT", {"default": 1, "min": 1, "max": 1e3}),
|
||||
"bias": ("BOOLEAN", {"default": True}),
|
||||
"batch_first": ("BOOLEAN", {"default": True}),
|
||||
"dropout": ("FLOAT", {"default": 0, "min": 0, "max": 1}),
|
||||
"dropout": ("FLOAT", {"default": 0, "min": 0, "max": 1, "step": 0.001}),
|
||||
"bidirectional": ("BOOLEAN", {"default": False}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ class PtnGRULinear:
|
||||
"num_layers": ("INT", {"default": 1, "min": 1, "max": 1e3}),
|
||||
"bias": ("BOOLEAN", {"default": True}),
|
||||
"batch_first": ("BOOLEAN", {"default": True}),
|
||||
"dropout": ("FLOAT", {"default": 0, "min": 0, "max": 1}),
|
||||
"dropout": ("FLOAT", {"default": 0, "min": 0, "max": 1, "step": 0.001}),
|
||||
"bidirectional": ("BOOLEAN", {"default": False}),
|
||||
"linear_output_size": ("INT", {"default": 1, "min": 1, "max": 1e6}),
|
||||
"linear_bias": ("BOOLEAN", {"default": True}),
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
from typing import Any, Dict, Tuple
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from .ptn_hf_fine_tuned_classification_model_def import HfFineTunedClassificationModel
|
||||
|
||||
class PtnHfFineTunedClassificationModel:
|
||||
"""
|
||||
A binary classification model containing a Hugging Face pretrained Transformer model.
|
||||
|
||||
Args:
|
||||
model_name (str): Name or path of the pretrained Hugging Face model.
|
||||
use_mean_pooling (bool): If True, average token embeddings using the attention mask.
|
||||
Otherwise, use pooler output or CLS token embedding.
|
||||
dropout (float): Dropout rate applied before the final linear layer.
|
||||
Set to 0.0 to disable dropout. Must be ≥ 0.
|
||||
|
||||
category: PyTorch wrapper - Model
|
||||
"""
|
||||
|
||||
@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": {
|
||||
"model_name": ("STRING", {"default": "", "multiline": False}),
|
||||
"use_mean_pooling": ("BOOLEAN", {"default": True}),
|
||||
"dropout": ("FLOAT", {"default": 0, "min": 0, "max": 1, "step": 0.001})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES: Tuple[str] = ("PTMODEL",)
|
||||
FUNCTION: str = "f"
|
||||
CATEGORY: str = "Training"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kw):
|
||||
return float("NaN")
|
||||
|
||||
def f(self,
|
||||
model_name: str,
|
||||
use_mean_pooling: bool,
|
||||
dropout: float
|
||||
) -> Tuple[nn.Module]:
|
||||
"""
|
||||
Instantiates a binary classification model containing a Hugging Face pretrained Transformer model.
|
||||
|
||||
Args:
|
||||
model_name (str): Name or path of the pretrained Hugging Face model.
|
||||
use_mean_pooling (bool): If True, average token embeddings using the attention mask.
|
||||
Otherwise, use pooler output or CLS token embedding.
|
||||
dropout (float): Dropout rate applied before the final linear layer.
|
||||
Set to 0.0 to disable dropout. Must be ≥ 0.
|
||||
|
||||
Returns:
|
||||
Tuple[nn.Module]: A tuple containing the instantiated PyTorch model.
|
||||
"""
|
||||
with torch.inference_mode(False):
|
||||
model = HfFineTunedClassificationModel(
|
||||
model_name=model_name,
|
||||
use_mean_pooling=use_mean_pooling,
|
||||
dropout=dropout
|
||||
)
|
||||
|
||||
return (model,)
|
||||
@@ -0,0 +1,92 @@
|
||||
from typing import List, Dict, Any, Tuple, Type
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from transformers import AutoConfig, PreTrainedModel
|
||||
|
||||
class HfFineTunedClassificationModel(nn.Module):
|
||||
"""Generic transformer-based binary classification model.
|
||||
|
||||
Args:
|
||||
model_name (str): Name or path of the pretrained Hugging Face model.
|
||||
use_mean_pooling (bool): If True, average token embeddings using the attention mask.
|
||||
Otherwise, use pooler output or CLS token embedding.
|
||||
dropout (float): Dropout rate applied before the final linear layer.
|
||||
Set to 0.0 to disable dropout. Must be ≥ 0.
|
||||
|
||||
pragma: skip_doc
|
||||
"""
|
||||
|
||||
MODEL_CLASS_PREFIXES: List[Tuple[str, str]] = [
|
||||
("bert-base-uncased", "BertModel"),
|
||||
("roberta-base", "RobertaModel"),
|
||||
("distilbert-base-uncased", "DistilBertModel"),
|
||||
("albert-base-v2", "AlbertModel"),
|
||||
("google/electra-base-discriminator", "ElectraModel"),
|
||||
]
|
||||
|
||||
def __init__(self,
|
||||
model_name: str,
|
||||
use_mean_pooling: bool = False,
|
||||
dropout: float = 0.1):
|
||||
super().__init__()
|
||||
if dropout < 0.0:
|
||||
raise ValueError(f"Dropout must be non-negative, got {dropout}")
|
||||
|
||||
self.config = AutoConfig.from_pretrained(model_name)
|
||||
model_class = self._resolve_model_class(model_name)
|
||||
self.llm_model: PreTrainedModel = model_class.from_pretrained(model_name, config=self.config)
|
||||
|
||||
self.hidden_size = self.config.hidden_size
|
||||
self.use_mean_pooling = use_mean_pooling
|
||||
self.linear = nn.Linear(self.hidden_size, 1)
|
||||
|
||||
self.use_dropout = dropout > 0.0
|
||||
if self.use_dropout:
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
|
||||
# Initialize linear weight
|
||||
nn.init.xavier_uniform_(self.linear.weight)
|
||||
if self.linear.bias is not None:
|
||||
nn.init.zeros_(self.linear.bias)
|
||||
|
||||
def _resolve_model_class(self, model_name: str) -> Type[PreTrainedModel]:
|
||||
for prefix, class_name in self.MODEL_CLASS_PREFIXES:
|
||||
if model_name.startswith(prefix):
|
||||
module = __import__("transformers", fromlist=[class_name])
|
||||
return getattr(module, class_name)
|
||||
raise ValueError(f"Model '{model_name}' is not supported.")
|
||||
|
||||
def forward(self, inputs:Tuple[torch.Tensor]) -> torch.Tensor:
|
||||
"""
|
||||
Runs a forward pass through the transformer model followed by optional dropout and a classification head.
|
||||
|
||||
Args:
|
||||
inputs (Tuple[torch.Tensor]): Tuple of below tensors:
|
||||
input_ids (torch.Tensor): Token IDs of shape (batch_size, seq_len).
|
||||
attention_mask (torch.Tensor): Attention mask of shape (batch_size, seq_len).
|
||||
|
||||
Returns:
|
||||
torch.Tensor: Raw classification logits of shape (batch_size, 1).
|
||||
"""
|
||||
input_ids, attention_mask = inputs
|
||||
outputs = self.llm_model(input_ids=input_ids, attention_mask=attention_mask)
|
||||
|
||||
if self.use_mean_pooling:
|
||||
last_hidden = outputs.last_hidden_state
|
||||
# Expand the mask across the hidden dimension
|
||||
# e.g. if hidden size is 768, the (seq_len,) mask becomes (seq_len, 768) per sample.
|
||||
input_mask_expanded = attention_mask.unsqueeze(-1).expand(last_hidden.size())
|
||||
sum_embeddings = torch.sum(last_hidden * input_mask_expanded, dim=1)
|
||||
mask_adjusted_count = torch.clamp(input_mask_expanded.sum(1), min=1e-9)
|
||||
x = sum_embeddings / mask_adjusted_count
|
||||
elif hasattr(outputs, "pooler_output") and outputs.pooler_output is not None:
|
||||
x = outputs.pooler_output
|
||||
else: # Grab the first feature in the sequence
|
||||
x = outputs.last_hidden_state[:, 0, :]
|
||||
|
||||
assert x.dim() == 2
|
||||
|
||||
if self.use_dropout:
|
||||
x = self.dropout(x)
|
||||
x = self.linear(x)
|
||||
return x
|
||||
@@ -36,7 +36,7 @@ class PtnLSTM:
|
||||
"num_layers": ("INT", {"default": 1, "min": 1, "max": 1e3}),
|
||||
"bias": ("BOOLEAN", {"default": True}),
|
||||
"batch_first": ("BOOLEAN", {"default": True}),
|
||||
"dropout": ("FLOAT", {"default": 0, "min": 0, "max": 1}),
|
||||
"dropout": ("FLOAT", {"default": 0, "min": 0, "max": 1, "step": 0.001}),
|
||||
"bidirectional": ("BOOLEAN", {"default": False}),
|
||||
"proj_size": ("INT", {"default": 0, "min": 0, "max": 1e6}),
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ class PtnLSTMLinear:
|
||||
"num_layers": ("INT", {"default": 1, "min": 1, "max": 1e3}),
|
||||
"bias": ("BOOLEAN", {"default": True}),
|
||||
"batch_first": ("BOOLEAN", {"default": True}),
|
||||
"dropout": ("FLOAT", {"default": 0, "min": 0, "max": 1}),
|
||||
"dropout": ("FLOAT", {"default": 0, "min": 0, "max": 1, "step": 0.001}),
|
||||
"bidirectional": ("BOOLEAN", {"default": False}),
|
||||
"proj_size": ("INT", {"default": 0, "min": 0, "max": 1e6}),
|
||||
"linear_output_size": ("INT", {"default": 1, "min": 1, "max": 1e6}),
|
||||
|
||||
@@ -37,7 +37,7 @@ class PtnRNN:
|
||||
"nonlinearity": (("tanh", "relu"),),
|
||||
"bias": ("BOOLEAN", {"default": True}),
|
||||
"batch_first": ("BOOLEAN", {"default": True}),
|
||||
"dropout": ("FLOAT", {"default": 0, "min": 0, "max": 1}),
|
||||
"dropout": ("FLOAT", {"default": 0, "min": 0, "max": 1, "step": 0.001}),
|
||||
"bidirectional": ("BOOLEAN", {"default": False}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ class PtnRNNLinear:
|
||||
"nonlinearity": (("tanh", "relu"),),
|
||||
"bias": ("BOOLEAN", {"default": True}),
|
||||
"batch_first": ("BOOLEAN", {"default": True}),
|
||||
"dropout": ("FLOAT", {"default": 0, "min": 0, "max": 1}),
|
||||
"dropout": ("FLOAT", {"default": 0, "min": 0, "max": 1, "step": 0.001}),
|
||||
"bidirectional": ("BOOLEAN", {"default": False}),
|
||||
"linear_output_size": ("INT", {"default": 1, "min": 1, "max": 1e6}),
|
||||
"linear_bias": ("BOOLEAN", {"default": True}),
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import os
|
||||
import sys
|
||||
from typing import Any, Dict, Tuple, Callable
|
||||
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 hugging_face_wrapper.hf_dataset_with_token_encode import HfDatasetWithTokenEncode
|
||||
|
||||
class PtvHfDatasetWithTokenEncode:
|
||||
"""
|
||||
@@ -13,6 +19,7 @@ class PtvHfDatasetWithTokenEncode:
|
||||
label_field_name (str): Field name for labels.
|
||||
encode (PTCALLABLE): The reference to a token encoder function.
|
||||
remove_html_tags (bool): Remove html tags in text if True.
|
||||
encode_return_dict (bool): Encode function returns a Dict instead of a Tuple.
|
||||
|
||||
category: PyTorch wrapper - Training
|
||||
"""
|
||||
@@ -33,6 +40,7 @@ class PtvHfDatasetWithTokenEncode:
|
||||
"label_field_name": ("STRING", {"default": "label","multiline": False}),
|
||||
"encode": ("PTCALLABLE", {}),
|
||||
"remove_html_tags": ("BOOLEAN", {"default": False}),
|
||||
"encode_return_dict": ("BOOLEAN", {"default": False}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +58,8 @@ class PtvHfDatasetWithTokenEncode:
|
||||
sample_field_name: str,
|
||||
label_field_name: str,
|
||||
encode: Callable,
|
||||
remove_html_tags:bool) -> Tuple:
|
||||
remove_html_tags:bool,
|
||||
encode_return_dict:bool) -> Tuple:
|
||||
"""
|
||||
Loads a dataset from Hugging Face with specified parameters.
|
||||
|
||||
@@ -61,14 +70,11 @@ class PtvHfDatasetWithTokenEncode:
|
||||
label_field_name (str): Field name for labels.
|
||||
encode (Callable): The reference to a token encoder function.
|
||||
remove_html_tags (bool): Remove html tags in text if True.
|
||||
encode_return_dict (bool): Encode function returns a Dict instead of a Tuple.
|
||||
|
||||
Returns:
|
||||
Tuple: A tuple containing the dataset instance.
|
||||
"""
|
||||
# Keep import within this method so that relevant packages are imported
|
||||
# only when they are actually needed.
|
||||
from .hf_dataset_with_token_encode import HfDatasetWithTokenEncode
|
||||
|
||||
with torch.inference_mode(False):
|
||||
dc = HfDatasetWithTokenEncode(
|
||||
dataset_name=name,
|
||||
@@ -76,5 +82,6 @@ class PtvHfDatasetWithTokenEncode:
|
||||
sample_field_name=sample_field_name,
|
||||
label_field_name=label_field_name,
|
||||
encode=encode,
|
||||
remove_html_tags=remove_html_tags)
|
||||
remove_html_tags=remove_html_tags,
|
||||
encode_return_dict=encode_return_dict)
|
||||
return (dc,)
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import os
|
||||
import sys
|
||||
from typing import Any, Dict, Tuple
|
||||
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 hugging_face_wrapper.hf_glove_dataset import HfGloveDataset
|
||||
|
||||
class PtvHfGloveDataset:
|
||||
"""
|
||||
@@ -65,9 +71,6 @@ class PtvHfGloveDataset:
|
||||
Returns:
|
||||
Tuple: A tuple containing the dataset instance.
|
||||
"""
|
||||
# Keep import within this method so that relevant packages are imported
|
||||
# only when they are actually needed.
|
||||
from .hf_glove_dataset import HfGloveDataset
|
||||
|
||||
with torch.inference_mode(False):
|
||||
dc = HfGloveDataset(
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Optional, Tuple, Union, List
|
||||
import random
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
import torch.nn as nn
|
||||
|
||||
DTYPE_MAPPING = {
|
||||
"float32": torch.float32,
|
||||
@@ -237,3 +237,16 @@ def pad_truncate_sequence(
|
||||
return torch.stack(token_list), torch.stack(mask_list)
|
||||
else:
|
||||
return token_list, mask_list
|
||||
|
||||
|
||||
def set_requires_grad(module: nn.Module, requires_grad: bool) -> None:
|
||||
"""
|
||||
Enables or disables gradient computation for all parameters in a given module.
|
||||
|
||||
Args:
|
||||
module (nn.Module): The PyTorch module whose parameters should be modified.
|
||||
requires_grad (bool): If False, freezes the module (no gradients computed).
|
||||
If True, unfreezes the module (gradients computed).
|
||||
"""
|
||||
for param in module.parameters():
|
||||
param.requires_grad = requires_grad
|
||||
|
||||
+1
-1
@@ -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.4.0"
|
||||
version = "3.5.0"
|
||||
license = {file = "LICENSE"}
|
||||
|
||||
[project.urls]
|
||||
|
||||
@@ -4,5 +4,6 @@ seaborn>=0.13.2
|
||||
scipy>=1.14.1
|
||||
scikit-learn>=1.6.1
|
||||
datasets>=3.4.1
|
||||
transformers>=4.47.1
|
||||
gensim==4.3.2
|
||||
sentencepiece>=0.2.0
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
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 hugging_face_wrapper.hf_dataset_with_token_encode import HfDatasetWithTokenEncode
|
||||
from sentencepiece_wrapper.sp_load_model import SpLoadModel
|
||||
from sentencepiece_wrapper.sp_encode import SpEncode
|
||||
|
||||
|
||||
class TestHfDatasetWithTokenEncode(unittest.TestCase):
|
||||
"""
|
||||
Tests Hugging Face Dataset wrapper with custom token_encode function.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test instance."""
|
||||
self.load_node = SpLoadModel()
|
||||
self.sp = self.load_node.f("spiece.model")[0]
|
||||
self.encode_node = SpEncode()
|
||||
self.max_length=15
|
||||
encode = self.encode_node.f(
|
||||
spmodel=self.sp,
|
||||
padding=False,
|
||||
padding_method="longest",
|
||||
padding_value=0,
|
||||
truncation=True,
|
||||
max_length=self.max_length
|
||||
)[0]
|
||||
|
||||
self.ds = HfDatasetWithTokenEncode(
|
||||
"imdb", # dataset_name
|
||||
"train", #split
|
||||
encode=encode,
|
||||
)
|
||||
|
||||
self.ds_remove_html = HfDatasetWithTokenEncode(
|
||||
"imdb", # dataset_name
|
||||
"train", #split
|
||||
encode=encode,
|
||||
remove_html_tags=True
|
||||
)
|
||||
|
||||
def test_loading(self):
|
||||
it = iter(self.ds)
|
||||
(sample, mask), label = next(it)
|
||||
|
||||
# Compare text
|
||||
actual = sample.size()
|
||||
expected = torch.Size((self.max_length, ))
|
||||
self.assertEqual(expected, actual, f"expected {expected} and actual {actual} do not match.")
|
||||
|
||||
# Compare mask
|
||||
actual = mask.size()
|
||||
expected = torch.Size((self.max_length, ))
|
||||
self.assertEqual(expected, actual, f"expected {expected} and actual {actual} do not match.")
|
||||
|
||||
# Compare label
|
||||
actual = label
|
||||
expected = torch.tensor(0, dtype=torch.int64)
|
||||
self.assertEqual(expected, actual, f"expected {expected} and actual {actual} do not match.")
|
||||
|
||||
|
||||
def test_loading_remove_html(self):
|
||||
it = iter(self.ds_remove_html)
|
||||
(sample, mask), label = next(it)
|
||||
|
||||
# Compare text
|
||||
actual = sample.size()
|
||||
expected = torch.Size((self.max_length, ))
|
||||
self.assertEqual(expected, actual, f"expected {expected} and actual {actual} do not match.")
|
||||
|
||||
# Compare mask
|
||||
actual = mask.size()
|
||||
expected = torch.Size((self.max_length, ))
|
||||
self.assertEqual(expected, actual, f"expected {expected} and actual {actual} do not match.")
|
||||
|
||||
# Compare label
|
||||
actual = label
|
||||
expected = torch.tensor(0, dtype=torch.int64)
|
||||
self.assertEqual(expected, actual, f"expected {expected} and actual {actual} do not match.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,86 @@
|
||||
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 hugging_face_wrapper.hf_dataset_with_token_encode import HfDatasetWithTokenEncode
|
||||
from hugging_face_wrapper.hf_tokenizer_encode import HfTokenizerEncode
|
||||
|
||||
|
||||
class TestHfDatasetWithTokenEncode(unittest.TestCase):
|
||||
"""
|
||||
Tests Hugging Face Dataset wrapper with custom token_encode function.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test instance."""
|
||||
self.max_length=15
|
||||
encode = HfTokenizerEncode().f(
|
||||
model_name="distilbert-base-uncased",
|
||||
padding=True,
|
||||
padding_method="longest",
|
||||
truncation=True,
|
||||
max_length=self.max_length
|
||||
)[0]
|
||||
|
||||
self.ds = HfDatasetWithTokenEncode(
|
||||
"imdb", # dataset_name
|
||||
"train", #split
|
||||
encode=encode,
|
||||
encode_return_dict=True
|
||||
)
|
||||
|
||||
self.ds_remove_html = HfDatasetWithTokenEncode(
|
||||
"imdb", # dataset_name
|
||||
"train", #split
|
||||
encode=encode,
|
||||
remove_html_tags=True,
|
||||
encode_return_dict=True
|
||||
)
|
||||
|
||||
def test_loading(self):
|
||||
it = iter(self.ds)
|
||||
(sample, mask), label = next(it)
|
||||
|
||||
# Compare text
|
||||
actual = sample.size()
|
||||
expected = torch.Size((self.max_length,))
|
||||
self.assertEqual(expected, actual, f"expected {expected} and actual {actual} do not match.")
|
||||
|
||||
# Compare mask
|
||||
actual = mask.size()
|
||||
expected = torch.Size((self.max_length,))
|
||||
self.assertEqual(expected, actual, f"expected {expected} and actual {actual} do not match.")
|
||||
|
||||
# Compare label
|
||||
actual = label
|
||||
expected = torch.tensor(0, dtype=torch.int64)
|
||||
self.assertEqual(expected, actual, f"expected {expected} and actual {actual} do not match.")
|
||||
|
||||
|
||||
def test_loading_remove_html(self):
|
||||
it = iter(self.ds_remove_html)
|
||||
(sample, mask), label = next(it)
|
||||
|
||||
# Compare text
|
||||
actual = sample.size()
|
||||
expected = torch.Size((self.max_length, ))
|
||||
self.assertEqual(expected, actual, f"expected {expected} and actual {actual} do not match.")
|
||||
|
||||
# Compare mask
|
||||
actual = mask.size()
|
||||
expected = torch.Size((self.max_length, ))
|
||||
self.assertEqual(expected, actual, f"expected {expected} and actual {actual} do not match.")
|
||||
|
||||
# Compare label
|
||||
actual = label
|
||||
expected = torch.tensor(0, dtype=torch.int64)
|
||||
self.assertEqual(expected, actual, f"expected {expected} and actual {actual} do not match.")
|
||||
|
||||
|
||||
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.hf_glove_dataset import HfGloveDataset
|
||||
|
||||
|
||||
class TestHfGloveDataset(unittest.TestCase):
|
||||
"""
|
||||
Tests Hugging Face Dataset wrapper with word embedding support.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test instance."""
|
||||
self.embedding_dim = 100
|
||||
self.max_seq_len = 256
|
||||
self.ds = HfGloveDataset(
|
||||
"imdb", # dataset_name
|
||||
"train", #split
|
||||
self.embedding_dim,
|
||||
self.max_seq_len
|
||||
)
|
||||
|
||||
def test_vocabulary_size(self):
|
||||
self.assertEqual(400000, len(self.ds.glove))
|
||||
|
||||
def test_tokenize(self):
|
||||
"""Test tokenization"""
|
||||
s = "Maddie is a cute puppy."
|
||||
actual = self.ds.tokenize(s)
|
||||
expected = ['maddie', 'is', 'a', 'cute', 'puppy']
|
||||
self.assertEqual(expected, actual, f"expected {expected} and actual {actual} do not match.")
|
||||
|
||||
def test_vectorize(self):
|
||||
"""Test embedding generation"""
|
||||
s = "Maddie is a cute puppy."
|
||||
tokens = self.ds.tokenize(s)
|
||||
embeddings = self.ds.vectorize(tokens)
|
||||
actual = embeddings.size()
|
||||
expected = torch.Size((self.max_seq_len, self.embedding_dim))
|
||||
self.assertEqual(expected, actual, f"expected {expected} and actual {actual} do not match.")
|
||||
|
||||
def test_loading(self):
|
||||
it = iter(self.ds)
|
||||
sample = next(it)
|
||||
|
||||
# Compare text
|
||||
actual = sample[0].size()
|
||||
expected = torch.Size((self.max_seq_len, self.embedding_dim))
|
||||
self.assertEqual(expected, actual, f"expected {expected} and actual {actual} do not match.")
|
||||
|
||||
# Compare label
|
||||
actual = sample[1]
|
||||
expected = torch.tensor(0, dtype=torch.int64)
|
||||
self.assertEqual(expected, actual, f"expected {expected} and actual {actual} do not match.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,97 @@
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
import torch
|
||||
from itertools import product
|
||||
|
||||
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 hugging_face_wrapper.hf_tokenizer_encode import HfTokenizerEncode
|
||||
|
||||
|
||||
class TestHfTokenizerEncode(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.node = HfTokenizerEncode()
|
||||
self.test_sentence_1d = "Hello, world"
|
||||
self.test_sentence_2d = [
|
||||
"a b c d e f g h i j k l m n o p q r s t u v w x y z",
|
||||
"a b c d e f g h i j k l"]
|
||||
self.models = {
|
||||
"bert-base-uncased": "BertTokenizer",
|
||||
"roberta-base": "RobertaTokenizer",
|
||||
"distilbert-base-uncased": "DistilBertTokenizer",
|
||||
"albert-base-v2": "AlbertTokenizer",
|
||||
"google/electra-small-discriminator": "ElectraTokenizer",
|
||||
}
|
||||
|
||||
def test_tokenizer_variants(self):
|
||||
for model_name, tokenizer_name in self.models.items():
|
||||
for is_1d, padding, truncation, pad_method in product(
|
||||
[True, False], [True, False], [True, False], ["longest", "max_length"]
|
||||
):
|
||||
|
||||
if not is_1d and not padding:
|
||||
continue # Cannot batch ragged output into tensors
|
||||
|
||||
with self.subTest(
|
||||
tokenizer_name=tokenizer_name,
|
||||
is_1d=is_1d,
|
||||
padding=padding,
|
||||
truncation=truncation,
|
||||
pad_method=pad_method
|
||||
):
|
||||
sentence = self.test_sentence_1d if is_1d else self.test_sentence_2d
|
||||
|
||||
if truncation:
|
||||
max_length = 10
|
||||
else:
|
||||
max_length = 256
|
||||
|
||||
encoder = self.node.f(
|
||||
model_name=model_name,
|
||||
padding=padding,
|
||||
padding_method=pad_method,
|
||||
truncation=truncation,
|
||||
max_length=max_length
|
||||
)[0]
|
||||
output = encoder(sentence=sentence)
|
||||
|
||||
input_ids = output["input_ids"]
|
||||
mask = output["attention_mask"]
|
||||
|
||||
# shape agreement
|
||||
self.assertEqual(input_ids.shape, mask.shape)
|
||||
|
||||
# correct batch size
|
||||
self.assertEqual(input_ids.shape[0], 1 if is_1d else len(sentence))
|
||||
|
||||
# correct max length behavior
|
||||
if not is_1d:
|
||||
if truncation:
|
||||
self.assertEqual(input_ids.shape[1], 10)
|
||||
elif padding and pad_method == "longest":
|
||||
self.assertEqual(input_ids[0].size(0), input_ids[1].size(0))
|
||||
elif padding and pad_method == "max_length":
|
||||
self.assertGreaterEqual(input_ids.shape[1], 256)
|
||||
|
||||
|
||||
def test_padding_false_1d(self):
|
||||
for model_name in self.models:
|
||||
with self.subTest(model=model_name):
|
||||
encoder = self.node.f(
|
||||
model_name=model_name,
|
||||
padding=False,
|
||||
padding_method="longest",
|
||||
truncation=False,
|
||||
max_length=256
|
||||
)[0]
|
||||
output = encoder(sentence=self.test_sentence_1d)
|
||||
input_ids = output["input_ids"]
|
||||
self.assertEqual(input_ids.shape[0], 1)
|
||||
self.assertGreater(torch.count_nonzero(input_ids), 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+1
-1
@@ -14,7 +14,7 @@ MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
|
||||
sys.path.append(MODULE_ROOT)
|
||||
|
||||
from pytorch_wrapper.ptn_embedding_rnn_linear import PtnEmbeddingRNNLinear
|
||||
from pytorch_wrapper.hf_dataset_with_token_encode import HfDatasetWithTokenEncode
|
||||
from hugging_face_wrapper.hf_dataset_with_token_encode import HfDatasetWithTokenEncode
|
||||
from sentencepiece_wrapper.sp_load_model import SpLoadModel
|
||||
from sentencepiece_wrapper.sp_encode import SpEncode
|
||||
from pytorch_wrapper.pto_adam import PtoAdam
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
|
||||
sys.path.append(MODULE_ROOT)
|
||||
|
||||
from pytorch_wrapper.ptn_embedding_transformer_linear import PtnEmbeddingTransformerLinear
|
||||
from pytorch_wrapper.hf_dataset_with_token_encode import HfDatasetWithTokenEncode
|
||||
from hugging_face_wrapper.hf_dataset_with_token_encode import HfDatasetWithTokenEncode
|
||||
from sentencepiece_wrapper.sp_load_model import SpLoadModel
|
||||
from sentencepiece_wrapper.sp_encode import SpEncode
|
||||
from pytorch_wrapper.pto_adamw import PtoAdamW
|
||||
@@ -105,7 +105,7 @@ class TestPtTrainEmbeddingTransformerLinearTextClassificationModel(unittest.Test
|
||||
)[0]
|
||||
|
||||
self.minimum_lr = 1e-9
|
||||
self.epochs = 10
|
||||
self.epochs = 5
|
||||
self.scheduler = PtoLrSchedulerCosineAnnealing().f(self.optimizer,
|
||||
self.epochs,
|
||||
self.minimum_lr)[0]
|
||||
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
"""
|
||||
Dataset URL:https://huggingface.co/datasets/stanfordnlp/imdb
|
||||
|
||||
This unit test covers a test case where pretrained module is not frozen.
|
||||
For a case where pretrained module is frozen, see:
|
||||
tests/pytorch_wrapper/pt_train_fine_tune_classification_transformer_model_text_test.py
|
||||
|
||||
For DistilBERT, this script should produce validation accuracy around 0.9242 for the first epoch and around 0.9216 for the secod epoch.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import random
|
||||
import numpy as np
|
||||
|
||||
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_hf_fine_tuned_classification_model import PtnHfFineTunedClassificationModel
|
||||
from hugging_face_wrapper.hf_dataset_with_token_encode import HfDatasetWithTokenEncode
|
||||
from hugging_face_wrapper.hf_tokenizer_encode import HfTokenizerEncode
|
||||
from pytorch_wrapper.pto_adamw import PtoAdamW
|
||||
from pytorch_wrapper.pt_data_loader import PtDataLoader
|
||||
from pytorch_wrapper.pt_train_classification_transformer_model import PtTrainClassificationTransformerModel
|
||||
from pytorch_wrapper.pt_save_model import PtSaveModel
|
||||
from pytorch_wrapper.pto_lr_scheduler_cosine_annealing import PtoLrSchedulerCosineAnnealing
|
||||
from pytorch_wrapper.ptn_bce_with_logits_loss import PtnBCEWithLogitsLoss
|
||||
from pytorch_wrapper.utils import set_seed
|
||||
|
||||
|
||||
class TestPtnHfFineTunedTextClassificationModel(unittest.TestCase):
|
||||
|
||||
@unittest.skipIf(os.getenv("RUN_SKIPPED_TESTS") != "1", "Skipping unless explicitly enabled")
|
||||
def setUp(self):
|
||||
set_seed(42)
|
||||
|
||||
self.loss_fun = PtnBCEWithLogitsLoss().f(reduction="mean")[0]
|
||||
self.batch_size = 32
|
||||
self.epochs = 2
|
||||
self.minimum_lr = 1e-9
|
||||
self.models = {
|
||||
"bert-base-uncased": "BertTokenizer",
|
||||
"distilbert-base-uncased": "DistilBertTokenizer",
|
||||
"albert-base-v2": "AlbertTokenizer",
|
||||
"roberta-base": "RobertaTokenizer",
|
||||
# "google/electra-small-discriminator": "ElectraTokenizer",
|
||||
}
|
||||
self.model_path = "hf_fine_tuned_text_classification_model_test.pt"
|
||||
self.linear_head = True
|
||||
|
||||
@unittest.skipIf(os.getenv("RUN_SKIPPED_TESTS") != "1", "Skipping unless explicitly enabled")
|
||||
def test_all_models(self):
|
||||
for model_name in self.models:
|
||||
print(f"\n=== Testing model: {model_name} ===")
|
||||
|
||||
encode = HfTokenizerEncode().f(
|
||||
model_name=model_name,
|
||||
padding=True,
|
||||
padding_method="max_length",
|
||||
truncation=True,
|
||||
max_length=0 # Use model's max length
|
||||
)[0]
|
||||
|
||||
train_dataset = HfDatasetWithTokenEncode(
|
||||
"imdb", "train", encode=encode,
|
||||
remove_html_tags=True,
|
||||
encode_return_dict=True
|
||||
)
|
||||
|
||||
test_dataset = HfDatasetWithTokenEncode(
|
||||
"imdb", "test", encode=encode,
|
||||
remove_html_tags=True,
|
||||
encode_return_dict=True
|
||||
)
|
||||
|
||||
train_loader = PtDataLoader().f(
|
||||
dataset=train_dataset,
|
||||
batch_size=self.batch_size,
|
||||
shuffle=True,
|
||||
parameters='{"num_workers":1}'
|
||||
)[0]
|
||||
|
||||
test_loader = PtDataLoader().f(
|
||||
dataset=test_dataset,
|
||||
batch_size=self.batch_size,
|
||||
shuffle=False,
|
||||
parameters='{"num_workers":1}'
|
||||
)[0]
|
||||
|
||||
model = PtnHfFineTunedClassificationModel().f(
|
||||
model_name,
|
||||
use_mean_pooling=True,
|
||||
dropout=0.1
|
||||
)[0]
|
||||
|
||||
optimizer = PtoAdamW().f(
|
||||
model, 1e-5, 0.9, 0.999,
|
||||
0.01, # weight decay
|
||||
False # amsgrad
|
||||
)[0]
|
||||
|
||||
scheduler = PtoLrSchedulerCosineAnnealing().f(
|
||||
optimizer,
|
||||
self.epochs,
|
||||
self.minimum_lr
|
||||
)[0]
|
||||
|
||||
trainer = PtTrainClassificationTransformerModel()
|
||||
|
||||
trained_model = trainer.f(
|
||||
model,
|
||||
train_loader,
|
||||
optimizer,
|
||||
self.loss_fun,
|
||||
self.epochs,
|
||||
use_gpu=True,
|
||||
early_stopping=False,
|
||||
early_stopping_rounds=1,
|
||||
scheduler=scheduler,
|
||||
output_best_val_model=False,
|
||||
classification_metrics=True,
|
||||
val_loader=test_loader
|
||||
)[0]
|
||||
|
||||
PtSaveModel().f(trained_model, self.model_path)
|
||||
|
||||
# Clean up to free memory and VRAM
|
||||
del model, trained_model, optimizer, scheduler, trainer
|
||||
del train_loader, test_loader, train_dataset, test_dataset, encode
|
||||
torch.cuda.empty_cache()
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -84,13 +84,13 @@ class TestPtTrainGRULinearTextClassificationModel(unittest.TestCase):
|
||||
)[0]
|
||||
|
||||
self.optimizer = PtoAdamW().f(self.model,
|
||||
0.001,
|
||||
0.0003,
|
||||
0.9, 0.999,
|
||||
weight_decay=0.01,
|
||||
amsgrad=False)[0]
|
||||
|
||||
self.scheduler = PtoLrSchedulerReduceOnPlateau().f(self.optimizer,
|
||||
grace_period=10,
|
||||
grace_period=5,
|
||||
gamma=0.5
|
||||
)[0]
|
||||
|
||||
@@ -98,7 +98,7 @@ class TestPtTrainGRULinearTextClassificationModel(unittest.TestCase):
|
||||
self.save_model = PtSaveModel()
|
||||
self.load_model = PtLoadModel()
|
||||
self.linear_head = True
|
||||
self.epochs = 50
|
||||
self.epochs = 20
|
||||
|
||||
@unittest.skipIf(os.getenv("RUN_SKIPPED_TESTS") != "1", "Skipping unless explicitly enabled")
|
||||
def test_1(self):
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
"""
|
||||
Dataset URL:https://huggingface.co/datasets/stanfordnlp/imdb
|
||||
|
||||
This unit test covers a rare test case where pretrained module is not frozen.
|
||||
For a case where pretrained module is frozen, see:
|
||||
tests/pytorch_wrapper/pt_train_fine_tune_classification_transformer_model_text_test.py
|
||||
|
||||
This script should produce validation accuracy around 0.9242 for the first epoch and around 0.9216 for the secod epoch.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import random
|
||||
import numpy as np
|
||||
|
||||
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_hf_fine_tuned_classification_model import PtnHfFineTunedClassificationModel
|
||||
from hugging_face_wrapper.hf_dataset_with_token_encode import HfDatasetWithTokenEncode
|
||||
from hugging_face_wrapper.hf_tokenizer_encode import HfTokenizerEncode
|
||||
from pytorch_wrapper.pto_adamw import PtoAdamW
|
||||
from pytorch_wrapper.pt_data_loader import PtDataLoader
|
||||
from pytorch_wrapper.pt_train_classification_transformer_model import PtTrainClassificationTransformerModel
|
||||
from pytorch_wrapper.pt_save_model import PtSaveModel
|
||||
from pytorch_wrapper.pt_load_model import PtLoadModel
|
||||
from pytorch_wrapper.pto_lr_scheduler_cosine_annealing import PtoLrSchedulerCosineAnnealing
|
||||
from pytorch_wrapper.ptn_bce_with_logits_loss import PtnBCEWithLogitsLoss
|
||||
from pytorch_wrapper.utils import set_seed
|
||||
|
||||
|
||||
class TestPtnHfFineTunedTextClassificationModel(unittest.TestCase):
|
||||
|
||||
@unittest.skipIf(os.getenv("RUN_SKIPPED_TESTS") != "1", "Skipping unless explicitly enabled")
|
||||
def setUp(self):
|
||||
set_seed(42)
|
||||
|
||||
self.loss_fun =PtnBCEWithLogitsLoss().f(reduction="mean")[0]
|
||||
|
||||
self.max_length=512
|
||||
self.model_name = "distilbert-base-uncased"
|
||||
encode = HfTokenizerEncode().f(
|
||||
model_name=self.model_name,
|
||||
padding=True,
|
||||
padding_method="max_length",
|
||||
truncation=True,
|
||||
max_length=0 # Use model's max length
|
||||
)[0]
|
||||
|
||||
train_dataset = HfDatasetWithTokenEncode(
|
||||
"imdb", # dataset_name
|
||||
"train", #split
|
||||
encode=encode,
|
||||
remove_html_tags=True,
|
||||
encode_return_dict=True
|
||||
)
|
||||
|
||||
test_dataset = HfDatasetWithTokenEncode(
|
||||
"imdb", # dataset_name
|
||||
"test", #split
|
||||
encode=encode,
|
||||
remove_html_tags=True,
|
||||
encode_return_dict=True
|
||||
)
|
||||
|
||||
train_data_loader_node = PtDataLoader()
|
||||
test_data_loader_node = PtDataLoader()
|
||||
|
||||
self.train_loader = train_data_loader_node.f(
|
||||
dataset=train_dataset,
|
||||
batch_size=32,
|
||||
shuffle=True,
|
||||
parameters='{"num_workers":1}'
|
||||
)[0]
|
||||
|
||||
self.test_loader = test_data_loader_node.f(
|
||||
dataset=test_dataset,
|
||||
batch_size=32,
|
||||
shuffle=False,
|
||||
parameters='{"num_workers":1}'
|
||||
)[0]
|
||||
|
||||
self.model_path = "hf_fine_tuned_text_classification_model_test.pt"
|
||||
|
||||
self.model = PtnHfFineTunedClassificationModel().f(
|
||||
self.model_name,
|
||||
use_mean_pooling=True,
|
||||
dropout=0.1
|
||||
)[0]
|
||||
|
||||
self.optimizer = PtoAdamW().f(self.model, 1e-5, 0.9, 0.999,
|
||||
0.01, # weight decay
|
||||
False # amsgrad
|
||||
)[0]
|
||||
|
||||
self.minimum_lr = 1e-9
|
||||
self.epochs = 2
|
||||
self.scheduler = PtoLrSchedulerCosineAnnealing().f(self.optimizer,
|
||||
self.epochs,
|
||||
self.minimum_lr)[0]
|
||||
|
||||
self.trainer = PtTrainClassificationTransformerModel()
|
||||
self.save_model = PtSaveModel()
|
||||
self.load_model = PtLoadModel()
|
||||
self.linear_head = True
|
||||
|
||||
|
||||
@unittest.skipIf(os.getenv("RUN_SKIPPED_TESTS") != "1", "Skipping unless explicitly enabled")
|
||||
def test_1(self):
|
||||
|
||||
trained_model = self.trainer.f(
|
||||
self.model,
|
||||
self.train_loader,
|
||||
self.optimizer,
|
||||
self.loss_fun,
|
||||
self.epochs, # epochs
|
||||
use_gpu=True,
|
||||
early_stopping=False,
|
||||
early_stopping_rounds=1,
|
||||
scheduler=self.scheduler,
|
||||
output_best_val_model=False,
|
||||
classification_metrics=True,
|
||||
val_loader=self.test_loader)[0]
|
||||
self.save_model.f(trained_model, self.model_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -85,13 +85,13 @@ class TestPtTrainLSTMLinearTextClassificationModel(unittest.TestCase):
|
||||
)[0]
|
||||
|
||||
self.optimizer = PtoAdamW().f(self.model,
|
||||
0.001,
|
||||
0.0003,
|
||||
0.9, 0.999,
|
||||
weight_decay=0.01,
|
||||
amsgrad=False)[0]
|
||||
|
||||
self.scheduler = PtoLrSchedulerReduceOnPlateau().f(self.optimizer,
|
||||
grace_period=10,
|
||||
grace_period=5,
|
||||
gamma=0.5
|
||||
)[0]
|
||||
|
||||
@@ -99,7 +99,7 @@ class TestPtTrainLSTMLinearTextClassificationModel(unittest.TestCase):
|
||||
self.save_model = PtSaveModel()
|
||||
self.load_model = PtLoadModel()
|
||||
self.linear_head = True
|
||||
self.epochs = 50
|
||||
self.epochs = 15
|
||||
|
||||
@unittest.skipIf(os.getenv("RUN_SKIPPED_TESTS") != "1", "Skipping unless explicitly enabled")
|
||||
def test_1(self):
|
||||
|
||||
@@ -95,7 +95,7 @@ class TestPtTrainRNNLinearTextClassificationModel(unittest.TestCase):
|
||||
self.save_model = PtSaveModel()
|
||||
self.load_model = PtLoadModel()
|
||||
self.linear_head = True
|
||||
self.epochs = 50
|
||||
self.epochs = 10
|
||||
|
||||
@unittest.skipIf(os.getenv("RUN_SKIPPED_TESTS") != "1", "Skipping unless explicitly enabled")
|
||||
def test_1(self):
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
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_hf_fine_tuned_classification_model_def import HfFineTunedClassificationModel
|
||||
from hugging_face_wrapper.hf_tokenizer_encode import HfTokenizerEncode
|
||||
|
||||
|
||||
class TestHfFineTunedClassificationModel(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Setup shared test conditions."""
|
||||
self.text = [
|
||||
"I really liked the scenary.",
|
||||
"I did not like the vision of the creator."
|
||||
]
|
||||
self.model_names = [
|
||||
"distilbert-base-uncased",
|
||||
"bert-base-uncased",
|
||||
"roberta-base",
|
||||
"albert-base-v2",
|
||||
"google/electra-base-discriminator"
|
||||
]
|
||||
|
||||
def test_all_models(self):
|
||||
"""Run output shape test across all supported models."""
|
||||
for model_name in self.model_names:
|
||||
with self.subTest(model_name=model_name):
|
||||
encode = HfTokenizerEncode().f(
|
||||
model_name=model_name,
|
||||
padding=True,
|
||||
padding_method="max_length",
|
||||
truncation=True,
|
||||
max_length=0 # Use model's max length
|
||||
)[0]
|
||||
|
||||
model = HfFineTunedClassificationModel(
|
||||
model_name,
|
||||
use_mean_pooling=True,
|
||||
dropout=0.1
|
||||
)
|
||||
|
||||
token_dict = encode(self.text)
|
||||
out = model((token_dict["input_ids"], token_dict["attention_mask"]))
|
||||
self.assertEqual(out.size(), torch.Size([2, 1]))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,52 @@
|
||||
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_hf_fine_tuned_classification_model import PtnHfFineTunedClassificationModel
|
||||
from hugging_face_wrapper.hf_tokenizer_encode import HfTokenizerEncode
|
||||
|
||||
|
||||
class TestPtfHfFineTunedClassificationModel(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Setup shared test conditions."""
|
||||
self.text = [
|
||||
"I really liked the scenary.",
|
||||
"I did not like the vision of the creator."
|
||||
]
|
||||
self.model_names = [
|
||||
"distilbert-base-uncased",
|
||||
"bert-base-uncased",
|
||||
"roberta-base",
|
||||
"albert-base-v2",
|
||||
"google/electra-base-discriminator"
|
||||
]
|
||||
|
||||
def test_all_models(self):
|
||||
"""Run output shape test across all supported models."""
|
||||
for model_name in self.model_names:
|
||||
with self.subTest(model_name=model_name):
|
||||
encode = HfTokenizerEncode().f(
|
||||
model_name=model_name,
|
||||
padding=True,
|
||||
padding_method="max_length",
|
||||
truncation=True,
|
||||
max_length=0 # Use model's max length
|
||||
)[0]
|
||||
|
||||
model = PtnHfFineTunedClassificationModel().f(
|
||||
model_name,
|
||||
use_mean_pooling=True,
|
||||
dropout=0.1
|
||||
)[0]
|
||||
|
||||
token_dict = encode(self.text)
|
||||
out = model((token_dict["input_ids"], token_dict["attention_mask"]))
|
||||
self.assertEqual(out.size(), torch.Size([2, 1]))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -37,7 +37,8 @@ class TestPtvHfDatasetWithTokenEncode(unittest.TestCase):
|
||||
"text",
|
||||
"label",
|
||||
encode,
|
||||
False # remove_html_tags
|
||||
False, # remove_html_tags
|
||||
False # encode_return_dict
|
||||
)[0]
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
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.utils import set_requires_grad
|
||||
|
||||
|
||||
class TestSetRequiresGrad(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.model = nn.Linear(2, 3, bias=True)
|
||||
|
||||
def test_1(self):
|
||||
# Check that gradients are initially enabled
|
||||
for param in self.model.parameters():
|
||||
self.assertTrue(param.requires_grad)
|
||||
|
||||
# Turn off gradients
|
||||
set_requires_grad(self.model, False)
|
||||
for param in self.model.parameters():
|
||||
self.assertFalse(param.requires_grad)
|
||||
|
||||
# Turn on gradients
|
||||
set_requires_grad(self.model, True)
|
||||
for param in self.model.parameters():
|
||||
self.assertTrue(param.requires_grad)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user