| from torch.utils.data import Dataset, DataLoader |
| from transformers import AutoTokenizer |
| from dataclasses import dataclass |
| from typing import List, Dict, Any, Union, Optional |
| from pathlib import Path |
|
|
|
|
| class TextDataset(Dataset): |
| """Text dataset supporting lists, files, and folder structures.""" |
|
|
| def __init__(self, source: Union[List[str], str, Path]): |
| """ |
| Args: |
| source: Can be: |
| - List of strings (text data) |
| - Path to single .txt file |
| - Path to folder containing .txt files (recursive) |
| """ |
| self.texts = [] |
| self.file_paths = [] |
|
|
| if isinstance(source, list): |
| self.texts = source |
| self.file_paths = [None] * len(source) |
| else: |
| source = Path(source) |
| if source.is_file(): |
| self._load_file(source) |
| elif source.is_dir(): |
| self._load_directory(source) |
| else: |
| raise ValueError(f"Source not found: {source}") |
|
|
| if len(self.texts) == 0: |
| raise ValueError("No text data loaded!") |
|
|
| def _load_file(self, file_path: Path): |
| """Load a single text file.""" |
| with open(file_path, 'r', encoding='utf-8') as f: |
| text = f.read().strip() |
| if text: |
| self.texts.append(text) |
| self.file_paths.append(str(file_path)) |
|
|
| def _load_directory(self, dir_path: Path): |
| """Recursively load all .txt files from directory.""" |
| txt_files = sorted(dir_path.rglob("*.txt")) |
|
|
| for file_path in txt_files: |
| try: |
| self._load_file(file_path) |
| except Exception as e: |
| print(f"Warning: Failed to load {file_path}: {e}") |
|
|
| def __len__(self): |
| return len(self.texts) |
|
|
| def __getitem__(self, idx): |
| return self.texts[idx] |
|
|
| def get_file_path(self, idx: int) -> Optional[str]: |
| """Get source file path for debugging.""" |
| return self.file_paths[idx] |
|
|
|
|
| @dataclass |
| class TokenizerConfig: |
| """Tokenizer configuration.""" |
| model_name: str = "bert-base-uncased" |
| max_length: int = 512 |
| truncation: bool = True |
| padding: str = "max_length" |
| return_tensors: str = "pt" |
|
|
|
|
| class TokenizerWrapper: |
| """Wrapper for HuggingFace tokenizer with collating dataloader.""" |
|
|
| def __init__(self, config: TokenizerConfig = None): |
| self.config = config or TokenizerConfig() |
| self.tokenizer = AutoTokenizer.from_pretrained(self.config.model_name) |
|
|
| def collate_fn(self, batch: List[str]) -> Dict[str, Any]: |
| """Collate function for DataLoader.""" |
| return self.tokenizer( |
| batch, |
| max_length=self.config.max_length, |
| truncation=self.config.truncation, |
| padding=self.config.padding, |
| return_tensors=self.config.return_tensors |
| ) |
|
|
| def decode(self, token_ids, skip_special_tokens: bool = True) -> Union[str, List[str]]: |
| """ |
| Decode token IDs back to text. |
| |
| Args: |
| token_ids: Single sequence or batch of token IDs (tensor or list) |
| skip_special_tokens: Whether to remove [CLS], [SEP], [PAD] etc. |
| |
| Returns: |
| Decoded text string or list of strings |
| """ |
| return self.tokenizer.decode(token_ids, skip_special_tokens=skip_special_tokens) |
|
|
| def batch_decode(self, token_ids, skip_special_tokens: bool = True) -> List[str]: |
| """ |
| Decode a batch of token IDs back to texts. |
| |
| Args: |
| token_ids: Batch of token IDs (shape: [batch_size, seq_len]) |
| skip_special_tokens: Whether to remove special tokens |
| |
| Returns: |
| List of decoded text strings |
| """ |
| return self.tokenizer.batch_decode(token_ids, skip_special_tokens=skip_special_tokens) |
|
|
| def get_dataloader( |
| self, |
| source: Union[List[str], str, Path], |
| batch_size: int = 8, |
| shuffle: bool = True, |
| num_workers: int = 0 |
| ) -> DataLoader: |
| """ |
| Create a DataLoader with tokenization collation. |
| |
| Args: |
| source: List of texts, file path, or directory path |
| batch_size: Batch size |
| shuffle: Whether to shuffle data |
| num_workers: Number of workers for data loading |
| """ |
| dataset = TextDataset(source) |
| print(f"Loaded {len(dataset)} texts") |
|
|
| return DataLoader( |
| dataset, |
| batch_size=batch_size, |
| shuffle=shuffle, |
| num_workers=num_workers, |
| collate_fn=self.collate_fn |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| texts = "_tests" |
|
|
| config = TokenizerConfig( |
| model_name="bert-base-uncased", |
| max_length=32, |
| padding="longest" |
| ) |
| wrapper = TokenizerWrapper(config) |
|
|
| print("=== Example 1: List of texts ===") |
| dataloader = wrapper.get_dataloader(texts, batch_size=2, shuffle=False) |
|
|
| for batch_idx, batch in enumerate(dataloader): |
| print(f"\nBatch {batch_idx}: {batch['input_ids'].shape}") |
|
|
| decoded_batch = wrapper.batch_decode(batch['input_ids'], skip_special_tokens=True) |
| print(f"Decoded texts: {decoded_batch}") |
|
|
| single_decoded = wrapper.decode(batch['input_ids'][0], skip_special_tokens=True) |
| print(f"First sequence: {single_decoded}") |
|
|
| with_special = wrapper.decode(batch['input_ids'][0], skip_special_tokens=False) |
| print(f"With special tokens: {with_special}") |