From 72a36773251f21472b870d3125714817ce399849 Mon Sep 17 00:00:00 2001 From: Harsh Vardhan Singhal Date: Thu, 15 May 2025 09:29:44 +0530 Subject: [PATCH 1/5] Create workshop.py --- .../examples/sentiment_analysis/workshop.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/dryml/examples/sentiment_analysis/workshop.py diff --git a/src/dryml/examples/sentiment_analysis/workshop.py b/src/dryml/examples/sentiment_analysis/workshop.py new file mode 100644 index 00000000..7ccd392a --- /dev/null +++ b/src/dryml/examples/sentiment_analysis/workshop.py @@ -0,0 +1,46 @@ +import dryml +import tensorflow_datasets as tfds +import torch +from torch.utils.data import Dataset +from dryml.data.torch import TorchDataset +from dryml.models.torch.text import TextVectorizer +from dryml.models import Pipe +from dryml.metrics.scalar import binary_accuracy + +class IMDBTorchDataset(Dataset): + def __init__(self, data): + self.data = data + + def __len__(self): + return len(self.data) + + def __getitem__(self, idx): + text, label = self.data[idx] + return text.decode('utf-8'), torch.tensor(float(label), dtype=torch.float) + +class SentimentWorkshop(dryml.Workshop): + def data_prep(self): + (ds_train, ds_test), ds_info = tfds.load( + 'imdb_reviews', + split=['train', 'test'], + as_supervised=True, + with_info=True + ) + train_data = list(ds_train.as_numpy_iterator()) + test_data = list(ds_test.as_numpy_iterator()) + + train_torch_ds = IMDBTorchDataset(train_data) + test_torch_ds = IMDBTorchDataset(test_data) + self.train_ds = TorchDataset(train_torch_ds, supervised=True) + self.test_ds = TorchDataset(test_torch_ds, supervised=True) + +def train(trainable): + ws = SentimentWorkshop() + ws.data_prep() + + trainable.prep_train() + + trainable.train(ws.train_ds) + return trainable + + From b0b1d9f918aa04ffe1c97f12d30e42ec7462529b Mon Sep 17 00:00:00 2001 From: Harsh Vardhan Singhal Date: Thu, 15 May 2025 09:32:28 +0530 Subject: [PATCH 2/5] Create train.py --- .../examples/sentiment_analysis/train.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/dryml/examples/sentiment_analysis/train.py diff --git a/src/dryml/examples/sentiment_analysis/train.py b/src/dryml/examples/sentiment_analysis/train.py new file mode 100644 index 00000000..a3b068dc --- /dev/null +++ b/src/dryml/examples/sentiment_analysis/train.py @@ -0,0 +1,45 @@ +import dryml +from dryml import Repo + +dryml.context.set_context({ + 'default': {}, + 'torch': {'gpu/0': 1.}, + 'tf': {} +}) + +from dryml.models import Pipe + +from dryml.examples.sentiment_analysis.workshop import train as train_model + +if __name__ == "__main__": + from dryml.models.torch.text import TextVectorizer + from dryml.examples.sentiment_analysis.torch.models import SentimentTorchModel + from dryml.models.torch.generic import BasicTraining, TorchOptimizer, Trainable as TorchTrainable + from dryml.models.torch.generic import Wrapper + import torch.nn as nn + import torch + + torch_vectorizer = TextVectorizer(max_tokens=10000, sequence_length=250, dry_id="imdb_vectorizer") + + torch_optimizer = TorchOptimizer(torch.optim.Adam, SentimentTorchModel, lr=0.001) + loss_fn = Wrapper(nn.BCELoss) + + sentiment_torch_trainable = TorchTrainable( + model=SentimentTorchModel, + train_fn=BasicTraining(epochs=10, optimizer=torch_optimizer, loss=loss_fn) + ) + + from dryml.data.torch.transforms import TorchDevice + from dryml.context import context + + dev = context().get_torch_devices()[0] + + torch_pipe = Pipe(torch_vectorizer, TorchDevice(device=dev), sentiment_torch_trainable) + trained_torch_pipe = train_model(torch_pipe) + + repo = Repo(directory="sentiment_models", create=True) + repo.add_object(trained_torch_pipe, add_nested=False) + repo.save() + + + print("Torch model and all contained sub-objects saved under ./sentiment_models/") From c586afe44b132e19632ed9f4b28d0db97f1a797b Mon Sep 17 00:00:00 2001 From: Harsh Vardhan Singhal Date: Thu, 15 May 2025 09:32:50 +0530 Subject: [PATCH 3/5] Create init.py --- src/dryml/examples/sentiment_analysis/init.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/dryml/examples/sentiment_analysis/init.py diff --git a/src/dryml/examples/sentiment_analysis/init.py b/src/dryml/examples/sentiment_analysis/init.py new file mode 100644 index 00000000..f58c060d --- /dev/null +++ b/src/dryml/examples/sentiment_analysis/init.py @@ -0,0 +1,7 @@ +from dryml.examples.sentiment_analysis.workshop import SentimentWorkshop +from dryml.examples.sentiment_analysis.torch.models import SentimentTorchModel + +__all__ = [ + "SentimentWorkshop", + "SentimentTorchModel" +] From 78f084bd88b05ecd0f7fc5def64b280672b1ea64 Mon Sep 17 00:00:00 2001 From: Harsh Vardhan Singhal Date: Thu, 15 May 2025 09:36:08 +0530 Subject: [PATCH 4/5] Create models.py --- .../sentiment_analysis/torch/models.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/dryml/examples/sentiment_analysis/torch/models.py diff --git a/src/dryml/examples/sentiment_analysis/torch/models.py b/src/dryml/examples/sentiment_analysis/torch/models.py new file mode 100644 index 00000000..beec13f2 --- /dev/null +++ b/src/dryml/examples/sentiment_analysis/torch/models.py @@ -0,0 +1,26 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +from dryml.models.torch.generic import ModelWrapper + +class SentimentModelTorch(nn.Module): + def __init__(self, vocab_size=10000, max_length=250, embedding_dim=32): + super().__init__() + self.embedding = nn.Embedding(num_embeddings=vocab_size, embedding_dim=embedding_dim) + self.conv = nn.Conv2d(in_channels=1, out_channels=32, kernel_size=(5, embedding_dim)) + self.fc1 = nn.Linear(32, 32) + self.dropout = nn.Dropout(0.3) + self.fc2 = nn.Linear(32, 1) + + def forward(self, x): + x = self.embedding(x) + x = x.unsqueeze(1) + x = F.relu(self.conv(x)) + x = torch.max(x, dim=2).values + x = x.view(x.size(0), -1) + x = F.relu(self.fc1(x)) + x = self.dropout(x) + x = torch.sigmoid(self.fc2(x)) + return x.squeeze(1) + +SentimentTorchModel = ModelWrapper(SentimentModelTorch, vocab_size=10000, max_length=250, embedding_dim=32) From 5c16c209b1120f8fc722bbb4ca9485d4e3f4c39f Mon Sep 17 00:00:00 2001 From: Harsh Vardhan Singhal Date: Thu, 15 May 2025 09:57:35 +0530 Subject: [PATCH 5/5] Create text.py --- src/dryml/models/torch/text.py | 77 ++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/dryml/models/torch/text.py diff --git a/src/dryml/models/torch/text.py b/src/dryml/models/torch/text.py new file mode 100644 index 00000000..ac1ad133 --- /dev/null +++ b/src/dryml/models/torch/text.py @@ -0,0 +1,77 @@ +from dryml.models.torch.generic import Trainable +import torch +from collections import Counter +import re +import numpy as np + +# Text Vectorizer for Sentiment Analysis Model +class TextVectorizer(Trainable): + def __init__(self, max_tokens=10000, sequence_length=250, pad_token="", unk_token=""): + super().__init__() + self.max_tokens = max_tokens + self.sequence_length = sequence_length + self.pad_token = pad_token + self.unk_token = unk_token + + self.vocab = {} + self.id_to_token = [] + self.trained = False + + def simple_tokenize(self, text): + return re.findall(r"\w+", text.lower()) + + def build_vocab(self, dataset): + counter = Counter() + for x, _ in dataset: + tokens = self.simple_tokenize(x) + counter.update(tokens) + most_common = counter.most_common(self.max_tokens - 2) + self.id_to_token = [self.pad_token, self.unk_token] + [w for w, _ in most_common] + self.vocab = {w: i for i, w in enumerate(self.id_to_token)} + + def vectorize_text(self, text): + tokens = self.simple_tokenize(text) + ids = [] + for t in tokens: + ids.append(self.vocab.get(t, self.vocab[self.unk_token])) + if len(ids) < self.sequence_length: + ids += [self.vocab[self.pad_token]] * (self.sequence_length - len(ids)) + else: + ids = ids[:self.sequence_length] + return np.array(ids, dtype=np.int64) + + def train(self, ds, **kwargs): + if not self.trained: + self.build_vocab(ds) + self.trained = True + + def predict(self, x, **kwargs): + if isinstance(x, str): + return torch.tensor(self.vectorize_text(x), dtype=torch.long) + elif isinstance(x, np.ndarray): + return self.predict(x.tolist(), **kwargs) + elif isinstance(x, (list, tuple)): + return torch.stack([ + torch.tensor(self.vectorize_text(xx), dtype=torch.long) + for xx in x + ]) + raise ValueError("Unsupported input type for vectorizer.") + + + def eval(self, dataset, **kwargs): + return dataset.map(lambda x: (self.predict(x[0]), x[1])) + + def prep_train(self, **kwargs): + pass + + def save_model(self, *args, **kwargs): + return { + 'vocab': self.vocab, + 'id_to_token': self.id_to_token, + 'trained': self.trained + } + + def load_model(self, dct, *args, **kwargs): + self.vocab = dct['vocab'] + self.id_to_token = dct['id_to_token'] + self.trained = dct['trained']