GitHub Actions commited on
Commit
fe965ff
·
1 Parent(s): 2a50d5b

Sync from GitHub Actions

Browse files
.gitattributes DELETED
@@ -1,35 +0,0 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.gitignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ note.txt
2
+ __pycache__/
3
+ *.pyc
4
+ .ipynb_checkpoints/
5
+ .env
6
+ .venv/
7
+ venv/
8
+ artifacts/
Dockerfile CHANGED
@@ -1,20 +1,16 @@
1
- FROM python:3.13.5-slim
2
 
3
  WORKDIR /app
4
 
5
- RUN apt-get update && apt-get install -y \
6
- build-essential \
7
- curl \
8
- git \
9
- && rm -rf /var/lib/apt/lists/*
10
 
11
- COPY requirements.txt ./
12
- COPY src/ ./src/
13
 
14
- RUN pip3 install -r requirements.txt
15
 
16
- EXPOSE 8501
17
 
18
- HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
19
 
20
- ENTRYPOINT ["streamlit", "run", "src/streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
 
1
+ FROM python:3.11-slim
2
 
3
  WORKDIR /app
4
 
5
+ ENV PYTHONUNBUFFERED=1
6
+ ENV PIP_NO_CACHE_DIR=1
 
 
 
7
 
8
+ COPY requirements.txt .
 
9
 
10
+ RUN pip install --upgrade pip && pip install -r requirements.txt
11
 
12
+ COPY . .
13
 
14
+ EXPOSE 8501
15
 
16
+ CMD ["streamlit", "run", "app/app.py", "--server.address=0.0.0.0", "--server.port=8501"]
app/app.py ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import sys
3
+ from pathlib import Path
4
+ import streamlit as st
5
+ from PIL import Image
6
+ from inference import load_predictor, render_prediction_card, render_top_predictions, render_metrics
7
+ from autocatalog.utils.config import load_config
8
+
9
+ ROOT_DIR = Path(__file__).resolve().parents[1]
10
+ if str(ROOT_DIR) not in sys.path:
11
+ sys.path.insert(0, str(ROOT_DIR))
12
+
13
+
14
+ def main():
15
+ st.set_page_config(
16
+ page_title="AutoCatalogAI",
17
+ page_icon="🛍️",
18
+ layout="wide",
19
+ )
20
+
21
+ st.markdown(
22
+ """
23
+ <style>
24
+ .main-title {
25
+ font-size: 2.5rem;
26
+ font-weight: 800;
27
+ margin-bottom: 0.2rem;
28
+ }
29
+ .subtitle {
30
+ color: #666;
31
+ font-size: 1.05rem;
32
+ margin-bottom: 2rem;
33
+ }
34
+ .prediction-card {
35
+ border: 1px solid #e5e7eb;
36
+ border-radius: 14px;
37
+ padding: 16px;
38
+ margin-bottom: 12px;
39
+ background: #ffffff;
40
+ box-shadow: 0 1px 4px rgba(0,0,0,0.04);
41
+ }
42
+ .prediction-header {
43
+ display: flex;
44
+ justify-content: space-between;
45
+ align-items: center;
46
+ }
47
+ .task-name {
48
+ font-size: 0.9rem;
49
+ font-weight: 700;
50
+ color: #374151;
51
+ }
52
+ .confidence {
53
+ font-size: 0.9rem;
54
+ font-weight: 700;
55
+ color: #111827;
56
+ }
57
+ .label {
58
+ font-size: 1.25rem;
59
+ font-weight: 800;
60
+ margin-top: 8px;
61
+ margin-bottom: 10px;
62
+ }
63
+ .bar-bg {
64
+ width: 100%;
65
+ height: 8px;
66
+ background: #e5e7eb;
67
+ border-radius: 999px;
68
+ overflow: hidden;
69
+ }
70
+ .bar-fill {
71
+ height: 100%;
72
+ background: #111827;
73
+ border-radius: 999px;
74
+ }
75
+ .catalog-box {
76
+ border: 1px solid #e5e7eb;
77
+ border-radius: 14px;
78
+ padding: 18px;
79
+ background: #fafafa;
80
+ margin-bottom: 16px;
81
+ }
82
+ </style>
83
+ """,
84
+ unsafe_allow_html=True,
85
+ )
86
+
87
+ config = load_config("configs/config.yaml")
88
+ repo_id = config.get("model", {}).get(
89
+ "repo_id",
90
+ "mohsin416/autocatalogai-clip-multitask",
91
+ )
92
+
93
+ top_k = int(config.get("inference", {}).get("top_k", 3))
94
+ device = config.get("inference", {}).get("device", None)
95
+
96
+ st.markdown('<div class="main-title">AutoCatalogAI</div>', unsafe_allow_html=True)
97
+ st.markdown(
98
+ '<div class="subtitle">Fashion product attribute extraction and catalog metadata generation using CLIP multi-task learning.</div>',
99
+ unsafe_allow_html=True,
100
+ )
101
+
102
+ with st.sidebar:
103
+ st.header("Settings")
104
+ st.write("Model repository")
105
+ st.code(repo_id)
106
+
107
+ selected_top_k = st.slider(
108
+ "Top-K predictions",
109
+ min_value=1,
110
+ max_value=5,
111
+ value=top_k,
112
+ )
113
+
114
+ st.divider()
115
+ st.caption("Model loads from Hugging Face Hub and runs inference only.")
116
+
117
+ with st.spinner("Loading AutoCatalogAI model..."):
118
+ predictor = load_predictor(
119
+ repo_id=repo_id,
120
+ device=device,
121
+ top_k=selected_top_k
122
+ )
123
+
124
+ render_metrics(predictor.get_model_metrics())
125
+ st.divider()
126
+
127
+ left_col, right_col = st.columns([0.9, 1.1])
128
+
129
+ with left_col:
130
+ st.subheader("Upload Product Image")
131
+ uploaded_file = st.file_uploader(
132
+ "Choose a product image",
133
+ type=["jpg", "jpeg", "png", "webp"],
134
+ label_visibility="collapsed",
135
+ )
136
+
137
+ if uploaded_file is not None:
138
+ image = Image.open(uploaded_file).convert("RGB")
139
+ st.image(image, caption="Uploaded Image", use_container_width=True)
140
+
141
+ with right_col:
142
+ st.subheader("Prediction Result")
143
+
144
+ if uploaded_file is None:
145
+ st.info("Upload a fashion product image to generate catalog attributes.")
146
+ return
147
+
148
+ if st.button("Generate Catalog", type="primary", use_container_width=True):
149
+ with st.spinner("Predicting product attributes..."):
150
+ result = predictor.predict(
151
+ image=image,
152
+ top_k=selected_top_k,
153
+ )
154
+
155
+ prediction = result["prediction"]
156
+ catalog_output = result["catalog_output"]
157
+ runtime = result["runtime"]
158
+
159
+ st.markdown(
160
+ f"""
161
+ <div class="catalog-box">
162
+ <strong>Suggested Title</strong>
163
+ <h3>{catalog_output["suggested_title"]}</h3>
164
+ </div>
165
+ """,
166
+ unsafe_allow_html=True,
167
+ )
168
+
169
+ st.markdown("**Search Tags**")
170
+ st.write(", ".join(catalog_output["search_tags"]))
171
+ st.markdown("**Predicted Attributes**")
172
+
173
+ for task, task_result in prediction.items():
174
+ render_prediction_card(task, task_result)
175
+
176
+ render_top_predictions(prediction)
177
+ st.markdown("**Runtime**")
178
+ st.write(f"Device: `{runtime['device']}`")
179
+ st.write(f"Inference time: `{runtime['inference_time_ms']:.2f} ms`")
180
+
181
+ json_output = json.dumps(
182
+ catalog_output["json_export"],
183
+ indent=2,
184
+ ensure_ascii=False,
185
+ )
186
+
187
+ st.download_button(
188
+ label="Download JSON",
189
+ data=json_output,
190
+ file_name="autocatalogai_prediction.json",
191
+ mime="application/json",
192
+ use_container_width=True,
193
+ )
194
+
195
+ with st.expander("Raw JSON Output"):
196
+ st.json(catalog_output["json_export"])
197
+
198
+
199
+ if __name__ == "__main__":
200
+ main()
app/inference.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from autocatalog.inference.predictor import AutoCatalogPredictor
3
+
4
+ @st.cache_resource(show_spinner=False)
5
+ def load_predictor(repo_id, device, top_k):
6
+ return AutoCatalogPredictor(
7
+ repo_id=repo_id,
8
+ device=device,
9
+ top_k=top_k
10
+ )
11
+
12
+
13
+ def format_percent(value):
14
+ return f"{value * 100:.2f}%"
15
+
16
+
17
+ def render_prediction_card(task_name, task_result):
18
+ label = task_result["label"]
19
+ confidence = task_result["confidence"]
20
+
21
+ st.markdown(
22
+ f"""
23
+ <div class="prediction-card">
24
+ <div class="prediction-header">
25
+ <span class="task-name">{task_name}</span>
26
+ <span class="confidence">{format_percent(confidence)}</span>
27
+ </div>
28
+ <div class="label">{label}</div>
29
+ <div class="bar-bg">
30
+ <div class="bar-fill" style="width: {confidence * 100:.2f}%"></div>
31
+ </div>
32
+ </div>
33
+ """,
34
+ unsafe_allow_html=True,
35
+ )
36
+
37
+ def render_top_predictions(prediction):
38
+ with st.expander("View Top-3 Predictions"):
39
+ for task, result in prediction.items():
40
+ st.markdown(f"**{task}**")
41
+
42
+ for item in result["top_3"]:
43
+ st.write(f"{item['label']} — {format_percent(item['confidence'])}")
44
+
45
+ st.divider()
46
+
47
+
48
+ def render_metrics(metrics):
49
+ if not metrics:
50
+ return
51
+
52
+ overall = metrics.get("overall_metrics", {})
53
+ if not overall:
54
+ return
55
+
56
+ st.subheader("Model Evaluation")
57
+ col1, col2, col3, col4 = st.columns(4)
58
+
59
+ col1.metric("Average Accuracy", format_percent(overall.get("average_accuracy", 0)))
60
+ col2.metric("Weighted F1", format_percent(overall.get("average_weighted_f1", 0)))
61
+ col3.metric("Top-3 Accuracy", format_percent(overall.get("average_top3_accuracy", 0)))
62
+ col4.metric("Test Samples", f"{overall.get('test_samples', 0):,}")
autocatalog/__init__.py ADDED
File without changes
autocatalog/data/__init__.py ADDED
File without changes
autocatalog/data/dataset.py ADDED
File without changes
autocatalog/data/preprocessing.py ADDED
File without changes
autocatalog/evaluation/__init__.py ADDED
File without changes
autocatalog/evaluation/error_analysis.py ADDED
File without changes
autocatalog/evaluation/evaluate.py ADDED
File without changes
autocatalog/evaluation/metrics.py ADDED
File without changes
autocatalog/inference/__init__.py ADDED
File without changes
autocatalog/inference/catalog_generator.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def normalize_text(value):
2
+ if value is None:
3
+ return ""
4
+
5
+ value = str(value).strip()
6
+ value = value.replace("_", " ")
7
+ value = value.replace("-", " ")
8
+ value = " ".join(value.split())
9
+
10
+ return value
11
+
12
+
13
+ def generate_title(predicted_labels):
14
+ parts = []
15
+
16
+ gender = normalize_text(predicted_labels.get("gender"))
17
+ color = normalize_text(predicted_labels.get("baseColour"))
18
+ usage = normalize_text(predicted_labels.get("usage"))
19
+ article_type = normalize_text(predicted_labels.get("articleType"))
20
+
21
+ for value in [gender, color, usage, article_type]:
22
+ if value:
23
+ parts.append(value)
24
+
25
+ return " ".join(parts)
26
+
27
+
28
+ def generate_search_tags(predicted_labels):
29
+ tags = []
30
+ unique_tags = []
31
+
32
+ gender = normalize_text(predicted_labels.get("gender")).lower()
33
+ master_category = normalize_text(predicted_labels.get("masterCategory")).lower()
34
+ sub_category = normalize_text(predicted_labels.get("subCategory")).lower()
35
+ article_type = normalize_text(predicted_labels.get("articleType")).lower()
36
+ color = normalize_text(predicted_labels.get("baseColour")).lower()
37
+ season = normalize_text(predicted_labels.get("season")).lower()
38
+ usage = normalize_text(predicted_labels.get("usage")).lower()
39
+
40
+ if gender and article_type:
41
+ tags.append(f"{gender} {article_type}")
42
+
43
+ if color and article_type:
44
+ tags.append(f"{color} {article_type}")
45
+
46
+ if usage and sub_category:
47
+ tags.append(f"{usage} {sub_category}")
48
+
49
+ if season and master_category:
50
+ tags.append(f"{season} {master_category}")
51
+
52
+ if gender and usage:
53
+ tags.append(f"{gender} {usage} wear")
54
+
55
+ if color and usage:
56
+ tags.append(f"{color} {usage} fashion")
57
+
58
+ if sub_category:
59
+ tags.append(sub_category)
60
+
61
+ if article_type:
62
+ tags.append(article_type)
63
+
64
+ for tag in tags:
65
+ tag = " ".join(tag.split())
66
+
67
+ if tag and tag not in unique_tags:
68
+ unique_tags.append(tag)
69
+
70
+ return unique_tags
71
+
72
+
73
+ def generate_catalog_output(predicted_labels):
74
+ suggested_title = generate_title(predicted_labels)
75
+ search_tags = generate_search_tags(predicted_labels)
76
+
77
+ return {
78
+ "suggested_title": suggested_title,
79
+ "search_tags": search_tags,
80
+ "json_export": {
81
+ "gender": predicted_labels.get("gender"),
82
+ "category": predicted_labels.get("masterCategory"),
83
+ "subcategory": predicted_labels.get("subCategory"),
84
+ "article_type": predicted_labels.get("articleType"),
85
+ "color": predicted_labels.get("baseColour"),
86
+ "season": predicted_labels.get("season"),
87
+ "usage": predicted_labels.get("usage"),
88
+ "title": suggested_title,
89
+ "tags": search_tags,
90
+ },
91
+ }
autocatalog/inference/predictor.py ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import time
3
+ from pathlib import Path
4
+
5
+ import torch
6
+ from PIL import Image
7
+ from huggingface_hub import hf_hub_download
8
+ from transformers import CLIPImageProcessor
9
+
10
+ from autocatalog.models.multitask_clip import CLIPMultiTaskClassifier
11
+ from autocatalog.inference.catalog_generator import generate_catalog_output
12
+
13
+
14
+ class AutoCatalogPredictor:
15
+ def __init__(
16
+ self,
17
+ repo_id="mohsin416/autocatalogai-clip-multitask",
18
+ device=None,
19
+ top_k=3,
20
+ ):
21
+ self.repo_id = repo_id
22
+ self.device = device or ("cuda" if torch.cuda.is_available() else "cpu")
23
+ self.top_k = top_k
24
+
25
+ self.model_path = hf_hub_download(
26
+ repo_id=self.repo_id,
27
+ filename="model.pt",
28
+ repo_type="model"
29
+ )
30
+
31
+ self.config_path = hf_hub_download(
32
+ repo_id=self.repo_id,
33
+ filename="config.json",
34
+ repo_type="model"
35
+ )
36
+
37
+ self.label_maps_path = hf_hub_download(
38
+ repo_id=self.repo_id,
39
+ filename="label_maps.json",
40
+ repo_type="model"
41
+ )
42
+
43
+ self.metrics_path = self._try_download("metrics.json")
44
+
45
+ self.config = self._load_json(self.config_path)
46
+ self.label_maps = self._load_json(self.label_maps_path)
47
+ self.metrics = self._load_json(self.metrics_path) if self.metrics_path else {}
48
+
49
+ self.tasks = self.config.get("tasks") or list(self.label_maps.keys())
50
+ self.base_model_name = self.config.get("base_model_name") or self.config.get("model_name")
51
+ self.hidden_dim = self.config.get("hidden_dim", 512)
52
+ self.dropout = self.config.get("dropout", 0.2)
53
+ self.unfreeze_last_n_vision_layers = self.config.get("unfreeze_last_n_vision_layers", 0)
54
+
55
+ if self.base_model_name is None:
56
+ self.base_model_name = "openai/clip-vit-base-patch32"
57
+
58
+ self.task_num_classes = self._get_task_num_classes()
59
+ self.processor = CLIPImageProcessor.from_pretrained(self.base_model_name)
60
+ self.model = self._load_model()
61
+ self.model.eval()
62
+
63
+
64
+ def _try_download(self, filename):
65
+ try:
66
+ return hf_hub_download(
67
+ repo_id=self.repo_id,
68
+ filename=filename,
69
+ repo_type="model",
70
+ )
71
+ except Exception:
72
+ return None
73
+
74
+
75
+ def _load_json(self, path):
76
+ with open(path, "r", encoding="utf-8") as file:
77
+ return json.load(file)
78
+
79
+ def _safe_torch_load(self, path):
80
+ try:
81
+ return torch.load(
82
+ path,
83
+ map_location=self.device,
84
+ weights_only=False
85
+ )
86
+
87
+ except TypeError:
88
+ return torch.load(
89
+ path,
90
+ map_location=self.device
91
+ )
92
+
93
+ def _get_task_num_classes(self):
94
+ if "task_num_classes" in self.config:
95
+ return {
96
+ task: int(value)
97
+ for task, value in self.config["task_num_classes"].items()
98
+ }
99
+
100
+ task_num_classes = {}
101
+ for task in self.tasks:
102
+ task_num_classes[task] = len(self.label_maps[task]["label2id"])
103
+
104
+ return task_num_classes
105
+
106
+ def _load_model(self):
107
+ checkpoint = self._safe_torch_load(self.model_path)
108
+
109
+ model = CLIPMultiTaskClassifier(
110
+ model_name=self.base_model_name,
111
+ task_num_classes=self.task_num_classes,
112
+ hidden_dim=self.hidden_dim,
113
+ droput=self.dropout,
114
+ unfreeze_last_n_vision_layers=self.unfreeze_last_n_vision_layers
115
+ )
116
+
117
+ state_dict = checkpoint.get("model_state_dict", checkpoint)
118
+ model.load_state_dict(state_dict, strict=True)
119
+ model.to(self.device)
120
+
121
+ return model
122
+
123
+
124
+ def _prepare_image(self, image):
125
+ if isinstance(image, Image.Image):
126
+ return image.convert("RGB")
127
+
128
+ if isinstance(image, (str, Path)):
129
+ return Image.open(image).convert("RGB")
130
+
131
+
132
+ def predict(self, image, top_k=None):
133
+ top_k = top_k or self.top_k
134
+ image = self._prepare_image(image)
135
+
136
+ inputs = self.processor(
137
+ images=image,
138
+ return_tensors="pt"
139
+ )
140
+ pixel_values = inputs["pixel_values"].to(self.device)
141
+
142
+ if self.device == "cuda":
143
+ torch.cuda.synchronize()
144
+
145
+ start_time = time.time()
146
+
147
+ with torch.no_grad():
148
+ outputs = self.model(pixel_values)
149
+
150
+ if self.device == "cuda":
151
+ torch.cuda.synchronize()
152
+
153
+ end_time = time.time()
154
+ prediction = {}
155
+ simple_predictions = {}
156
+
157
+ for task in self.tasks:
158
+ logits = outputs[task]
159
+ probs = torch.softmax(logits, dim=-1).squeeze(0)
160
+
161
+ k = min(top_k, probs.shape[0])
162
+ top_probs, top_indices = torch.topk(
163
+ probs,
164
+ k=k
165
+ )
166
+
167
+ top_predictions = []
168
+ for prob, idx in zip(top_probs, top_indices):
169
+ label = self.label_maps[task]["id2label"][str(int(idx.item()))]
170
+
171
+ top_predictions.append({
172
+ "label": label,
173
+ "confidence": float(prob.item()),
174
+ })
175
+
176
+ prediction[task] = {
177
+ "label": top_predictions[0]["label"],
178
+ "confidence": top_predictions[0]["confidence"],
179
+ "top_3": top_predictions,
180
+ }
181
+
182
+ simple_predictions[task] = top_predictions[0]["label"]
183
+
184
+ catalog_output = generate_catalog_output(simple_predictions)
185
+
186
+ return {
187
+ "prediction": prediction,
188
+ "catalog_output": catalog_output,
189
+ "runtime": {
190
+ "device": self.device,
191
+ "inference_time_ms": float((end_time - start_time) * 1000),
192
+ "model": self.base_model_name,
193
+ "repo_id": self.repo_id,
194
+ },
195
+ }
196
+
197
+ def get_model_metrics(self):
198
+ return self.metrics
autocatalog/models/__init__.py ADDED
File without changes
autocatalog/models/heads.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch.nn as nn
2
+
3
+ class ClassificationHead(nn.Module):
4
+ def __init__(self, embedding_dim, num_classes, hidden_dim=512, dropout=0.2):
5
+ super().__init__()
6
+
7
+ self.net = nn.Sequential(
8
+ nn.LayerNorm(embedding_dim),
9
+ nn.Linear(embedding_dim, hidden_dim),
10
+ nn.GELU(),
11
+ nn.Dropout(dropout),
12
+ nn.Linear(hidden_dim, num_classes)
13
+ )
14
+
15
+ def forward(self, x):
16
+ return self.net(x)
autocatalog/models/multitask_clip.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ from transformers import CLIPModel
5
+ from autocatalog.models.heads import ClassificationHead
6
+
7
+ class CLIPMultiTaskClassifier(nn.Module):
8
+ def __init__(self, model_name, task_num_classes, hidden_dim=512, droput=0.2, unfreeze_last_n_vision_layers=0):
9
+ super().__init__()
10
+ self.clip = CLIPModel.from_pretrained(model_name)
11
+
12
+ for param in self.clip.parameters():
13
+ param.requires_grad = False
14
+
15
+ if unfreeze_last_n_vision_layers > 0:
16
+ vision_layer = self.clip.vision_model.encoder.layers
17
+
18
+ for layer in vision_layer[-unfreeze_last_n_vision_layers:]:
19
+ for param in layer.parameters():
20
+ param.requires_grad = True
21
+
22
+ for param in self.clip.visual_projection.parameters():
23
+ param.requires_grad = True
24
+
25
+ for param in self.clip.vision_model.post_layernorm.parameters():
26
+ param.requires_grad = True
27
+
28
+ embedding_dim = self.clip.config.projection_dim
29
+ self.heads = nn.ModuleDict({
30
+ task : ClassificationHead(
31
+ embedding_dim=embedding_dim,
32
+ num_classes=num_classes,
33
+ hidden_dim=hidden_dim,
34
+ dropout=droput,
35
+ )
36
+ for task, num_classes in task_num_classes.items()
37
+ })
38
+
39
+
40
+ def forward(self, pixel_values):
41
+ image_features = self.clip.get_image_features(pixel_values=pixel_values)
42
+ image_features = F.normalize(image_features, dim=-1)
43
+
44
+ return {
45
+ task : head(image_features)
46
+ for task, head in self.heads.items()
47
+ }
autocatalog/training/__init__.py ADDED
File without changes
autocatalog/training/losses.py ADDED
File without changes
autocatalog/training/train.py ADDED
File without changes
autocatalog/utils/__init__.py ADDED
File without changes
autocatalog/utils/config.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ import yaml
3
+
4
+ def load_config(path):
5
+ config_path = Path(path)
6
+ if not config_path.exists():
7
+ return {}
8
+
9
+ with open(config_path, "r", encoding="utf-8") as file:
10
+ data = yaml.safe_load(file)
11
+
12
+ return data or {}
autocatalog/utils/logger.py ADDED
File without changes
autocatalog/utils/seed.py ADDED
File without changes
configs/config.yaml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ project:
2
+ name: AutoCatalogAI
3
+ version: v1
4
+
5
+ model:
6
+ repo_id: mohsin416/autocatalogai-clip-multitask
7
+
8
+ inference:
9
+ device: null
10
+ top_k: 3
model_card.md ADDED
File without changes
notebooks/01_dataset_experiment.ipynb ADDED
@@ -0,0 +1,786 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "id": "27ea1a10",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "!pip install -q torch torchvision transformers datasets pillow pandas scikit-learn tqdm huggingface_hub matplotlib"
11
+ ]
12
+ },
13
+ {
14
+ "cell_type": "code",
15
+ "execution_count": 2,
16
+ "id": "d246862c",
17
+ "metadata": {},
18
+ "outputs": [],
19
+ "source": [
20
+ "import os\n",
21
+ "import json\n",
22
+ "import random\n",
23
+ "import time\n",
24
+ "from pathlib import Path\n",
25
+ "\n",
26
+ "import numpy as np\n",
27
+ "import pandas as pd\n",
28
+ "import torch\n",
29
+ "import torch.nn as nn\n",
30
+ "import torch.nn.functional as F\n",
31
+ "\n",
32
+ "from PIL import Image\n",
33
+ "from tqdm.auto import tqdm\n",
34
+ "from datasets import load_dataset\n",
35
+ "from torch.utils.data import Dataset, DataLoader\n",
36
+ "from transformers import CLIPModel, CLIPImageProcessor, get_cosine_schedule_with_warmup\n",
37
+ "\n",
38
+ "from sklearn.model_selection import train_test_split\n",
39
+ "from sklearn.metrics import accuracy_score, f1_score, confusion_matrix, classification_report"
40
+ ]
41
+ },
42
+ {
43
+ "cell_type": "markdown",
44
+ "id": "7e09b0d2",
45
+ "metadata": {},
46
+ "source": [
47
+ "_Config_"
48
+ ]
49
+ },
50
+ {
51
+ "cell_type": "code",
52
+ "execution_count": 3,
53
+ "id": "697fa289",
54
+ "metadata": {},
55
+ "outputs": [],
56
+ "source": [
57
+ "DATASET_NAME = \"ashraq/fashion-product-images-small\"\n",
58
+ "MODEL_NAME = \"openai/clip-vit-base-patch32\"\n",
59
+ "TASKS = [\n",
60
+ " \"gender\",\n",
61
+ " \"masterCategory\",\n",
62
+ " \"subCategory\",\n",
63
+ " \"articleType\",\n",
64
+ " \"baseColour\",\n",
65
+ " \"season\",\n",
66
+ " \"usage\"\n",
67
+ "]\n",
68
+ "\n",
69
+ "SEED = 42\n",
70
+ "TRAIN_RATIO = 0.70\n",
71
+ "VAL_RATIO = 0.15\n",
72
+ "TEST_RATIO = 0.15\n",
73
+ "\n",
74
+ "BATCH_SIZE = 32\n",
75
+ "EPOCHS = 5\n",
76
+ "\n",
77
+ "HEAD_LR = 3e-4\n",
78
+ "BACKBONE_LR = 1e-5\n",
79
+ "WEIGHT_DECAY = 1e-2\n",
80
+ "\n",
81
+ "HIDDEN_DIM = 512\n",
82
+ "DROPOUT = 0.20\n",
83
+ "\n",
84
+ "UNFREEZE_LAST_N_VISION_LAYERS = 2\n",
85
+ "\n",
86
+ "USE_CLASS_WEIGHTS = True\n",
87
+ "USE_AMP = True\n",
88
+ "\n",
89
+ "MAX_GRAD_NORM = 1.0\n",
90
+ "EARLY_STOPPING_PATIENCE = 2\n",
91
+ "NUM_WORKERS = 2\n",
92
+ "DEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\""
93
+ ]
94
+ },
95
+ {
96
+ "cell_type": "code",
97
+ "execution_count": 4,
98
+ "id": "ef7f600c",
99
+ "metadata": {},
100
+ "outputs": [
101
+ {
102
+ "name": "stdout",
103
+ "output_type": "stream",
104
+ "text": [
105
+ "Device: cuda\n",
106
+ "Model: openai/clip-vit-base-patch32\n"
107
+ ]
108
+ }
109
+ ],
110
+ "source": [
111
+ "OUTPUT_DIR = Path(\"artifacts/models/autocatalogai_clip\")\n",
112
+ "EVAL_DIR = Path(\"artifacts/evaluation\")\n",
113
+ "PLOT_DIR = Path(\"artifacts/plots\")\n",
114
+ "PROCESSED_DIR = Path(\"data/processed\")\n",
115
+ "\n",
116
+ "for directory in [OUTPUT_DIR, EVAL_DIR, PLOT_DIR, PROCESSED_DIR]:\n",
117
+ " directory.mkdir(parents=True, exist_ok=True)\n",
118
+ "\n",
119
+ "os.environ[\"TOKENIZERS_PARALLELISM\"] = \"false\"\n",
120
+ "\n",
121
+ "print(\"Device:\", DEVICE)\n",
122
+ "print(\"Model:\", MODEL_NAME)"
123
+ ]
124
+ },
125
+ {
126
+ "cell_type": "code",
127
+ "execution_count": 5,
128
+ "id": "d5242fdc",
129
+ "metadata": {},
130
+ "outputs": [],
131
+ "source": [
132
+ "def set_seed(seed):\n",
133
+ " random.seed(seed)\n",
134
+ " np.random.seed(seed)\n",
135
+ " torch.manual_seed(seed)\n",
136
+ " \n",
137
+ " if torch.cuda.is_available():\n",
138
+ " torch.cuda.manual_seed_all(seed)\n",
139
+ " torch.backends.cudnn.benchmark = True\n",
140
+ "\n",
141
+ "\n",
142
+ "set_seed(SEED)"
143
+ ]
144
+ },
145
+ {
146
+ "cell_type": "markdown",
147
+ "id": "f07779e1",
148
+ "metadata": {},
149
+ "source": [
150
+ "#### Load Full Dataset"
151
+ ]
152
+ },
153
+ {
154
+ "cell_type": "code",
155
+ "execution_count": 6,
156
+ "id": "280fb69a",
157
+ "metadata": {},
158
+ "outputs": [
159
+ {
160
+ "name": "stderr",
161
+ "output_type": "stream",
162
+ "text": [
163
+ "/usr/local/lib/python3.12/dist-packages/huggingface_hub/utils/_auth.py:138: UserWarning: \n",
164
+ "Error while fetching `HF_TOKEN` secret value from your vault: 'Requesting secret HF_TOKEN timed out. Secrets can only be fetched when running from the Colab UI.'.\n",
165
+ "You are not authenticated with the Hugging Face Hub in this notebook.\n",
166
+ "If the error persists, please let us know by opening an issue on GitHub (https://github.com/huggingface/huggingface_hub/issues/new).\n",
167
+ " warnings.warn(\n"
168
+ ]
169
+ },
170
+ {
171
+ "data": {
172
+ "application/vnd.jupyter.widget-view+json": {
173
+ "model_id": "f72208f4dc6348fc87d90c6c22769b0f",
174
+ "version_major": 2,
175
+ "version_minor": 0
176
+ },
177
+ "text/plain": [
178
+ "README.md: 0%| | 0.00/867 [00:00<?, ?B/s]"
179
+ ]
180
+ },
181
+ "metadata": {},
182
+ "output_type": "display_data"
183
+ },
184
+ {
185
+ "name": "stderr",
186
+ "output_type": "stream",
187
+ "text": [
188
+ "Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.\n",
189
+ "WARNING:huggingface_hub.utils._http:Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.\n"
190
+ ]
191
+ },
192
+ {
193
+ "data": {
194
+ "application/vnd.jupyter.widget-view+json": {
195
+ "model_id": "ee563a2427bc4eb9b91f160c593b5477",
196
+ "version_major": 2,
197
+ "version_minor": 0
198
+ },
199
+ "text/plain": [
200
+ "data/train-00000-of-00002-6cff4c59f91661(…): 0%| | 0.00/136M [00:00<?, ?B/s]"
201
+ ]
202
+ },
203
+ "metadata": {},
204
+ "output_type": "display_data"
205
+ },
206
+ {
207
+ "data": {
208
+ "application/vnd.jupyter.widget-view+json": {
209
+ "model_id": "2960cb54d2ba4573a7ef82894e897ad6",
210
+ "version_major": 2,
211
+ "version_minor": 0
212
+ },
213
+ "text/plain": [
214
+ "data/train-00001-of-00002-bb459e5ac5f01e(…): 0%| | 0.00/135M [00:00<?, ?B/s]"
215
+ ]
216
+ },
217
+ "metadata": {},
218
+ "output_type": "display_data"
219
+ },
220
+ {
221
+ "data": {
222
+ "application/vnd.jupyter.widget-view+json": {
223
+ "model_id": "7aa26aab19ff40e1a74b28de564323e2",
224
+ "version_major": 2,
225
+ "version_minor": 0
226
+ },
227
+ "text/plain": [
228
+ "Generating train split: 0%| | 0/44072 [00:00<?, ? examples/s]"
229
+ ]
230
+ },
231
+ "metadata": {},
232
+ "output_type": "display_data"
233
+ },
234
+ {
235
+ "name": "stdout",
236
+ "output_type": "stream",
237
+ "text": [
238
+ "Dataset({\n",
239
+ " features: ['id', 'gender', 'masterCategory', 'subCategory', 'articleType', 'baseColour', 'season', 'year', 'usage', 'productDisplayName', 'image'],\n",
240
+ " num_rows: 44072\n",
241
+ "})\n",
242
+ "['id', 'gender', 'masterCategory', 'subCategory', 'articleType', 'baseColour', 'season', 'year', 'usage', 'productDisplayName', 'image']\n",
243
+ "Total rows: 44072\n"
244
+ ]
245
+ }
246
+ ],
247
+ "source": [
248
+ "raw_dataset = load_dataset(DATASET_NAME, split=\"train\")\n",
249
+ "\n",
250
+ "print(raw_dataset)\n",
251
+ "print(raw_dataset.column_names)\n",
252
+ "print(\"Total rows:\", len(raw_dataset))"
253
+ ]
254
+ },
255
+ {
256
+ "cell_type": "markdown",
257
+ "id": "5edb84b5",
258
+ "metadata": {},
259
+ "source": [
260
+ "#### Clean Dataset"
261
+ ]
262
+ },
263
+ {
264
+ "cell_type": "code",
265
+ "execution_count": 7,
266
+ "id": "bdf04d04",
267
+ "metadata": {},
268
+ "outputs": [],
269
+ "source": [
270
+ "missing_columns = [task for task in TASKS if task not in raw_dataset.column_names]\n",
271
+ "\n",
272
+ "if \"image\" not in raw_dataset.column_names:\n",
273
+ " raise ValueError(f\"Dataset must contain image column. Found: {raw_dataset.column_names}\")\n",
274
+ "\n",
275
+ "if missing_columns:\n",
276
+ " raise ValueError(f\"Missing task columns: {missing_columns}\")"
277
+ ]
278
+ },
279
+ {
280
+ "cell_type": "code",
281
+ "execution_count": 8,
282
+ "id": "721ec563",
283
+ "metadata": {},
284
+ "outputs": [
285
+ {
286
+ "data": {
287
+ "application/vnd.jupyter.widget-view+json": {
288
+ "model_id": "49bb2b38bea040a5896ceaf814335e21",
289
+ "version_major": 2,
290
+ "version_minor": 0
291
+ },
292
+ "text/plain": [
293
+ "Filter: 0%| | 0/44072 [00:00<?, ? examples/s]"
294
+ ]
295
+ },
296
+ "metadata": {},
297
+ "output_type": "display_data"
298
+ },
299
+ {
300
+ "name": "stdout",
301
+ "output_type": "stream",
302
+ "text": [
303
+ "Before cleaning: 44072\n",
304
+ "After cleaning: 44072\n"
305
+ ]
306
+ }
307
+ ],
308
+ "source": [
309
+ "def is_valid_row(row):\n",
310
+ " for task in TASKS:\n",
311
+ " value = row.get(task)\n",
312
+ " if value is None:\n",
313
+ " return False\n",
314
+ " if str(value).strip() == \"\":\n",
315
+ " return False\n",
316
+ " \n",
317
+ " return row.get(\"image\") is not None\n",
318
+ "\n",
319
+ "\n",
320
+ "clean_dataset = raw_dataset.filter(is_valid_row)\n",
321
+ "print(\"Before cleaning:\", len(raw_dataset))\n",
322
+ "print(\"After cleaning:\", len(clean_dataset))"
323
+ ]
324
+ },
325
+ {
326
+ "cell_type": "markdown",
327
+ "id": "59e5f003",
328
+ "metadata": {},
329
+ "source": [
330
+ "#### Create Metadata DataFrame"
331
+ ]
332
+ },
333
+ {
334
+ "cell_type": "code",
335
+ "execution_count": 9,
336
+ "id": "17fecce2",
337
+ "metadata": {},
338
+ "outputs": [
339
+ {
340
+ "data": {
341
+ "text/html": [
342
+ "\n",
343
+ " <div id=\"df-819ad3e2-5ed3-4458-823f-695ea26d18b3\" class=\"colab-df-container\">\n",
344
+ " <div>\n",
345
+ "<style scoped>\n",
346
+ " .dataframe tbody tr th:only-of-type {\n",
347
+ " vertical-align: middle;\n",
348
+ " }\n",
349
+ "\n",
350
+ " .dataframe tbody tr th {\n",
351
+ " vertical-align: top;\n",
352
+ " }\n",
353
+ "\n",
354
+ " .dataframe thead th {\n",
355
+ " text-align: right;\n",
356
+ " }\n",
357
+ "</style>\n",
358
+ "<table border=\"1\" class=\"dataframe\">\n",
359
+ " <thead>\n",
360
+ " <tr style=\"text-align: right;\">\n",
361
+ " <th></th>\n",
362
+ " <th>gender</th>\n",
363
+ " <th>masterCategory</th>\n",
364
+ " <th>subCategory</th>\n",
365
+ " <th>articleType</th>\n",
366
+ " <th>baseColour</th>\n",
367
+ " <th>season</th>\n",
368
+ " <th>usage</th>\n",
369
+ " <th>id</th>\n",
370
+ " <th>productDisplayName</th>\n",
371
+ " <th>dataset_idx</th>\n",
372
+ " </tr>\n",
373
+ " </thead>\n",
374
+ " <tbody>\n",
375
+ " <tr>\n",
376
+ " <th>0</th>\n",
377
+ " <td>Men</td>\n",
378
+ " <td>Apparel</td>\n",
379
+ " <td>Topwear</td>\n",
380
+ " <td>Shirts</td>\n",
381
+ " <td>Navy Blue</td>\n",
382
+ " <td>Fall</td>\n",
383
+ " <td>Casual</td>\n",
384
+ " <td>15970</td>\n",
385
+ " <td>Turtle Check Men Navy Blue Shirt</td>\n",
386
+ " <td>0</td>\n",
387
+ " </tr>\n",
388
+ " <tr>\n",
389
+ " <th>1</th>\n",
390
+ " <td>Men</td>\n",
391
+ " <td>Apparel</td>\n",
392
+ " <td>Bottomwear</td>\n",
393
+ " <td>Jeans</td>\n",
394
+ " <td>Blue</td>\n",
395
+ " <td>Summer</td>\n",
396
+ " <td>Casual</td>\n",
397
+ " <td>39386</td>\n",
398
+ " <td>Peter England Men Party Blue Jeans</td>\n",
399
+ " <td>1</td>\n",
400
+ " </tr>\n",
401
+ " <tr>\n",
402
+ " <th>2</th>\n",
403
+ " <td>Women</td>\n",
404
+ " <td>Accessories</td>\n",
405
+ " <td>Watches</td>\n",
406
+ " <td>Watches</td>\n",
407
+ " <td>Silver</td>\n",
408
+ " <td>Winter</td>\n",
409
+ " <td>Casual</td>\n",
410
+ " <td>59263</td>\n",
411
+ " <td>Titan Women Silver Watch</td>\n",
412
+ " <td>2</td>\n",
413
+ " </tr>\n",
414
+ " <tr>\n",
415
+ " <th>3</th>\n",
416
+ " <td>Men</td>\n",
417
+ " <td>Apparel</td>\n",
418
+ " <td>Bottomwear</td>\n",
419
+ " <td>Track Pants</td>\n",
420
+ " <td>Black</td>\n",
421
+ " <td>Fall</td>\n",
422
+ " <td>Casual</td>\n",
423
+ " <td>21379</td>\n",
424
+ " <td>Manchester United Men Solid Black Track Pants</td>\n",
425
+ " <td>3</td>\n",
426
+ " </tr>\n",
427
+ " <tr>\n",
428
+ " <th>4</th>\n",
429
+ " <td>Men</td>\n",
430
+ " <td>Apparel</td>\n",
431
+ " <td>Topwear</td>\n",
432
+ " <td>Tshirts</td>\n",
433
+ " <td>Grey</td>\n",
434
+ " <td>Summer</td>\n",
435
+ " <td>Casual</td>\n",
436
+ " <td>53759</td>\n",
437
+ " <td>Puma Men Grey T-shirt</td>\n",
438
+ " <td>4</td>\n",
439
+ " </tr>\n",
440
+ " </tbody>\n",
441
+ "</table>\n",
442
+ "</div>\n",
443
+ " <div class=\"colab-df-buttons\">\n",
444
+ " \n",
445
+ " <div class=\"colab-df-container\">\n",
446
+ " <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-819ad3e2-5ed3-4458-823f-695ea26d18b3')\"\n",
447
+ " title=\"Convert this dataframe to an interactive table.\"\n",
448
+ " style=\"display:none;\">\n",
449
+ " \n",
450
+ " <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\">\n",
451
+ " <path d=\"M120-120v-720h720v720H120Zm60-500h600v-160H180v160Zm220 220h160v-160H400v160Zm0 220h160v-160H400v160ZM180-400h160v-160H180v160Zm440 0h160v-160H620v160ZM180-180h160v-160H180v160Zm440 0h160v-160H620v160Z\"/>\n",
452
+ " </svg>\n",
453
+ " </button>\n",
454
+ " \n",
455
+ " <style>\n",
456
+ " .colab-df-container {\n",
457
+ " display:flex;\n",
458
+ " gap: 12px;\n",
459
+ " }\n",
460
+ "\n",
461
+ " .colab-df-convert {\n",
462
+ " background-color: #E8F0FE;\n",
463
+ " border: none;\n",
464
+ " border-radius: 50%;\n",
465
+ " cursor: pointer;\n",
466
+ " display: none;\n",
467
+ " fill: #1967D2;\n",
468
+ " height: 32px;\n",
469
+ " padding: 0 0 0 0;\n",
470
+ " width: 32px;\n",
471
+ " }\n",
472
+ "\n",
473
+ " .colab-df-convert:hover {\n",
474
+ " background-color: #E2EBFA;\n",
475
+ " box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
476
+ " fill: #174EA6;\n",
477
+ " }\n",
478
+ "\n",
479
+ " .colab-df-buttons div {\n",
480
+ " margin-bottom: 4px;\n",
481
+ " }\n",
482
+ "\n",
483
+ " [theme=dark] .colab-df-convert {\n",
484
+ " background-color: #3B4455;\n",
485
+ " fill: #D2E3FC;\n",
486
+ " }\n",
487
+ "\n",
488
+ " [theme=dark] .colab-df-convert:hover {\n",
489
+ " background-color: #434B5C;\n",
490
+ " box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
491
+ " filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
492
+ " fill: #FFFFFF;\n",
493
+ " }\n",
494
+ " </style>\n",
495
+ "\n",
496
+ " <script>\n",
497
+ " const buttonEl =\n",
498
+ " document.querySelector('#df-819ad3e2-5ed3-4458-823f-695ea26d18b3 button.colab-df-convert');\n",
499
+ " buttonEl.style.display =\n",
500
+ " google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
501
+ "\n",
502
+ " async function convertToInteractive(key) {\n",
503
+ " const element = document.querySelector('#df-819ad3e2-5ed3-4458-823f-695ea26d18b3');\n",
504
+ " const dataTable =\n",
505
+ " await google.colab.kernel.invokeFunction('convertToInteractive',\n",
506
+ " [key], {});\n",
507
+ " if (!dataTable) return;\n",
508
+ "\n",
509
+ " const docLinkHtml = 'Like what you see? Visit the ' +\n",
510
+ " '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
511
+ " + ' to learn more about interactive tables.';\n",
512
+ " element.innerHTML = '';\n",
513
+ " dataTable['output_type'] = 'display_data';\n",
514
+ " await google.colab.output.renderOutput(dataTable, element);\n",
515
+ " const docLink = document.createElement('div');\n",
516
+ " docLink.innerHTML = docLinkHtml;\n",
517
+ " element.appendChild(docLink);\n",
518
+ " }\n",
519
+ " </script>\n",
520
+ " </div>\n",
521
+ " \n",
522
+ " </div>\n",
523
+ " </div>\n",
524
+ " "
525
+ ],
526
+ "text/plain": [
527
+ " gender masterCategory subCategory articleType baseColour season usage \\\n",
528
+ "0 Men Apparel Topwear Shirts Navy Blue Fall Casual \n",
529
+ "1 Men Apparel Bottomwear Jeans Blue Summer Casual \n",
530
+ "2 Women Accessories Watches Watches Silver Winter Casual \n",
531
+ "3 Men Apparel Bottomwear Track Pants Black Fall Casual \n",
532
+ "4 Men Apparel Topwear Tshirts Grey Summer Casual \n",
533
+ "\n",
534
+ " id productDisplayName dataset_idx \n",
535
+ "0 15970 Turtle Check Men Navy Blue Shirt 0 \n",
536
+ "1 39386 Peter England Men Party Blue Jeans 1 \n",
537
+ "2 59263 Titan Women Silver Watch 2 \n",
538
+ "3 21379 Manchester United Men Solid Black Track Pants 3 \n",
539
+ "4 53759 Puma Men Grey T-shirt 4 "
540
+ ]
541
+ },
542
+ "execution_count": 9,
543
+ "metadata": {},
544
+ "output_type": "execute_result"
545
+ }
546
+ ],
547
+ "source": [
548
+ "metadata = {}\n",
549
+ "for_col = []\n",
550
+ "extra_columns = [\"id\", \"productDisplayName\"]\n",
551
+ "\n",
552
+ "for task in TASKS:\n",
553
+ " metadata[task] = [str(value).strip() for value in clean_dataset[task]]\n",
554
+ "\n",
555
+ "\n",
556
+ "for col in extra_columns:\n",
557
+ " if col in clean_dataset.column_names:\n",
558
+ " metadata[col] = clean_dataset[col]\n",
559
+ " for_col.append(col)\n",
560
+ "\n",
561
+ "df = pd.DataFrame(metadata)\n",
562
+ "df[\"dataset_idx\"] = np.arange(len(clean_dataset))\n",
563
+ "\n",
564
+ "df.head()"
565
+ ]
566
+ },
567
+ {
568
+ "cell_type": "markdown",
569
+ "id": "0d55ac03",
570
+ "metadata": {},
571
+ "source": [
572
+ "#### Label Distribution"
573
+ ]
574
+ },
575
+ {
576
+ "cell_type": "code",
577
+ "execution_count": 10,
578
+ "id": "5fe58be4",
579
+ "metadata": {},
580
+ "outputs": [],
581
+ "source": [
582
+ "label_distribution = {}\n",
583
+ "\n",
584
+ "for task in TASKS:\n",
585
+ " counts = df[task].value_counts().to_dict()\n",
586
+ " label_distribution[task] = counts\n",
587
+ " \n",
588
+ "with open(EVAL_DIR / \"label_distribution.json\", \"w\", encoding=\"utf-8\") as f:\n",
589
+ " json.dump(label_distribution, f, indent=2, ensure_ascii=False)"
590
+ ]
591
+ },
592
+ {
593
+ "cell_type": "code",
594
+ "execution_count": 11,
595
+ "id": "f77e1f0d",
596
+ "metadata": {},
597
+ "outputs": [
598
+ {
599
+ "data": {
600
+ "text/plain": [
601
+ "{'dataset_name': 'ashraq/fashion-product-images-small',\n",
602
+ " 'total_clean_samples': 44072,\n",
603
+ " 'tasks': ['gender',\n",
604
+ " 'masterCategory',\n",
605
+ " 'subCategory',\n",
606
+ " 'articleType',\n",
607
+ " 'baseColour',\n",
608
+ " 'season',\n",
609
+ " 'usage'],\n",
610
+ " 'num_classes': {'gender': 5,\n",
611
+ " 'masterCategory': 7,\n",
612
+ " 'subCategory': 45,\n",
613
+ " 'articleType': 141,\n",
614
+ " 'baseColour': 46,\n",
615
+ " 'season': 4,\n",
616
+ " 'usage': 8}}"
617
+ ]
618
+ },
619
+ "execution_count": 11,
620
+ "metadata": {},
621
+ "output_type": "execute_result"
622
+ }
623
+ ],
624
+ "source": [
625
+ "summary = {\n",
626
+ " \"dataset_name\": DATASET_NAME,\n",
627
+ " \"total_clean_samples\": len(df),\n",
628
+ " \"tasks\": TASKS,\n",
629
+ " \"num_classes\": {\n",
630
+ " task: int(df[task].nunique())\n",
631
+ " for task in TASKS\n",
632
+ " }\n",
633
+ "}\n",
634
+ "\n",
635
+ "with open(EVAL_DIR / \"dataset_summary.json\", \"w\", encoding=\"utf-8\") as f:\n",
636
+ " json.dump(summary, f, indent=2, ensure_ascii=False)\n",
637
+ "\n",
638
+ "summary"
639
+ ]
640
+ },
641
+ {
642
+ "cell_type": "markdown",
643
+ "id": "b73fb107",
644
+ "metadata": {},
645
+ "source": [
646
+ "#### Train / Validation / Test Split"
647
+ ]
648
+ },
649
+ {
650
+ "cell_type": "code",
651
+ "execution_count": 12,
652
+ "id": "ff328f23",
653
+ "metadata": {},
654
+ "outputs": [],
655
+ "source": [
656
+ "def make_safe_stratify_labels(series):\n",
657
+ " counts = series.value_counts()\n",
658
+ " return series.apply(lambda x: x if counts[x] >= 2 else \"__rare__\")\n",
659
+ "\n",
660
+ "stratify_labels = make_safe_stratify_labels(df[\"articleType\"])\n",
661
+ "all_indices = df.index.to_numpy()"
662
+ ]
663
+ },
664
+ {
665
+ "cell_type": "code",
666
+ "execution_count": 13,
667
+ "id": "c876c207",
668
+ "metadata": {},
669
+ "outputs": [],
670
+ "source": [
671
+ "train_idx, temp_idx = train_test_split(\n",
672
+ " all_indices,\n",
673
+ " test_size=0.30,\n",
674
+ " random_state=SEED,\n",
675
+ " stratify=stratify_labels\n",
676
+ ")\n",
677
+ "\n",
678
+ "temp_df = df.loc[temp_idx].copy()\n",
679
+ "temp_stratify_labels = make_safe_stratify_labels(temp_df[\"articleType\"])"
680
+ ]
681
+ },
682
+ {
683
+ "cell_type": "code",
684
+ "execution_count": 14,
685
+ "id": "0ffaad6a",
686
+ "metadata": {},
687
+ "outputs": [],
688
+ "source": [
689
+ "val_idx, test_idx = train_test_split(\n",
690
+ " temp_idx,\n",
691
+ " test_size=0.50,\n",
692
+ " random_state=SEED,\n",
693
+ " stratify=temp_stratify_labels\n",
694
+ ")\n",
695
+ "\n",
696
+ "train_df = df.loc[train_idx].copy()\n",
697
+ "val_df = df.loc[val_idx].copy()\n",
698
+ "test_df = df.loc[test_idx].copy()\n",
699
+ "\n",
700
+ "train_df[\"split\"] = \"train\"\n",
701
+ "val_df[\"split\"] = \"validation\"\n",
702
+ "test_df[\"split\"] = \"test\"\n",
703
+ "\n",
704
+ "train_df.to_csv(PROCESSED_DIR / \"train.csv\", index=False)\n",
705
+ "val_df.to_csv(PROCESSED_DIR / \"val.csv\", index=False)\n",
706
+ "test_df.to_csv(PROCESSED_DIR / \"test.csv\", index=False)"
707
+ ]
708
+ },
709
+ {
710
+ "cell_type": "code",
711
+ "execution_count": 15,
712
+ "id": "0bd769f2",
713
+ "metadata": {},
714
+ "outputs": [
715
+ {
716
+ "name": "stdout",
717
+ "output_type": "stream",
718
+ "text": [
719
+ "Train: 30850 0.7\n",
720
+ "Validation: 6611 0.15\n",
721
+ "Test: 6611 0.15\n"
722
+ ]
723
+ }
724
+ ],
725
+ "source": [
726
+ "print(\"Train:\", len(train_df), round(len(train_df) / len(df), 3))\n",
727
+ "print(\"Validation:\", len(val_df), round(len(val_df) / len(df), 3))\n",
728
+ "print(\"Test:\", len(test_df), round(len(test_df) / len(df), 3))"
729
+ ]
730
+ },
731
+ {
732
+ "cell_type": "markdown",
733
+ "id": "1b2990da",
734
+ "metadata": {},
735
+ "source": [
736
+ "#### Build HF Split Datasets"
737
+ ]
738
+ },
739
+ {
740
+ "cell_type": "code",
741
+ "execution_count": 16,
742
+ "id": "c42e21e5",
743
+ "metadata": {},
744
+ "outputs": [
745
+ {
746
+ "data": {
747
+ "text/plain": [
748
+ "(30850, 6611, 6611)"
749
+ ]
750
+ },
751
+ "execution_count": 16,
752
+ "metadata": {},
753
+ "output_type": "execute_result"
754
+ }
755
+ ],
756
+ "source": [
757
+ "train_hf_dataset = clean_dataset.select(train_df[\"dataset_idx\"].tolist())\n",
758
+ "val_hf_dataset = clean_dataset.select(val_df[\"dataset_idx\"].tolist())\n",
759
+ "test_hf_dataset = clean_dataset.select(test_df[\"dataset_idx\"].tolist())\n",
760
+ "\n",
761
+ "len(train_hf_dataset), len(val_hf_dataset), len(test_hf_dataset)"
762
+ ]
763
+ }
764
+ ],
765
+ "metadata": {
766
+ "kernelspec": {
767
+ "display_name": "Python 3 (ipykernel)",
768
+ "language": "python",
769
+ "name": "python3"
770
+ },
771
+ "language_info": {
772
+ "codemirror_mode": {
773
+ "name": "ipython",
774
+ "version": 3
775
+ },
776
+ "file_extension": ".py",
777
+ "mimetype": "text/x-python",
778
+ "name": "python",
779
+ "nbconvert_exporter": "python",
780
+ "pygments_lexer": "ipython3",
781
+ "version": "3.12.13"
782
+ }
783
+ },
784
+ "nbformat": 4,
785
+ "nbformat_minor": 5
786
+ }
requirements.txt CHANGED
@@ -1,3 +1,7 @@
1
- altair
2
- pandas
3
- streamlit
 
 
 
 
 
1
+ streamlit
2
+ torch
3
+ torchvision
4
+ transformers
5
+ huggingface_hub
6
+ Pillow
7
+ PyYAML
scripts/evaluate_model.py ADDED
File without changes
scripts/predict_image.py ADDED
File without changes
scripts/prepare_dataset.py ADDED
File without changes
scripts/train_baseline.py ADDED
File without changes
scripts/train_multitask_clip.py ADDED
File without changes
setup.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="autocatalogai",
5
+ version="0.1.0",
6
+ author="Md Mohsin",
7
+ author_email="siam.mohsin2005@gmail.com",
8
+ packages=find_packages()
9
+ )
src/streamlit_app.py DELETED
@@ -1,40 +0,0 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
- import streamlit as st
5
-
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
template.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from pathlib import Path
3
+
4
+ project_name = "AutoCatalogAI"
5
+ list_of_files = [
6
+ f"{project_name}/app/app.py",
7
+ f"{project_name}/app/inference.py",
8
+ f"{project_name}/app/templates/index.html",
9
+
10
+ f"{project_name}/autocatalog/__init__.py",
11
+
12
+ f"{project_name}/autocatalog/data/__init__.py",
13
+ f"{project_name}/autocatalog/data/dataset.py",
14
+ f"{project_name}/autocatalog/data/preprocessing.py",
15
+
16
+ f"{project_name}/autocatalog/models/__init__.py",
17
+ f"{project_name}/autocatalog/models/multitask_clip.py",
18
+ f"{project_name}/autocatalog/models/heads.py",
19
+
20
+ f"{project_name}/autocatalog/training/__init__.py",
21
+ f"{project_name}/autocatalog/training/train.py",
22
+ f"{project_name}/autocatalog/training/losses.py",
23
+
24
+ f"{project_name}/autocatalog/evaluation/__init__.py",
25
+ f"{project_name}/autocatalog/evaluation/evaluate.py",
26
+ f"{project_name}/autocatalog/evaluation/metrics.py",
27
+ f"{project_name}/autocatalog/evaluation/error_analysis.py",
28
+
29
+ f"{project_name}/autocatalog/inference/__init__.py",
30
+ f"{project_name}/autocatalog/inference/predictor.py",
31
+ f"{project_name}/autocatalog/inference/catalog_generator.py",
32
+
33
+ f"{project_name}/autocatalog/utils/__init__.py",
34
+ f"{project_name}/autocatalog/utils/config.py",
35
+ f"{project_name}/autocatalog/utils/logger.py",
36
+ f"{project_name}/autocatalog/utils/seed.py",
37
+
38
+ f"{project_name}/configs/config.yaml",
39
+
40
+ f"{project_name}/data/processed/train.csv",
41
+ f"{project_name}/data/processed/val.csv",
42
+ f"{project_name}/data/processed/test.csv",
43
+
44
+ f"{project_name}/artifacts/models/.gitkeep",
45
+ f"{project_name}/artifacts/evaluation/.gitkeep",
46
+ f"{project_name}/artifacts/plots/.gitkeep",
47
+ f"{project_name}/artifacts/examples/.gitkeep",
48
+
49
+ f"{project_name}/notebooks/01_dataset_experiment.ipynb",
50
+
51
+ f"{project_name}/scripts/prepare_dataset.py",
52
+ f"{project_name}/scripts/train_baseline.py",
53
+ f"{project_name}/scripts/train_multitask_clip.py",
54
+ f"{project_name}/scripts/evaluate_model.py",
55
+ f"{project_name}/scripts/predict_image.py",
56
+
57
+ f"{project_name}/tests/test_dataset.py",
58
+ f"{project_name}/tests/test_model.py",
59
+ f"{project_name}/tests/test_inference.py",
60
+
61
+ f"{project_name}/.gitignore",
62
+ f"{project_name}/README.md",
63
+ f"{project_name}/model_card.md",
64
+ f"{project_name}/requirements.txt",
65
+ f"{project_name}/Dockerfile",
66
+ f"{project_name}/setup.py",
67
+ ]
68
+
69
+ for filepath in list_of_files:
70
+ filepath = Path(filepath)
71
+ filedir, filename = os.path.split(filepath)
72
+
73
+ if filedir:
74
+ os.makedirs(filedir, exist_ok=True)
75
+
76
+ if not filepath.exists():
77
+ filepath.touch()
78
+ print(f"Created: {filepath}")
79
+ else:
80
+ print(f"Already exists: {filepath}")
tests/test_dataset.py ADDED
File without changes
tests/test_inference.py ADDED
File without changes
tests/test_model.py ADDED
File without changes