Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"tasks": {
"build": "pip install -r requirements.txt",
"launch": "python -m deeptuner.run"
}
}
39 changes: 39 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Publish Package

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.8'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel
- name: Build package
run: python setup.py sdist bdist_wheel

- name: Publish package
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: |
pip install twine
twine upload dist/*
- name: Clean up
run: rm -rf dist build *.egg-info
8 changes: 8 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
include LICENSE
include README.md
include requirements.txt
include config.json
recursive-include examples *
recursive-include deeptuner *
global-exclude __pycache__
global-exclude *.py[cod]
162 changes: 144 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# DeepTune
# DeepTuner

## Description

DeepTune is an open source Python package for fine-tuning computer vision (CV) based deep models using Siamese architecture with a triplet loss function. The package supports various model backbones and provides tools for data preprocessing and evaluation metrics.
DeepTuner is an open source Python package for fine-tuning computer vision (CV) based deep models using Siamese architecture with a triplet loss function. The package supports various model backbones and provides tools for data preprocessing and evaluation metrics.

## Installation

To install the package, use the following command:

```bash
pip install DeepTune
pip install DeepTuner
```

## Usage
Expand All @@ -19,25 +19,151 @@ pip install DeepTune
Here is an example of how to use the package for fine-tuning models with Siamese architecture and triplet loss:

```python
import DeepTune
from DeepTune import triplet_loss, backbones, data_preprocessing, evaluation_metrics
import os
import json
from sklearn.model_selection import train_test_split
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.metrics import Mean
from tensorflow.keras.callbacks import ReduceLROnPlateau, EarlyStopping, ModelCheckpoint
from wandb.integration.keras import WandbMetricsLogger
import wandb

# Load and preprocess data
data = data_preprocessing.load_data('path/to/dataset')
triplets = data_preprocessing.create_triplets(data)
from deeptuner.backbones.resnet import ResNetBackbone
from deeptuner.architectures.siamese import SiameseArchitecture
from deeptuner.losses.triplet_loss import triplet_loss
from deeptuner.datagenerators.triplet_data_generator import TripletDataGenerator
from deeptuner.callbacks.finetune_callback import FineTuneCallback

# Initialize model backbone
model = backbones.get_model('resnet')
# Load configuration from JSON file
with open('config.json', 'r') as config_file:
config = json.load(config_file)

# Compile model with triplet loss
model.compile(optimizer='adam', loss=triplet_loss.triplet_loss)
data_dir = config['data_dir']
image_size = tuple(config['image_size'])
batch_size = config['batch_size']
margin = config['margin']
epochs = config['epochs']
initial_epoch = config['initial_epoch']
learning_rate = config['learning_rate']
patience = config['patience']
unfreeze_layers = config['unfreeze_layers']

# Train model
model.fit(triplets, epochs=10, batch_size=32)
# Initialize W&B
wandb.init(project=config['project_name'], config=config)

# Evaluate model
metrics = evaluation_metrics.evaluate_model(model, triplets)
print(metrics)
# Load and preprocess the data
image_paths = []
labels = []

for label in os.listdir(data_dir):
label_dir = os.path.join(data_dir, label)
if os.path.isdir(label_dir):
for image_name in os.listdir(label_dir):
image_paths.append(os.path.join(label_dir, image_name))
labels.append(label)

# Debugging output
print(f"Found {len(image_paths)} images in {len(set(labels))} classes")

# Split the data into training and validation sets
train_paths, val_paths, train_labels, val_labels = train_test_split(
image_paths, labels, test_size=0.2, stratify=labels, random_state=42
)

# Check if the splits are non-empty
print(f"Training on {len(train_paths)} images")
print(f"Validating on {len(val_paths)} images")

# Create data generators
num_classes = len(set(labels))
train_generator = TripletDataGenerator(train_paths, train_labels, batch_size, image_size, num_classes)
val_generator = TripletDataGenerator(val_paths, val_labels, batch_size, image_size, num_classes)

# Check if the generators have data
assert len(train_generator) > 0, "Training generator is empty!"
assert len(val_generator) > 0, "Validation generator is empty!"

# Create the embedding model and freeze layers
backbone = ResNetBackbone(input_shape=image_size + (3,))
embedding_model = backbone.create_model()

# Freeze all layers initially
for layer in embedding_model.layers:
layer.trainable = False
# Unfreeze last few layers
for layer in embedding_model.layers[-unfreeze_layers:]:
layer.trainable = True

# Create the siamese network
siamese_architecture = SiameseArchitecture(input_shape=image_size + (3,), embedding_model=embedding_model)
siamese_network = siamese_architecture.create_siamese_network()

# Initialize the Siamese model
loss_tracker = Mean(name="loss")
siamese_model = SiameseModel(siamese_network, margin, loss_tracker)

# Set up callbacks
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=3, min_lr=1e-7, verbose=1)
early_stopping = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True, verbose=1)
model_checkpoint = ModelCheckpoint(
"models/best_siamese_model.weights.h5",
save_best_only=True,
save_weights_only=True,
monitor='val_loss',
verbose=1
)
embedding_checkpoint = ModelCheckpoint(
"models/best_embedding_model.weights.h5",
save_best_only=True,
save_weights_only=True,
monitor='val_loss',
verbose=1
)
fine_tune_callback = FineTuneCallback(embedding_model, patience=patience, unfreeze_layers=unfreeze_layers)

# Create models directory if it doesn't exist
os.makedirs('models', exist_ok=True)

# Compile the model
siamese_model.compile(optimizer=Adam(learning_rate=learning_rate), loss=triplet_loss(margin=margin))

# Train the model
history = siamese_model.fit(
train_generator,
validation_data=val_generator,
epochs=epochs,
initial_epoch=initial_epoch,
callbacks=[
reduce_lr,
early_stopping,
model_checkpoint,
embedding_checkpoint,
fine_tune_callback,
WandbMetricsLogger(log_freq=5)
]
)

# Save the final embedding model
embedding_model.save('models/final_embedding_model.h5')
```

### Using Configuration Files

To make it easier to experiment with different hyperparameter settings, you can use a configuration file (e.g., JSON) to store hyperparameters. Here is an example of a configuration file (`config.json`):

```json
{
"data_dir": "path/to/your/dataset",
"image_size": [224, 224],
"batch_size": 32,
"margin": 1.0,
"epochs": 50,
"initial_epoch": 0,
"learning_rate": 0.001,
"patience": 5,
"unfreeze_layers": 10,
"project_name": "DeepTuner"
}
```

For more detailed usage and examples, please refer to the documentation.
You can then load this configuration file in your code as shown in the usage example above.
12 changes: 12 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"data_dir": "datasets/lfw_processed",
"image_size": [224, 224],
"batch_size": 32,
"margin": 10.0,
"epochs": 50,
"initial_epoch": 0,
"learning_rate": 0.001,
"patience": 5,
"unfreeze_layers": 10,
"project_name": "DeepTuner"
}
5 changes: 5 additions & 0 deletions deeptuner/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
DeepTuner - A package for fine-tuning deep learning models with Siamese architecture and triplet loss
"""

__version__ = '0.1.7'
7 changes: 7 additions & 0 deletions deeptuner/architectures/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""
Neural network architectures for DeepTuner
"""

from .siamese import SiameseArchitecture

__all__ = ['SiameseArchitecture']
19 changes: 19 additions & 0 deletions deeptuner/architectures/siamese.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from tensorflow.keras import layers, Model, Input

class SiameseArchitecture:
def __init__(self, input_shape, embedding_model):
self.input_shape = input_shape
self.embedding_model = embedding_model

def create_siamese_network(self):
anchor_input = Input(name="anchor", shape=self.input_shape)
positive_input = Input(name="positive", shape=self.input_shape)
negative_input = Input(name="negative", shape=self.input_shape)

anchor_embedding = self.embedding_model(anchor_input)
positive_embedding = self.embedding_model(positive_input)
negative_embedding = self.embedding_model(negative_input)

outputs = [anchor_embedding, positive_embedding, negative_embedding]
model = Model(inputs=[anchor_input, positive_input, negative_input], outputs=outputs, name="siamese_network")
return model
7 changes: 7 additions & 0 deletions deeptuner/backbones/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""
Backbone models for DeepTuner
"""

from .resnet import ResNetBackbone

__all__ = ['ResNetBackbone']
27 changes: 27 additions & 0 deletions deeptuner/backbones/resnet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from tensorflow.keras.applications import ResNet50
from tensorflow.keras.layers import GlobalAveragePooling2D, Dense, Dropout, BatchNormalization
from tensorflow.keras.models import Model
from tensorflow.keras import Input

class ResNetBackbone:
def __init__(self, input_shape=(224, 224, 3), weights='imagenet'):
self.input_shape = input_shape
self.weights = weights

def create_model(self):
inputs = Input(shape=self.input_shape)
base_model = ResNet50(weights=self.weights, include_top=False, input_tensor=inputs)
x = base_model.output
x = GlobalAveragePooling2D()(x)
x = Dense(1024, activation='relu')(x)
x = Dropout(0.2)(x)
x = BatchNormalization()(x)
x = Dense(512, activation='relu')(x)
x = Dropout(0.2)(x)
x = BatchNormalization()(x)
x = Dense(256, activation='relu')(x)
x = Dropout(0.2)(x)
x = BatchNormalization()(x)
outputs = Dense(128)(x)
model = Model(inputs, outputs, name='resnet_backbone')
return model
Empty file added deeptuner/callbacks/__init__.py
Empty file.
37 changes: 37 additions & 0 deletions deeptuner/callbacks/finetune_callback.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import tensorflow as tf
from tensorflow.keras.callbacks import Callback
from tensorflow.keras.optimizers import Adam

class FineTuneCallback(Callback):
def __init__(self, base_model, patience=5, unfreeze_layers=10, margin=1.0):
super(FineTuneCallback, self).__init__()
self.base_model = base_model
self.patience = patience
self.unfreeze_layers = unfreeze_layers
self.margin = margin
self.best_weights = None
self.best_loss = float('inf')
self.wait = 0

def on_epoch_end(self, epoch, logs=None):
current_loss = logs.get('val_loss')
if current_loss < self.best_loss:
self.best_loss = current_loss
self.best_weights = self.model.get_weights()
self.wait = 0
else:
self.wait += 1
if self.wait >= self.patience:
# Restore the best weights
self.model.set_weights(self.best_weights)
self.wait = 0
# Unfreeze the last few layers
for layer in self.base_model.layers[-self.unfreeze_layers:]:
if hasattr(layer, 'trainable'):
layer.trainable = True
# Recompile the model with both optimizer and loss function
from deeptuner.losses.triplet_loss import triplet_loss
self.model.compile(
optimizer=Adam(learning_rate=1e-6),
loss=triplet_loss(margin=self.margin)
)
Empty file.
Loading