hnam25 commited on
Commit
064551d
·
verified ·
1 Parent(s): bd20565

Publish reproducible CNN baseline cnn-001

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* 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
 
 
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
36
+ models/cnn_001_participant_disjoint.keras filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ language:
3
+ - en
4
+ library_name: tensorflow
5
+ pipeline_tag: image-classification
6
+ tags:
7
+ - asl
8
+ - sign-language
9
+ - cnn
10
+ - reproducible-research
11
+ ---
12
+
13
+ # ASL-HG CNN baseline (`cnn-001`)
14
+
15
+ A TensorFlow/Keras CNN baseline for 36 static ASL classes (digits `0–9`, letters `A–Z`). This repository contains the trained checkpoint **and the full experiment record** needed to reproduce or audit the result.
16
+
17
+ ## Result
18
+
19
+ | Metric | Value |
20
+ |---|---:|
21
+ | Test accuracy | 83.2544% |
22
+ | Macro F1 | 80.0983% |
23
+ | Macro precision / recall | 79.5670% / 83.3056% |
24
+ | Best validation accuracy | 72.0964% |
25
+ | Epochs run | 15 |
26
+ | Recall `O` / `0` | 100.0000% / 0.0000% |
27
+
28
+ This is a deliberately simple CNN-from-scratch baseline, not the final proposed model. In particular, digit `0` has zero recall in this run, so later improvements should report this class separately.
29
+
30
+ ## Evaluation protocol
31
+
32
+ - Dataset: [`hnam25/asl-hand-gesture-images`](https://huggingface.co/datasets/hnam25/asl-hand-gesture-images), revision `8f36ac00ece6dfce94410a980a839d93a912d366`.
33
+ - Input: the publisher's `ASL_Processed_Images.zip`, SHA-256 `a8e7a38c4085fd9dc18aa4fa8646ad7d6917e9ff7374a3ffd5f9f3b37a5c045b`.
34
+ - Audit source: `metadata/colab-audit-2026-08-10`; the raw archive fingerprint and audit statistics are preserved in `metadata/experiment_config.json`.
35
+ - Exact duplicate policy: retain one deterministic canonical image per raw-image SHA-256. The run removes 559 duplicate crops from 36000 usable images.
36
+ - Split: **participant-disjoint**. Train: P1, P10, P3, P4, P5, P6, P7, P8; validation: P2; test: P9. No participant or exact image hash appears in multiple partitions.
37
+ - Seed: 42. Split files and the deduplication manifest are included under `metadata/`.
38
+
39
+ This participant-disjoint protocol is stricter than the publisher's supplied image-level train/test archive split and should not be numerically compared with a random image-level split.
40
+
41
+ ## Model
42
+
43
+ `Rescaling(1/255) → 3 × [Conv-BN-ReLU-Conv-BN-ReLU-MaxPool] (32/64/128 filters) → GAP → Dense(256) → Dropout(0.3) → Dense(36, softmax)`.
44
+
45
+ Training used Adam (`1e-3`), image size 128, batch size 64, at most 30 epochs, checkpointing on validation accuracy, early stopping on validation loss, and learning-rate reduction on plateau. Best validation accuracy occurred at epoch 10.
46
+
47
+ ## Files
48
+
49
+ - `models/cnn_001_participant_disjoint.keras`: best checkpoint.
50
+ - `metrics/`: summary, per-class report, confusion matrix.
51
+ - `figures/confusion_matrix.png`: visual confusion matrix.
52
+ - `logs/training_history.csv`: full optimization history.
53
+ - `metadata/experiment_config.json`: pinned data/config provenance.
54
+ - `metadata/split_manifest.json`, `train.csv`, `validation.csv`, `test.csv`, `deduplication_manifest.csv`: exact protocol and partitions.
55
+ - `reproducibility/08_cnn_baseline_reproducible.ipynb`: Colab notebook used for this run.
56
+ - `reproducibility/requirements.txt`: project requirements snapshot.
57
+
58
+ ## Reproduce
59
+
60
+ Open the included notebook in Google Colab, run it on a T4 GPU, and use the pinned dataset revision already embedded in the notebook. It downloads public data and audit metadata, verifies the processed archive and audit mapping, recreates the exact participant split, trains, and writes the same artifact layout.
figures/confusion_matrix.png ADDED
logs/training_history.csv ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ accuracy,loss,val_accuracy,val_loss,learning_rate
2
+ 0.4591221511363983,1.765419363975525,0.08603384345769882,6.974681854248047,0.0010000000474974513
3
+ 0.8571126461029053,0.44405597448349,0.31000861525535583,2.6612038612365723,0.0010000000474974513
4
+ 0.9495857357978821,0.17806586623191833,0.423286497592926,3.7735824584960938,0.0010000000474974513
5
+ 0.9778600335121155,0.08823823183774948,0.49784916639328003,1.708282709121704,0.0010000000474974513
6
+ 0.98769611120224,0.05259506031870842,0.42730140686035156,2.4472529888153076,0.0010000000474974513
7
+ 0.9845231771469116,0.05536973848938942,0.5976483821868896,1.0407171249389648,0.0010000000474974513
8
+ 0.9936894178390503,0.02670445665717125,0.5190708637237549,2.467351198196411,0.0010000000474974513
9
+ 0.9900934100151062,0.035457175225019455,0.22053341567516327,8.213214874267578,0.0010000000474974513
10
+ 0.9988013505935669,0.008368562906980515,0.7097792029380798,1.056762933731079,0.00020000000949949026
11
+ 0.9996827244758606,0.0033378207590430975,0.7209635972976685,1.0305737257003784,0.00020000000949949026
12
+ 0.9997884631156921,0.0028094735462218523,0.6724978685379028,1.2147690057754517,0.00020000000949949026
13
+ 0.9998589754104614,0.0024102323222905397,0.6570117473602295,1.3460917472839355,0.00020000000949949026
14
+ 1.0,0.001786125940270722,0.696587324142456,1.0568524599075317,4.0000002627493814e-05
15
+ 0.9998237490653992,0.0018538410076871514,0.7069113850593567,1.065900206565857,4.0000002627493814e-05
16
+ 0.9999294877052307,0.0016534593887627125,0.7051907181739807,1.0694741010665894,8.000000889296643e-06
metadata/deduplication_manifest.csv ADDED
The diff for this file is too large to render. See raw diff
 
metadata/environment.txt ADDED
@@ -0,0 +1,703 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ absl-py==1.4.0
2
+ accelerate==1.14.0
3
+ access==1.1.10.post3
4
+ affine==2.4.0
5
+ aiofiles==25.1.0
6
+ aiohappyeyeballs==2.7.1
7
+ aiohttp==3.14.1
8
+ aiosignal==1.4.0
9
+ aiosqlite==0.22.1
10
+ alabaster==1.0.0
11
+ albucore==0.0.24
12
+ albumentations==2.0.8
13
+ ale-py==0.12.0
14
+ altair==5.5.0
15
+ annotated-doc==0.0.4
16
+ annotated-types==0.7.0
17
+ antlr4-python3-runtime==4.9.3
18
+ anyio==4.14.2
19
+ anywidget==0.9.21
20
+ apsw==3.53.3.1
21
+ apswutils==0.1.2
22
+ argon2-cffi==25.1.0
23
+ argon2-cffi-bindings==25.1.0
24
+ array_record==0.8.3
25
+ arrow==1.4.0
26
+ arviz==0.22.0
27
+ astropy==7.2.2
28
+ astropy-iers-data==0.2026.7.13.0.54.2
29
+ astunparse==1.6.3
30
+ atpublic==5.1
31
+ attrs==26.1.0
32
+ audioread==3.1.0
33
+ Authlib==1.7.2
34
+ autograd==1.9.1
35
+ babel==2.18.0
36
+ backcall==0.2.0
37
+ beartype==0.22.9
38
+ beautifulsoup4==4.13.5
39
+ betterproto==2.0.0b6
40
+ bigframes==2.42.0
41
+ bigquery-magics==0.14.0
42
+ bleach==6.4.0
43
+ blinker==1.9.0
44
+ blis==1.3.3
45
+ blobfile==3.2.0
46
+ blosc2==4.8.0
47
+ bokeh==3.8.2
48
+ Bottleneck==1.4.2
49
+ bqplot==0.12.47
50
+ branca==0.8.2
51
+ brotli==1.2.0
52
+ CacheControl==0.14.4
53
+ cachetools==6.2.6
54
+ catalogue==2.0.10
55
+ certifi==2026.6.17
56
+ cffi==2.1.0
57
+ chardet==5.2.0
58
+ charset-normalizer==3.4.9
59
+ clarabel==0.11.1
60
+ click==8.4.2
61
+ cligj==0.7.2
62
+ cloudpathlib==0.24.0
63
+ cloudpickle==3.1.2
64
+ cmake==3.31.10
65
+ cmdstanpy==1.3.0
66
+ colorcet==3.2.1
67
+ colorlover==0.3.0
68
+ community==1.0.0b1
69
+ confection==1.3.3
70
+ cons==0.4.7
71
+ contourpy==1.3.3
72
+ cramjam==2.11.0
73
+ cryptography==49.0.0
74
+ cucim-cu12 @ https://pypi.nvidia.com/cucim-cu12/cucim_cu12-26.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
75
+ cuda-bindings==12.9.7
76
+ cuda-core==0.3.2
77
+ cuda-pathfinder==1.5.6
78
+ cuda-python==12.9.7
79
+ cuda-toolkit==12.8.1
80
+ cudf-cu12==26.2.1
81
+ cudf-polars-cu12==26.2.1
82
+ cufflinks==0.17.3
83
+ cuml-cu12==26.2.0
84
+ cupy-cuda12x==14.0.1
85
+ curl_cffi==0.15.0
86
+ cuvs-cu12 @ https://pypi.nvidia.com/cuvs-cu12/cuvs_cu12-26.2.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
87
+ cvxopt==1.3.2
88
+ cvxpy==1.6.7
89
+ cycler==0.12.1
90
+ cyipopt==1.5.0
91
+ cymem==2.0.13
92
+ Cython==3.0.12
93
+ dask==2026.1.1
94
+ dask-cuda==26.2.0
95
+ dask-cudf-cu12==26.2.1
96
+ dataproc-spark-connect==1.1.0
97
+ datasets==4.0.0
98
+ db-dtypes==1.7.1
99
+ dbus-python==1.2.18
100
+ debugpy==1.8.15
101
+ decorator==4.4.2
102
+ defusedxml==0.7.1
103
+ deprecation==2.1.0
104
+ diffusers==0.39.0
105
+ dill==0.3.8
106
+ distributed==2026.1.1
107
+ distributed-ucxx-cu12==0.48.0
108
+ distro==1.9.0
109
+ dlib==19.24.6
110
+ dm-tree==0.1.10
111
+ docstring_parser==0.18.0
112
+ docutils==0.21.2
113
+ dopamine_rl==4.1.2
114
+ duckdb==1.3.2
115
+ earthengine-api==1.7.35
116
+ easydict==1.13
117
+ editdistance==0.8.1
118
+ eerepr==0.1.2
119
+ einops==0.8.2
120
+ en_core_web_sm @ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl#sha256=1932429db727d4bff3deed6b34cfc05df17794f4a52eeb26cf8928f7c1a0fb85
121
+ entrypoints==0.4
122
+ esda==2.9.0
123
+ et_xmlfile==2.0.0
124
+ etils==1.14.0
125
+ etuples==0.3.10
126
+ Farama-Notifications==0.0.6
127
+ fastai==2.8.7
128
+ fastapi==0.139.0
129
+ fastcore==2.0.5
130
+ fastdownload==0.0.7
131
+ fastjsonschema==2.21.2
132
+ fastlite==0.2.4
133
+ fastprogress==1.1.6
134
+ fasttransform==0.0.2
135
+ filelock==3.29.7
136
+ firebase-admin==6.9.0
137
+ Flask==3.1.3
138
+ flatbuffers==25.12.19
139
+ flax==0.11.2
140
+ folium==0.20.0
141
+ fonttools==4.63.0
142
+ fqdn==1.5.1
143
+ frozendict==2.4.7
144
+ frozenlist==1.8.0
145
+ fsspec==2025.3.0
146
+ future==1.0.0
147
+ gast==0.7.0
148
+ gcsfs==2025.3.0
149
+ GDAL==3.8.4
150
+ gdown==5.2.2
151
+ geemap==0.38.3
152
+ geocoder==1.38.1
153
+ geographiclib==2.1
154
+ geopandas==1.1.4
155
+ geopy==2.4.1
156
+ giddy==2.3.6
157
+ gin-config==0.5.0
158
+ gitdb==4.0.12
159
+ GitPython==3.1.51
160
+ glob2==0.7
161
+ google==3.0.0
162
+ google-adk==2.4.0
163
+ google-ai-generativelanguage==0.6.15
164
+ google-api-core==2.30.3
165
+ google-api-python-client==2.198.0
166
+ google-auth==2.49.0
167
+ google-auth-httplib2==0.4.0
168
+ google-auth-oauthlib==1.4.0
169
+ google-cloud-bigquery==3.42.2
170
+ google-cloud-bigquery-connection==1.22.0
171
+ google-cloud-bigquery-storage==2.39.0
172
+ google-cloud-core==2.6.0
173
+ google-cloud-dataproc==5.28.0
174
+ google-cloud-datastore==2.25.0
175
+ google-cloud-firestore==2.27.0
176
+ google-cloud-functions==1.24.0
177
+ google-cloud-language==2.21.0
178
+ google-cloud-monitoring==2.31.0
179
+ google-cloud-resource-manager==1.18.0
180
+ google-cloud-spanner==3.68.0
181
+ google-cloud-storage==3.13.0
182
+ google-cloud-translate==3.27.0
183
+ google-colab @ file:///colabtools/dist/google_colab-1.0.0.tar.gz
184
+ google-crc32c==1.8.0
185
+ google-genai==2.11.0
186
+ google-generativeai==0.8.6
187
+ google-pasta==0.2.0
188
+ google-resumable-media==2.10.0
189
+ googleapis-common-protos==1.75.0
190
+ googledrivedownloader==1.1.0
191
+ gradio==6.20.0
192
+ gradio_client==2.5.0
193
+ grain==0.2.18
194
+ graphviz==0.21
195
+ greenlet==3.5.3
196
+ groovy==0.1.2
197
+ grpc-google-iam-v1==0.14.4
198
+ grpc-interceptor==0.15.4
199
+ grpcio==1.82.1
200
+ grpcio-status==1.71.2
201
+ grpclib==0.4.9
202
+ gspread==6.2.1
203
+ gspread-dataframe==4.0.0
204
+ gym==0.25.2
205
+ gym-notices==0.1.0
206
+ gymnasium==1.3.0
207
+ h11==0.16.0
208
+ h2==4.3.0
209
+ h5netcdf==1.8.1
210
+ h5py==3.16.0
211
+ hdbscan==0.8.44
212
+ hf-gradio==0.4.1
213
+ hf-xet==1.5.1
214
+ highspy==1.15.1
215
+ holidays==0.100
216
+ holoviews==1.23.1
217
+ hpack==4.2.0
218
+ html5lib==1.1
219
+ httpcore==1.0.9
220
+ httpcore2==2.5.0
221
+ httpimport==1.4.1
222
+ httplib2==0.32.0
223
+ httptools==0.8.0
224
+ httpx==0.28.1
225
+ httpx2==2.5.0
226
+ huggingface_hub==1.23.0
227
+ humanize==4.16.0
228
+ hyperframe==6.1.0
229
+ hyperopt==0.2.7
230
+ ibis-framework==9.5.0
231
+ idna==3.18
232
+ ImageIO==2.37.3
233
+ imageio-ffmpeg==0.6.0
234
+ imagesize==2.0.0
235
+ imbalanced-learn==0.14.2
236
+ immutabledict==4.3.1
237
+ importlib_metadata==9.0.0
238
+ importlib_resources==7.1.0
239
+ imutils==0.5.4
240
+ inequality==1.1.2
241
+ inflect==7.5.0
242
+ iniconfig==2.3.0
243
+ intel-cmplr-lib-ur==2025.3.3
244
+ intel-openmp==2025.3.3
245
+ ipyevents==2.0.4
246
+ ipyfilechooser==0.6.0
247
+ ipykernel==6.17.1
248
+ ipyleaflet==0.20.0
249
+ ipyparallel==8.8.0
250
+ ipython==7.34.0
251
+ ipython-genutils==0.2.0
252
+ ipython-sql==0.5.0
253
+ ipywidgets==7.7.1
254
+ isoduration==20.11.0
255
+ itsdangerous==2.2.0
256
+ jaraco.classes==3.4.0
257
+ jaraco.context==6.1.2
258
+ jaraco.functools==4.5.0
259
+ jax==0.7.2
260
+ jax-cuda12-pjrt==0.7.2
261
+ jax-cuda12-plugin==0.7.2
262
+ jaxlib==0.7.2
263
+ jeepney==0.9.0
264
+ jieba==0.42.1
265
+ Jinja2==3.1.6
266
+ jiter==0.16.0
267
+ joblib==1.5.3
268
+ joserfc==1.7.3
269
+ jsonpatch==1.33
270
+ jsonpickle==4.1.2
271
+ jsonpointer==3.1.1
272
+ jsonschema==4.26.0
273
+ jsonschema-specifications==2025.9.1
274
+ jupyter-console==6.6.3
275
+ jupyter-events==0.12.1
276
+ jupyter-leaflet==0.20.0
277
+ jupyter_client==7.4.9
278
+ jupyter_core==5.9.1
279
+ jupyter_kernel_gateway @ git+https://github.com/googlecolab/kernel_gateway@b134e9945df25c2dcb98ade9129399be10788671
280
+ jupyter_server==2.20.0
281
+ jupyter_server_terminals==0.5.4
282
+ jupyterlab_pygments==0.3.0
283
+ jupyterlab_widgets==3.0.16
284
+ jupytext==1.19.4
285
+ kaggle==2.0.2
286
+ kagglehub==1.0.2
287
+ kagglesdk==0.1.23
288
+ keras==3.13.2
289
+ keras-hub==0.26.0
290
+ keras-nlp==0.26.0
291
+ keyring==25.7.0
292
+ keyrings.google-artifactregistry-auth==1.1.2
293
+ kiwisolver==1.5.0
294
+ langchain==1.3.13
295
+ langchain-core==1.4.9
296
+ langchain-protocol==0.0.18
297
+ langgraph==1.2.9
298
+ langgraph-checkpoint==4.1.1
299
+ langgraph-prebuilt==1.1.0
300
+ langgraph-sdk==0.4.2
301
+ langsmith==0.10.2
302
+ lark==1.3.1
303
+ launchpadlib==1.10.16
304
+ lazr.restfulclient==0.14.4
305
+ lazr.uri==1.0.6
306
+ lazy-loader==0.5
307
+ libclang==18.1.1
308
+ libcudf-cu12==26.2.1
309
+ libcugraph-cu12==26.2.0
310
+ libcuml-cu12==26.2.0
311
+ libcuvs-cu12==26.2.0
312
+ libkvikio-cu12==26.2.0
313
+ libpysal==4.14.1
314
+ libraft-cu12==26.2.0
315
+ librmm-cu12==26.2.0
316
+ librosa==0.11.0
317
+ libucx-cu12==1.19.0
318
+ libucxx-cu12==0.48.0
319
+ lightgbm==4.6.0
320
+ linkify-it-py==2.1.0
321
+ llvmlite==0.43.0
322
+ locket==1.0.0
323
+ logical-unification==0.4.7
324
+ lxml==6.1.1
325
+ Mako==1.1.3
326
+ mapclassify==2.10.0
327
+ Markdown==3.10.2
328
+ markdown-it-py==4.2.0
329
+ MarkupSafe==3.0.3
330
+ matplotlib==3.10.0
331
+ matplotlib-inline==0.2.2
332
+ matplotlib-venn==1.1.2
333
+ mdit-py-plugins==0.6.1
334
+ mdurl==0.1.2
335
+ mgwr==2.2.1
336
+ miniKanren==1.0.5
337
+ missingno==0.5.2
338
+ mistune==3.3.3
339
+ mizani==0.13.5
340
+ mkl==2025.3.1
341
+ ml_dtypes==0.5.4
342
+ mlxtend==0.23.4
343
+ mmh3==5.2.1
344
+ momepy==0.11.0
345
+ more-itertools==10.8.0
346
+ moviepy==1.0.3
347
+ mpmath==1.3.0
348
+ msgpack==1.2.1
349
+ multidict==6.7.1
350
+ multipledispatch==1.0.0
351
+ multiprocess==0.70.16
352
+ multitasking==0.0.13
353
+ murmurhash==1.0.15
354
+ music21==9.9.2
355
+ namex==0.1.0
356
+ narwhals==2.24.0
357
+ natsort==8.4.0
358
+ nbclassic==1.3.3
359
+ nbclient==0.10.4
360
+ nbconvert==7.17.1
361
+ nbformat==5.10.4
362
+ ndindex==1.10.1
363
+ nest-asyncio==1.6.0
364
+ networkx==3.6.1
365
+ nh3==0.3.6
366
+ nibabel==5.4.2
367
+ nltk==3.9.1
368
+ notebook==6.5.7
369
+ notebook_shim==0.2.4
370
+ numba==0.60.0
371
+ numba-cuda==0.22.2
372
+ numexpr==2.14.1
373
+ numpy==2.0.2
374
+ nvidia-cublas-cu12==12.8.4.1
375
+ nvidia-cuda-cccl-cu12==12.9.27
376
+ nvidia-cuda-cupti-cu12==12.8.90
377
+ nvidia-cuda-nvcc-cu12==12.8.93
378
+ nvidia-cuda-nvrtc-cu12==12.8.93
379
+ nvidia-cuda-runtime-cu12==12.8.90
380
+ nvidia-cudnn-cu12==9.19.0.56
381
+ nvidia-cufft-cu12==11.3.3.83
382
+ nvidia-cufile-cu12==1.13.1.3
383
+ nvidia-curand-cu12==10.3.9.90
384
+ nvidia-cusolver-cu12==11.7.3.90
385
+ nvidia-cusparse-cu12==12.5.8.93
386
+ nvidia-cusparselt-cu12==0.7.1
387
+ nvidia-libnvcomp-cu12==5.1.0.21
388
+ nvidia-ml-py==13.610.43
389
+ nvidia-nccl-cu12==2.28.9
390
+ nvidia-nvimgcodec-cu12==0.7.0.11
391
+ nvidia-nvjitlink-cu12==12.8.93
392
+ nvidia-nvshmem-cu12==3.4.5
393
+ nvidia-nvtx-cu12==12.8.90
394
+ nvtx==0.2.15
395
+ nx-cugraph-cu12 @ https://pypi.nvidia.com/nx-cugraph-cu12/nx_cugraph_cu12-26.2.0-py3-none-any.whl
396
+ oauth2client==4.1.3
397
+ oauthlib==3.3.1
398
+ omegaconf==2.3.1
399
+ onemkl-license==2025.3.1
400
+ openai==2.45.0
401
+ opencv-contrib-python==4.13.0.92
402
+ opencv-python==5.0.0.93
403
+ opencv-python-headless==5.0.0.93
404
+ openpyxl==3.1.5
405
+ opentelemetry-api==1.42.1
406
+ opentelemetry-resourcedetector-gcp==1.12.0a0
407
+ opentelemetry-sdk==1.42.1
408
+ opentelemetry-semantic-conventions==0.63b1
409
+ opt_einsum==3.4.0
410
+ optax==0.2.8
411
+ optree==0.19.1
412
+ orbax-checkpoint==0.12.1
413
+ orjson==3.11.9
414
+ ormsgpack==1.12.2
415
+ osqp==1.1.3
416
+ packaging==26.2
417
+ pandas==2.2.2
418
+ pandas-datareader==0.11.1
419
+ pandas-gbq==0.30.0
420
+ pandas-stubs==2.2.2.240909
421
+ pandocfilters==1.5.1
422
+ panel==1.9.3
423
+ panel-material-ui==0.14.0
424
+ param==2.4.1
425
+ parso==0.8.7
426
+ parsy==2.2
427
+ partd==1.4.2
428
+ patsy==1.0.2
429
+ peewee==4.2.2
430
+ peft==0.19.1
431
+ pexpect==4.9.0
432
+ pickleshare==0.7.5
433
+ pillow==11.3.0
434
+ platformdirs==4.10.0
435
+ plotly==5.24.1
436
+ plotnine==0.14.5
437
+ pluggy==1.6.0
438
+ plum-dispatch==2.9.0
439
+ pointpats==2.5.5
440
+ polars==1.35.2
441
+ polars-runtime-32==1.35.2
442
+ pooch==1.9.0
443
+ portpicker==1.5.2
444
+ preshed==3.0.13
445
+ prettytable==3.18.0
446
+ proglog==0.1.12
447
+ progressbar2==4.5.0
448
+ prometheus_client==0.25.0
449
+ promise==2.3
450
+ prompt_toolkit==3.0.52
451
+ propcache==0.5.2
452
+ prophet==1.3.0
453
+ proto-plus==1.28.1
454
+ protobuf==5.29.6
455
+ psutil==5.9.5
456
+ psycopg2==2.9.12
457
+ psygnal==0.15.1
458
+ ptyprocess==0.7.0
459
+ PuLP==3.3.2
460
+ py-cpuinfo==9.0.0
461
+ py4j==0.10.9.9
462
+ pyarrow==18.1.0
463
+ pyasn1==0.6.4
464
+ pyasn1_modules==0.4.2
465
+ pycairo==1.29.0
466
+ pycocotools==2.0.11
467
+ pycparser==3.0
468
+ pycryptodomex==3.23.0
469
+ pydantic==2.13.4
470
+ pydantic_core==2.46.4
471
+ pydata-google-auth==1.9.1
472
+ pydot==4.0.1
473
+ pydotplus==2.0.2
474
+ PyDrive2==1.21.1
475
+ pydub==0.25.1
476
+ pyerfa==2.0.1.5
477
+ pygame==2.6.1
478
+ pygit2==1.19.3
479
+ Pygments==2.20.0
480
+ PyGObject==3.48.2
481
+ pyiceberg==0.11.1
482
+ PyJWT==2.13.0
483
+ pylibcudf-cu12==26.2.1
484
+ pylibcugraph-cu12==26.2.0
485
+ pylibraft-cu12==26.2.0
486
+ pymc==5.28.5
487
+ pynndescent==0.6.0
488
+ pyogrio==0.13.0
489
+ pyomo==6.10.1
490
+ PyOpenGL==3.1.10
491
+ pyOpenSSL==26.3.0
492
+ pyparsing==3.3.2
493
+ pyperclip==1.11.0
494
+ pyproj==3.7.2
495
+ pyroaring==1.1.0
496
+ pysal==25.7
497
+ pyshp==3.1.4
498
+ PySocks==1.7.1
499
+ pyspark==4.0.3
500
+ pytensor==2.38.3
501
+ pytest==8.4.2
502
+ python-apt==0.0.0
503
+ python-box==7.4.1
504
+ python-dateutil==2.9.0.post0
505
+ python-dotenv==1.2.2
506
+ python-fasthtml==0.14.6
507
+ python-json-logger==4.1.0
508
+ python-louvain==0.16
509
+ python-multipart==0.0.32
510
+ python-slugify==8.0.4
511
+ python-snappy==0.7.3
512
+ python-utils==4.0.0
513
+ pytz==2025.2
514
+ pyviz_comms==3.0.6
515
+ PyWavelets==1.9.0
516
+ PyYAML==6.0.3
517
+ pyzmq==26.2.1
518
+ quantecon==0.11.3
519
+ raft-dask-cu12==26.2.0
520
+ rapids-dask-dependency==26.2.0
521
+ rapids-logger==0.2.3
522
+ rasterio==1.5.0
523
+ rasterstats==0.21.0
524
+ ratelim==0.1.6
525
+ referencing==0.37.0
526
+ regex==2025.11.3
527
+ requests==2.32.4
528
+ requests-oauthlib==2.0.0
529
+ requests-toolbelt==1.0.0
530
+ requirements-parser==0.9.0
531
+ rfc3339-validator==0.1.4
532
+ rfc3986-validator==0.1.1
533
+ rfc3987-syntax==1.1.0
534
+ rich==13.9.4
535
+ rmm-cu12==26.2.0
536
+ roman-numerals==4.1.0
537
+ roman-numerals-py==4.1.0
538
+ rpds-py==2026.6.3
539
+ rpy2==3.5.17
540
+ rsa==4.9.1
541
+ rtree==1.4.1
542
+ safehttpx==0.1.7
543
+ safetensors==0.8.0
544
+ scikit-image==0.25.2
545
+ scikit-learn==1.6.1
546
+ scipy==1.16.3
547
+ scooby==0.11.2
548
+ scs==3.2.11
549
+ seaborn==0.13.2
550
+ SecretStorage==3.5.0
551
+ segregation==2.5.4
552
+ semantic-version==2.10.0
553
+ Send2Trash==2.1.0
554
+ sentence-transformers==5.6.0
555
+ sentencepiece==0.2.2
556
+ sentry-sdk==2.65.0
557
+ setuptools==75.2.0
558
+ shap==0.52.0
559
+ shapely==2.1.2
560
+ shellingham==1.5.4
561
+ simple-parsing==0.1.8
562
+ simplejson==4.1.1
563
+ simsimd==6.5.16
564
+ six==1.17.0
565
+ sklearn-compat==0.1.6
566
+ sklearn-pandas==2.2.0
567
+ slicer==0.0.8
568
+ smart_open==8.0.0
569
+ smmap==5.0.3
570
+ sniffio==1.3.1
571
+ snowballstemmer==3.1.1
572
+ sortedcontainers==2.4.0
573
+ soundfile==0.14.0
574
+ soupsieve==2.8.4
575
+ soxr==1.1.0
576
+ spacy==3.8.14
577
+ spacy-legacy==3.0.12
578
+ spacy-loggers==1.0.5
579
+ spaghetti==1.7.6
580
+ spanner-graph-notebook==1.1.10
581
+ spglm==1.1.0
582
+ Sphinx==8.2.3
583
+ sphinxcontrib-applehelp==2.0.0
584
+ sphinxcontrib-devhelp==2.0.0
585
+ sphinxcontrib-htmlhelp==2.1.0
586
+ sphinxcontrib-jsmath==1.0.1
587
+ sphinxcontrib-qthelp==2.0.0
588
+ sphinxcontrib-serializinghtml==2.0.0
589
+ spint==1.1.0
590
+ splot==1.1.7
591
+ spopt==0.7.0
592
+ spreg==1.9.0
593
+ SQLAlchemy==2.0.51
594
+ sqlglot==25.20.2
595
+ sqlparse==0.5.5
596
+ srsly==2.5.3
597
+ stanio==0.5.1
598
+ starlette==1.3.1
599
+ statsmodels==0.14.6
600
+ strictyaml==1.7.3
601
+ stringzilla==4.6.2
602
+ stumpy==1.13.0
603
+ sympy==1.14.0
604
+ tables==3.10.2
605
+ tabulate==0.9.0
606
+ tbb==2022.3.1
607
+ tblib==3.2.2
608
+ tcmlib==1.5.0
609
+ tenacity==9.1.4
610
+ tensorboard==2.20.0
611
+ tensorboard-data-server==0.7.2
612
+ tensorflow==2.20.0
613
+ tensorflow-datasets==4.9.10
614
+ tensorflow-hub==0.16.1
615
+ tensorflow-metadata==1.21.0
616
+ tensorflow-probability==0.25.0
617
+ tensorflow-text==2.20.1
618
+ tensorstore==0.1.84
619
+ termcolor==3.3.0
620
+ terminado==0.18.1
621
+ text-unidecode==1.3
622
+ textblob==0.19.0
623
+ tf-slim==1.1.0
624
+ tf_keras==2.20.0
625
+ thinc==8.3.13
626
+ threadpoolctl==3.6.0
627
+ tifffile==2026.4.11
628
+ tiktoken==0.13.0
629
+ timm==1.0.28
630
+ tinycss2==1.5.1
631
+ tobler==0.14.0
632
+ tokenizers==0.22.2
633
+ toml==0.10.2
634
+ tomlkit==0.14.0
635
+ toolz==0.12.1
636
+ torch==2.11.0+cu128
637
+ torchao==0.10.0
638
+ torchaudio==2.11.0+cu128
639
+ torchcodec==0.11.0+cu128
640
+ torchdata==0.11.0
641
+ torchsummary==1.5.1
642
+ torchtune==0.6.1
643
+ torchvision==0.26.0+cu128
644
+ tornado==6.5.7
645
+ tqdm==4.67.3
646
+ traitlets==5.7.1
647
+ traittypes==0.2.3
648
+ transformers==5.13.1
649
+ treelite==4.7.0
650
+ treescope==0.1.10
651
+ triton==3.6.0
652
+ truststore==0.10.4
653
+ tsfresh==0.21.2
654
+ tweepy==4.17.0
655
+ typeguard==4.5.2
656
+ typer==0.26.8
657
+ types-pytz==2026.2.0.20260518
658
+ types-setuptools==82.0.0.20260518
659
+ typing-inspection==0.4.2
660
+ typing_extensions==4.16.0
661
+ tzdata==2026.3
662
+ tzlocal==5.4.4
663
+ uc-micro-py==2.0.0
664
+ ucxx-cu12==0.48.0
665
+ umap-learn==0.5.12
666
+ umf==1.0.3
667
+ uri-template==1.3.0
668
+ uritemplate==4.2.0
669
+ urllib3==2.5.0
670
+ uuid_utils==0.17.0
671
+ uvicorn==0.51.0
672
+ uvloop==0.22.1
673
+ vega-datasets==0.9.0
674
+ wadllib==1.3.6
675
+ wandb==0.28.0
676
+ wasabi==1.1.3
677
+ watchdog==6.0.0
678
+ watchfiles==1.2.0
679
+ wcwidth==0.8.2
680
+ weasel==1.0.0
681
+ webcolors==25.10.0
682
+ webencodings==0.5.1
683
+ websocket-client==1.9.0
684
+ websockets==15.0.1
685
+ Werkzeug==3.1.8
686
+ wheel==0.47.0
687
+ widgetsnbextension==3.6.10
688
+ wordcloud==1.9.6
689
+ wrapt==2.2.2
690
+ xarray==2025.12.0
691
+ xarray-einstats==0.10.0
692
+ xgboost==3.3.0
693
+ xlrd==2.0.2
694
+ xxhash==3.8.1
695
+ xyzservices==2026.3.0
696
+ yarl==1.24.2
697
+ ydf==0.15.0
698
+ ydf_tf==2.20.0
699
+ yellowbrick==1.5
700
+ yfinance==0.2.66
701
+ zict==3.0.0
702
+ zipp==4.1.0
703
+ zstandard==0.25.0
metadata/experiment_config.json ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "experiment_id": "cnn-001-participant-disjoint",
3
+ "created_at_utc": "2026-08-10T04:55:21.052252+00:00",
4
+ "dataset_repo": "hnam25/asl-hand-gesture-images",
5
+ "dataset_revision": "8f36ac00ece6dfce94410a980a839d93a912d366",
6
+ "processed_archive": "ASL_HG_36000/ASL_Processed_Images.zip",
7
+ "processed_archive_sha256": "a8e7a38c4085fd9dc18aa4fa8646ad7d6917e9ff7374a3ffd5f9f3b37a5c045b",
8
+ "audit_manifest": {
9
+ "schema_version": 1,
10
+ "kind": "audit-only",
11
+ "created_at_utc": "2026-08-10T03:59:36.546025+00:00",
12
+ "dataset_repo": "hnam25/asl-hand-gesture-images",
13
+ "dataset_revision": "bad9dd9297697ec2da901562b203a5a14ebb93d1",
14
+ "raw_archive": "ASL_HG_36000/ASL_Raw_Images.zip",
15
+ "raw_archive_sha256": "594cfa0158044085ed61351315c187a6f3a3f9087795b4f4cdba68ecd04f12b1",
16
+ "classes": [
17
+ "0",
18
+ "1",
19
+ "2",
20
+ "3",
21
+ "4",
22
+ "5",
23
+ "6",
24
+ "7",
25
+ "8",
26
+ "9",
27
+ "A",
28
+ "B",
29
+ "C",
30
+ "D",
31
+ "E",
32
+ "F",
33
+ "G",
34
+ "H",
35
+ "I",
36
+ "J",
37
+ "K",
38
+ "L",
39
+ "M",
40
+ "N",
41
+ "O",
42
+ "P",
43
+ "Q",
44
+ "R",
45
+ "S",
46
+ "T",
47
+ "U",
48
+ "V",
49
+ "W",
50
+ "X",
51
+ "Y",
52
+ "Z"
53
+ ],
54
+ "image_extensions": [
55
+ ".bmp",
56
+ ".jpeg",
57
+ ".jpg",
58
+ ".png",
59
+ ".webp"
60
+ ],
61
+ "totals": {
62
+ "records": 36000,
63
+ "readable": 36000,
64
+ "unreadable": 0,
65
+ "duplicate_rows": 994,
66
+ "duplicate_hash_groups": 435
67
+ }
68
+ },
69
+ "split_manifest": {
70
+ "experiment_id": "cnn-001-participant-disjoint",
71
+ "policy": "participant-disjoint 8/1/1; one canonical representative per exact raw-image SHA-256",
72
+ "seed": 42,
73
+ "participants": {
74
+ "train": [
75
+ "P1",
76
+ "P10",
77
+ "P3",
78
+ "P4",
79
+ "P5",
80
+ "P6",
81
+ "P7",
82
+ "P8"
83
+ ],
84
+ "validation": "P2",
85
+ "test": "P9"
86
+ },
87
+ "counts": {
88
+ "before_deduplication": 36000,
89
+ "after_deduplication": 35441,
90
+ "removed_exact_duplicates": 559,
91
+ "train": 28365,
92
+ "validation": 3487,
93
+ "test": 3589
94
+ }
95
+ },
96
+ "model": {
97
+ "architecture": "CNN from scratch: 3 x [Conv-BN-ReLU-Conv-BN-ReLU-MaxPool]",
98
+ "image_size": 128,
99
+ "batch_size": 64,
100
+ "epochs_max": 30,
101
+ "learning_rate": 0.001,
102
+ "dropout": 0.3
103
+ },
104
+ "tensorflow": "2.20.0"
105
+ }
metadata/split_manifest.json ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "experiment_id": "cnn-001-participant-disjoint",
3
+ "policy": "participant-disjoint 8/1/1; one canonical representative per exact raw-image SHA-256",
4
+ "seed": 42,
5
+ "participants": {
6
+ "train": [
7
+ "P1",
8
+ "P10",
9
+ "P3",
10
+ "P4",
11
+ "P5",
12
+ "P6",
13
+ "P7",
14
+ "P8"
15
+ ],
16
+ "validation": "P2",
17
+ "test": "P9"
18
+ },
19
+ "counts": {
20
+ "before_deduplication": 36000,
21
+ "after_deduplication": 35441,
22
+ "removed_exact_duplicates": 559,
23
+ "train": 28365,
24
+ "validation": 3487,
25
+ "test": 3589
26
+ }
27
+ }
metadata/test.csv ADDED
The diff for this file is too large to render. See raw diff
 
metadata/train.csv ADDED
The diff for this file is too large to render. See raw diff
 
metadata/validation.csv ADDED
The diff for this file is too large to render. See raw diff
 
metrics/classification_report.csv ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ,precision,recall,f1-score,support
2
+ 0,0.0,0.0,0.0,100.0
3
+ 1,1.0,1.0,1.0,100.0
4
+ 2,1.0,1.0,1.0,100.0
5
+ 3,0.9009009009009009,1.0,0.9478672985781991,100.0
6
+ 4,1.0,0.89,0.9417989417989417,100.0
7
+ 5,1.0,1.0,1.0,99.0
8
+ 6,0.0,0.0,0.0,100.0
9
+ 7,0.6216216216216216,0.92,0.7419354838709677,100.0
10
+ 8,0.9259259259259259,1.0,0.9615384615384616,100.0
11
+ 9,1.0,0.44,0.6111111111111112,100.0
12
+ A,1.0,1.0,1.0,94.0
13
+ B,1.0,0.8,0.8888888888888888,100.0
14
+ C,1.0,1.0,1.0,99.0
15
+ D,1.0,1.0,1.0,100.0
16
+ E,0.5050505050505051,1.0,0.6711409395973155,100.0
17
+ F,1.0,1.0,1.0,100.0
18
+ G,0.0,0.0,0.0,100.0
19
+ H,0.3333333333333333,1.0,0.5,100.0
20
+ I,1.0,1.0,1.0,100.0
21
+ J,1.0,1.0,1.0,100.0
22
+ K,0.8305084745762712,1.0,0.9074074074074074,98.0
23
+ L,1.0,1.0,1.0,100.0
24
+ M,0.047619047619047616,0.05,0.04878048780487805,100.0
25
+ N,0.0,0.0,0.0,100.0
26
+ O,1.0,1.0,1.0,100.0
27
+ P,1.0,1.0,1.0,100.0
28
+ Q,1.0,1.0,1.0,100.0
29
+ R,1.0,1.0,1.0,99.0
30
+ S,1.0,0.97,0.9847715736040609,100.0
31
+ T,1.0,1.0,1.0,100.0
32
+ U,1.0,1.0,1.0,100.0
33
+ V,1.0,1.0,1.0,100.0
34
+ W,0.4791666666666667,0.92,0.6301369863013698,100.0
35
+ X,1.0,1.0,1.0,100.0
36
+ Y,1.0,1.0,1.0,100.0
37
+ Z,1.0,1.0,1.0,100.0
38
+ accuracy,0.8325438840902758,0.8325438840902758,0.8325438840902758,0.8325438840902758
39
+ macro avg,0.7956701798803966,0.8330555555555557,0.800982710569489,3589.0
40
+ weighted avg,0.7951383757649135,0.8325438840902758,0.8004243363709516,3589.0
metrics/confusion_matrix.csv ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ,0,1,2,3,4,5,6,7,8,9,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
2
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
3
+ 1,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
4
+ 2,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
5
+ 3,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
6
+ 4,0,0,0,11,89,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
7
+ 5,0,0,0,0,0,99,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
8
+ 6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0,0,0
9
+ 7,0,0,0,0,0,0,0,92,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
10
+ 8,0,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
11
+ 9,0,0,0,0,0,0,0,56,0,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
12
+ A,0,0,0,0,0,0,0,0,0,0,94,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
13
+ B,0,0,0,0,0,0,0,0,0,0,0,80,0,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
14
+ C,0,0,0,0,0,0,0,0,0,0,0,0,99,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
15
+ D,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
16
+ E,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
17
+ F,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
18
+ G,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
19
+ H,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
20
+ I,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
21
+ J,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
22
+ K,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,98,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
23
+ L,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0
24
+ M,0,0,0,0,0,0,0,0,0,0,0,0,0,0,95,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0
25
+ N,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0
26
+ O,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0
27
+ P,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0
28
+ Q,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0
29
+ R,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,99,0,0,0,0,0,0,0,0
30
+ S,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,97,0,0,0,0,0,0,0
31
+ T,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0,0,0
32
+ U,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0,0
33
+ V,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0
34
+ W,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,92,0,0,0
35
+ X,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0,0
36
+ Y,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0
37
+ Z,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100
metrics/summary.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "experiment_id": "cnn-001-participant-disjoint",
3
+ "test_accuracy": 0.8325438840902758,
4
+ "macro_precision": 0.7956701798803966,
5
+ "macro_recall": 0.8330555555555557,
6
+ "macro_f1": 0.800982710569489,
7
+ "O_recall": 1.0,
8
+ "0_recall": 0.0,
9
+ "O_to_0": 0,
10
+ "0_to_O": 0,
11
+ "best_validation_accuracy": 0.7209635972976685,
12
+ "epochs_ran": 15
13
+ }
models/cnn_001_participant_disjoint.keras ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7060712e0ac8574f0cbbf0f89f0e5dbc1571c5d5753c2fde6d7e51bd230e8be9
3
+ size 4083344
models/model_summary.txt ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Model: "cnn_asl"
2
+ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
3
+ ┃ Layer (type) ┃ Output Shape ┃ Param # ┃
4
+ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
5
+ │ image (InputLayer) │ (None, 128, 128, 3) │ 0 │
6
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
7
+ │ rescale (Rescaling) │ (None, 128, 128, 3) │ 0 │
8
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
9
+ │ conv2d_6 (Conv2D) │ (None, 128, 128, 32) │ 864 │
10
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
11
+ │ batch_normalization_6 │ (None, 128, 128, 32) │ 128 │
12
+ │ (BatchNormalization) │ │ │
13
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
14
+ │ re_lu_6 (ReLU) │ (None, 128, 128, 32) │ 0 │
15
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
16
+ │ conv2d_7 (Conv2D) │ (None, 128, 128, 32) │ 9,216 │
17
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
18
+ │ batch_normalization_7 │ (None, 128, 128, 32) │ 128 │
19
+ │ (BatchNormalization) │ │ │
20
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
21
+ │ re_lu_7 (ReLU) │ (None, 128, 128, 32) │ 0 │
22
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
23
+ │ max_pooling2d_3 (MaxPooling2D) │ (None, 64, 64, 32) │ 0 │
24
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
25
+ │ dropout_3 (Dropout) │ (None, 64, 64, 32) │ 0 │
26
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
27
+ │ conv2d_8 (Conv2D) │ (None, 64, 64, 64) │ 18,432 │
28
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
29
+ │ batch_normalization_8 │ (None, 64, 64, 64) │ 256 │
30
+ │ (BatchNormalization) │ │ │
31
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
32
+ │ re_lu_8 (ReLU) │ (None, 64, 64, 64) │ 0 │
33
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
34
+ │ conv2d_9 (Conv2D) │ (None, 64, 64, 64) │ 36,864 │
35
+ ├───────────────────────────��─────┼────────────────────────┼───────────────┤
36
+ │ batch_normalization_9 │ (None, 64, 64, 64) │ 256 │
37
+ │ (BatchNormalization) │ │ │
38
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
39
+ │ re_lu_9 (ReLU) │ (None, 64, 64, 64) │ 0 │
40
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
41
+ │ max_pooling2d_4 (MaxPooling2D) │ (None, 32, 32, 64) │ 0 │
42
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
43
+ │ dropout_4 (Dropout) │ (None, 32, 32, 64) │ 0 │
44
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
45
+ │ conv2d_10 (Conv2D) │ (None, 32, 32, 128) │ 73,728 │
46
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
47
+ │ batch_normalization_10 │ (None, 32, 32, 128) │ 512 │
48
+ │ (BatchNormalization) │ │ │
49
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
50
+ │ re_lu_10 (ReLU) │ (None, 32, 32, 128) │ 0 │
51
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
52
+ │ conv2d_11 (Conv2D) │ (None, 32, 32, 128) │ 147,456 │
53
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
54
+ │ batch_normalization_11 │ (None, 32, 32, 128) │ 512 │
55
+ │ (BatchNormalization) │ │ │
56
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
57
+ │ re_lu_11 (ReLU) │ (None, 32, 32, 128) │ 0 │
58
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
59
+ │ max_pooling2d_5 (MaxPooling2D) │ (None, 16, 16, 128) │ 0 │
60
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
61
+ │ dropout_5 (Dropout) │ (None, 16, 16, 128) │ 0 │
62
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
63
+ │ global_average_pooling │ (None, 128) │ 0 │
64
+ │ (GlobalAveragePooling2D) │ │ │
65
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
66
+ │ features (Dense) │ (None, 256) │ 33,024 │
67
+ ├─────────────────────────────────┼────────────────────────┼───────────────┤
68
+ │ dropout (Dropout) │ (None, 256) │ 0 │
69
+ ├─────────────────────────────────┼─────��──────────────────┼───────────────┤
70
+ │ classification (Dense) │ (None, 36) │ 9,252 │
71
+ └─────────────────────────────────┴────────────────────────┴───────────────┘
72
+ Total params: 330,628 (1.26 MB)
73
+ Trainable params: 329,732 (1.26 MB)
74
+ Non-trainable params: 896 (3.50 KB)
75
+
reproducibility/08_cnn_baseline_reproducible.ipynb ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# CNN baseline — ASL-HG, participant-disjoint\n",
8
+ "\n",
9
+ "Baseline tái lập: archive processed của ASL-HG, hash metadata đã publish, khử exact duplicate và split theo người tham gia (8/1/1). Không dùng split train/test dựng sẵn của tác giả vì protocol này khóa validation và test theo participant.\n"
10
+ ]
11
+ },
12
+ {
13
+ "cell_type": "code",
14
+ "execution_count": null,
15
+ "metadata": {},
16
+ "outputs": [],
17
+ "source": [
18
+ "%pip -q install 'huggingface-hub>=0.25' pandas pyarrow scikit-learn matplotlib seaborn\n"
19
+ ]
20
+ },
21
+ {
22
+ "cell_type": "code",
23
+ "execution_count": null,
24
+ "metadata": {},
25
+ "outputs": [],
26
+ "source": [
27
+ "from pathlib import Path\n",
28
+ "from datetime import datetime, timezone\n",
29
+ "import hashlib, json, os, random, re, shutil, zipfile\n",
30
+ "import matplotlib.pyplot as plt\n",
31
+ "import numpy as np\n",
32
+ "import pandas as pd\n",
33
+ "import seaborn as sns\n",
34
+ "import tensorflow as tf\n",
35
+ "from huggingface_hub import snapshot_download\n",
36
+ "from sklearn.metrics import accuracy_score, classification_report, confusion_matrix\n",
37
+ "\n",
38
+ "EXPERIMENT_ID = 'cnn-001-participant-disjoint'\n",
39
+ "DATASET_REPO = 'hnam25/asl-hand-gesture-images'\n",
40
+ "DATASET_REVISION = '8f36ac00ece6dfce94410a980a839d93a912d366'\n",
41
+ "AUDIT_DIRECTORY = 'metadata/colab-audit-2026-08-10'\n",
42
+ "PROCESSED_ARCHIVE = 'ASL_HG_36000/ASL_Processed_Images.zip'\n",
43
+ "SEED, IMAGE_SIZE, BATCH_SIZE, EPOCHS, LEARNING_RATE, DROPOUT = 42, 128, 64, 30, 1e-3, .3\n",
44
+ "CLASSES = [str(i) for i in range(10)] + [chr(i) for i in range(ord('A'), ord('Z') + 1)]\n",
45
+ "ROOT = Path('/content/asl-cnn-baseline')\n",
46
+ "HF_ROOT, PROCESSED, OUTPUTS = ROOT/'hf', ROOT/'processed', ROOT/'outputs'\n",
47
+ "for directory in (HF_ROOT, PROCESSED, OUTPUTS/'models', OUTPUTS/'metrics', OUTPUTS/'figures', OUTPUTS/'logs', OUTPUTS/'metadata'):\n",
48
+ " directory.mkdir(parents=True, exist_ok=True)\n",
49
+ "random.seed(SEED); np.random.seed(SEED); tf.keras.utils.set_random_seed(SEED)\n",
50
+ "print({'tensorflow': tf.__version__, 'gpus': [d.name for d in tf.config.list_physical_devices('GPU')], 'experiment_id': EXPERIMENT_ID})\n"
51
+ ]
52
+ },
53
+ {
54
+ "cell_type": "code",
55
+ "execution_count": null,
56
+ "metadata": {},
57
+ "outputs": [],
58
+ "source": [
59
+ "def sha256_file(path, chunk_size=8 * 1024 * 1024):\n",
60
+ " digest = hashlib.sha256()\n",
61
+ " with Path(path).open('rb') as handle:\n",
62
+ " for chunk in iter(lambda: handle.read(chunk_size), b''):\n",
63
+ " digest.update(chunk)\n",
64
+ " return digest.hexdigest()\n",
65
+ "\n",
66
+ "snapshot_download(repo_id=DATASET_REPO, repo_type='dataset', revision=DATASET_REVISION, local_dir=HF_ROOT, allow_patterns=[PROCESSED_ARCHIVE, f'{AUDIT_DIRECTORY}/**'])\n",
67
+ "archive = HF_ROOT / PROCESSED_ARCHIVE\n",
68
+ "audit_root = HF_ROOT / AUDIT_DIRECTORY\n",
69
+ "audit_manifest = json.loads((audit_root/'cache_manifest.json').read_text())\n",
70
+ "if not archive.is_file(): raise RuntimeError(f'Missing {PROCESSED_ARCHIVE}')\n",
71
+ "with zipfile.ZipFile(archive) as z:\n",
72
+ " for member in z.infolist():\n",
73
+ " target = (PROCESSED/member.filename).resolve()\n",
74
+ " if PROCESSED.resolve() not in target.parents and target != PROCESSED.resolve(): raise RuntimeError(f'Unsafe ZIP member: {member.filename}')\n",
75
+ " z.extractall(PROCESSED)\n",
76
+ "processed_archive_sha256 = sha256_file(archive)\n",
77
+ "audit = pd.read_csv(audit_root/'audit.csv')\n",
78
+ "print({'processed_archive_sha256': processed_archive_sha256, 'audit_totals': audit_manifest['totals']})\n"
79
+ ]
80
+ },
81
+ {
82
+ "cell_type": "code",
83
+ "execution_count": null,
84
+ "metadata": {},
85
+ "outputs": [],
86
+ "source": [
87
+ "# Match every processed image to audited raw-image hash, then retain one representative per exact hash.\n",
88
+ "image_paths = sorted(path for path in PROCESSED.rglob('*') if path.suffix.lower() in {'.jpg', '.jpeg', '.png', '.bmp', '.webp'})\n",
89
+ "records = []\n",
90
+ "subject_pattern = re.compile(r'^P(\\d+)_')\n",
91
+ "for path in image_paths:\n",
92
+ " parts = path.parts\n",
93
+ " label = next((part for part in reversed(parts[:-1]) if part in CLASSES), None)\n",
94
+ " if label is None: raise RuntimeError(f'Cannot infer label from {path}')\n",
95
+ " match = subject_pattern.match(path.name)\n",
96
+ " if not match: raise RuntimeError(f'Cannot infer participant from {path.name}')\n",
97
+ " records.append({'label': label, 'processed_path': str(path), 'relative_path': f'{label}/{path.name}', 'participant': f'P{match.group(1)}'})\n",
98
+ "processed = pd.DataFrame(records)\n",
99
+ "usable = processed.merge(audit[audit.status == 'ok'][['relative_path', 'label', 'sha256']], on=['relative_path', 'label'], how='left', validate='one_to_one')\n",
100
+ "if len(usable) != 36000 or usable.sha256.isna().any(): raise RuntimeError('Processed archive does not match the pinned audit metadata.')\n",
101
+ "if (usable.groupby('sha256').label.nunique() > 1).any(): raise RuntimeError('Conflicting labels for an exact raw-image hash.')\n",
102
+ "usable = usable.sort_values(['sha256', 'relative_path'], kind='stable').reset_index(drop=True)\n",
103
+ "usable['duplicate_group_size'] = usable.groupby('sha256').sha256.transform('size')\n",
104
+ "usable['canonical_relative_path'] = usable.groupby('sha256').relative_path.transform('first')\n",
105
+ "usable['is_canonical'] = usable.relative_path.eq(usable.canonical_relative_path)\n",
106
+ "usable.to_csv(OUTPUTS/'metadata'/'deduplication_manifest.csv', index=False)\n",
107
+ "before_dedup = len(usable); usable = usable[usable.is_canonical].copy()\n",
108
+ "participants = sorted(usable.participant.unique(), key=lambda value: int(value[1:]))\n",
109
+ "if len(participants) != 10 or set(usable.participant) != set(participants): raise RuntimeError(f'Expected P1-P10, got {participants}')\n",
110
+ "rng = np.random.default_rng(SEED); ordered = list(rng.permutation(participants))\n",
111
+ "train_participants, validation_participant, test_participant = sorted(ordered[:8]), ordered[8], ordered[9]\n",
112
+ "train = usable[usable.participant.isin(train_participants)].copy()\n",
113
+ "validation = usable[usable.participant.eq(validation_participant)].copy()\n",
114
+ "test = usable[usable.participant.eq(test_participant)].copy()\n",
115
+ "for name, frame in {'train': train, 'validation': validation, 'test': test}.items(): frame[['processed_path', 'label', 'participant', 'sha256']].to_csv(OUTPUTS/'metadata'/f'{name}.csv', index=False)\n",
116
+ "if set(train.sha256) & set(validation.sha256) or set(train.sha256) & set(test.sha256) or set(validation.sha256) & set(test.sha256): raise RuntimeError('Hash leakage detected.')\n",
117
+ "if set(train.participant) & set(validation.participant) or set(train.participant) & set(test.participant) or set(validation.participant) & set(test.participant): raise RuntimeError('Participant leakage detected.')\n",
118
+ "split_manifest = {'experiment_id': EXPERIMENT_ID, 'policy': 'participant-disjoint 8/1/1; one canonical representative per exact raw-image SHA-256', 'seed': SEED, 'participants': {'train': train_participants, 'validation': validation_participant, 'test': test_participant}, 'counts': {'before_deduplication': before_dedup, 'after_deduplication': len(usable), 'removed_exact_duplicates': before_dedup-len(usable), 'train': len(train), 'validation': len(validation), 'test': len(test)}}\n",
119
+ "(OUTPUTS/'metadata'/'split_manifest.json').write_text(json.dumps(split_manifest, indent=2), encoding='utf-8')\n",
120
+ "print(json.dumps(split_manifest, indent=2))\n"
121
+ ]
122
+ },
123
+ {
124
+ "cell_type": "code",
125
+ "execution_count": null,
126
+ "metadata": {},
127
+ "outputs": [],
128
+ "source": [
129
+ "label_index = {label: index for index, label in enumerate(CLASSES)}\n",
130
+ "def make_dataset(frame, training=False):\n",
131
+ " ds = tf.data.Dataset.from_tensor_slices((frame.processed_path.values, frame.label.map(label_index).values))\n",
132
+ " if training: ds = ds.shuffle(len(frame), seed=SEED, reshuffle_each_iteration=True)\n",
133
+ " def load(path, label):\n",
134
+ " image = tf.io.decode_image(tf.io.read_file(path), channels=3, expand_animations=False); image.set_shape([None, None, 3])\n",
135
+ " image = tf.image.resize(tf.cast(image, tf.float32), (IMAGE_SIZE, IMAGE_SIZE))\n",
136
+ " return image, tf.one_hot(label, len(CLASSES))\n",
137
+ " return ds.map(load, num_parallel_calls=tf.data.AUTOTUNE).batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)\n",
138
+ "\n",
139
+ "train_ds, validation_ds, test_ds = make_dataset(train, True), make_dataset(validation), make_dataset(test)\n",
140
+ "def build_cnn():\n",
141
+ " inputs = tf.keras.Input((IMAGE_SIZE, IMAGE_SIZE, 3), name='image')\n",
142
+ " x = tf.keras.layers.Rescaling(1/255, name='rescale')(inputs)\n",
143
+ " for filters in (32, 64, 128):\n",
144
+ " x = tf.keras.layers.Conv2D(filters, 3, padding='same', use_bias=False)(x)\n",
145
+ " x = tf.keras.layers.BatchNormalization()(x); x = tf.keras.layers.ReLU()(x)\n",
146
+ " x = tf.keras.layers.Conv2D(filters, 3, padding='same', use_bias=False)(x)\n",
147
+ " x = tf.keras.layers.BatchNormalization()(x); x = tf.keras.layers.ReLU()(x)\n",
148
+ " x = tf.keras.layers.MaxPooling2D()(x); x = tf.keras.layers.Dropout(DROPOUT / 2)(x)\n",
149
+ " x = tf.keras.layers.GlobalAveragePooling2D(name='global_average_pooling')(x)\n",
150
+ " x = tf.keras.layers.Dense(256, activation='relu', name='features')(x)\n",
151
+ " x = tf.keras.layers.Dropout(DROPOUT, name='dropout')(x)\n",
152
+ " outputs = tf.keras.layers.Dense(len(CLASSES), activation='softmax', name='classification')(x)\n",
153
+ " return tf.keras.Model(inputs, outputs, name='cnn_asl')\n",
154
+ "\n",
155
+ "model = build_cnn(); model.compile(optimizer=tf.keras.optimizers.Adam(LEARNING_RATE), loss='categorical_crossentropy', metrics=['accuracy'])\n",
156
+ "with (OUTPUTS/'models'/'model_summary.txt').open('w') as handle: model.summary(print_fn=lambda line: handle.write(line+'\\n'))\n",
157
+ "checkpoint = OUTPUTS/'models'/'cnn_001_participant_disjoint.keras'\n",
158
+ "callbacks = [tf.keras.callbacks.ModelCheckpoint(checkpoint, monitor='val_accuracy', save_best_only=True), tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True), tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', patience=2, factor=.2), tf.keras.callbacks.CSVLogger(OUTPUTS/'logs'/'training_history.csv')]\n",
159
+ "history = model.fit(train_ds, validation_data=validation_ds, epochs=EPOCHS, verbose=2, callbacks=callbacks)\n",
160
+ "pd.DataFrame(history.history).to_csv(OUTPUTS/'logs'/'training_history.csv', index=False)\n"
161
+ ]
162
+ },
163
+ {
164
+ "cell_type": "code",
165
+ "execution_count": null,
166
+ "metadata": {},
167
+ "outputs": [],
168
+ "source": [
169
+ "best = tf.keras.models.load_model(checkpoint)\n",
170
+ "probability = best.predict(test_ds, verbose=1); predicted = probability.argmax(1); truth = test.label.map(label_index).to_numpy()\n",
171
+ "report = classification_report(truth, predicted, labels=range(36), target_names=CLASSES, output_dict=True, zero_division=0)\n",
172
+ "matrix = confusion_matrix(truth, predicted, labels=range(36))\n",
173
+ "pd.DataFrame(report).T.to_csv(OUTPUTS/'metrics'/'classification_report.csv')\n",
174
+ "pd.DataFrame(matrix, index=CLASSES, columns=CLASSES).to_csv(OUTPUTS/'metrics'/'confusion_matrix.csv')\n",
175
+ "o, zero = label_index['O'], label_index['0']\n",
176
+ "summary = {'experiment_id': EXPERIMENT_ID, 'test_accuracy': float(accuracy_score(truth, predicted)), 'macro_precision': report['macro avg']['precision'], 'macro_recall': report['macro avg']['recall'], 'macro_f1': report['macro avg']['f1-score'], 'O_recall': report['O']['recall'], '0_recall': report['0']['recall'], 'O_to_0': int(matrix[o, zero]), '0_to_O': int(matrix[zero, o]), 'best_validation_accuracy': float(max(history.history['val_accuracy'])), 'epochs_ran': len(history.history['loss'])}\n",
177
+ "(OUTPUTS/'metrics'/'summary.json').write_text(json.dumps(summary, indent=2), encoding='utf-8')\n",
178
+ "plt.figure(figsize=(16, 13)); sns.heatmap(matrix, cmap='Blues', xticklabels=CLASSES, yticklabels=CLASSES); plt.xlabel('Predicted'); plt.ylabel('True'); plt.tight_layout(); plt.savefig(OUTPUTS/'figures'/'confusion_matrix.png', dpi=180); plt.close()\n",
179
+ "config = {'experiment_id': EXPERIMENT_ID, 'created_at_utc': datetime.now(timezone.utc).isoformat(), 'dataset_repo': DATASET_REPO, 'dataset_revision': DATASET_REVISION, 'processed_archive': PROCESSED_ARCHIVE, 'processed_archive_sha256': processed_archive_sha256, 'audit_manifest': audit_manifest, 'split_manifest': split_manifest, 'model': {'architecture': 'CNN from scratch: 3 x [Conv-BN-ReLU-Conv-BN-ReLU-MaxPool]', 'image_size': IMAGE_SIZE, 'batch_size': BATCH_SIZE, 'epochs_max': EPOCHS, 'learning_rate': LEARNING_RATE, 'dropout': DROPOUT}, 'tensorflow': tf.__version__}\n",
180
+ "(OUTPUTS/'metadata'/'experiment_config.json').write_text(json.dumps(config, indent=2), encoding='utf-8')\n",
181
+ "os.system(f\"pip freeze > {OUTPUTS/'metadata'/'environment.txt'}\")\n",
182
+ "print(json.dumps(summary, indent=2))\n"
183
+ ]
184
+ },
185
+ {
186
+ "cell_type": "markdown",
187
+ "metadata": {},
188
+ "source": [
189
+ "## Publish\n",
190
+ "\n",
191
+ "Download `outputs/` về local. Tạo một Hugging Face model repo và upload nguyên thư mục output cùng notebook này; model card phải nêu rõ participant-disjoint protocol, revision dữ liệu, SHA archive và số liệu test.\n"
192
+ ]
193
+ }
194
+ ],
195
+ "metadata": {
196
+ "kernelspec": {
197
+ "display_name": "Python 3",
198
+ "language": "python",
199
+ "name": "python3"
200
+ },
201
+ "language_info": {
202
+ "name": "python",
203
+ "version": "3.x"
204
+ }
205
+ },
206
+ "nbformat": 4,
207
+ "nbformat_minor": 5
208
+ }
reproducibility/requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ tensorflow>=2.16,<2.20
2
+ mediapipe>=0.10.14
3
+ opencv-python>=4.9
4
+ numpy>=1.26
5
+ pandas>=2.2
6
+ scikit-learn>=1.5
7
+ matplotlib>=3.9
8
+ seaborn>=0.13
9
+ PyYAML>=6.0
10
+ pyarrow>=16.0
11
+ jupyterlab>=4.0
12
+ pytest>=8.0
13
+ huggingface-hub>=0.25