Table of Contents Tables nan kontni Introduction: etikèt yo chè, imaj yo gratis Pandan 1 - Done eksplorasyon: konprann done ou ap travay ak Pati 2 — Preprocessing: pale lang nan modèl la Pandan 3 — Ekstraksyon karakteristik: Konvèti imaj nan nimewo vle di Pwodwi pou Telefòn Pwodwi pou Telefòn Pwodwi pou Telefòn Pwodwi pou Telefòn Pwodwi pou Telefòn Pwodwi pou Telefòn Pwodwi pou Telefòn Pwodwi pou Telefòn Pandan 5 - Semi-Supervised fòmasyon: Eksperyans la debaz Pandan 6 - Scaling nan Milyon nan imaj: Yon Roadmap Realistik Konklisyon Etikèt yo chè, imaj yo gratis Nan yon mond pafè, chak imaj nan dataset ou ta dwe gen yon etikèt. "Defektif." "Normal." "Crack kalite A." "Scratch kalite B." Men, nan mond lan reyèl, etikèt la se brutalman chè. Yon sèl radiolog medikal ka etikèt ka 50 tès sèvo pou chak èdtan - nan € 200 / èdtan. Yon enspekteur bon jan kalite endistriyèl ka anotasyon ka 100 pati pou chak èdtan. Nan mas la, etikèt 100,000 imaj ka koute plis pase fòmasyon modèl la menm. Isit la se paradoks la: konpayi yo souvan gen milyon de imaj (ki soti nan kamera, sansè, itilizatè uploads) men yo ka sèlman pèmèt yo etikèt yon ti kras fraksyon. Sa a se kote Kòm pou retire imaj yo san etikèt, nou itilize yo amelyore modèl la - konbine yon ti seri etikèt ak yon gwo seri san etikèt. semi-supervised learning Imagine ke ou se yon pwofesè ak 30 elèv. Ou bay tout moun yon egzamen, men ou sèlman gen tan pou evalye 5 papye. Ou evalye sa yo 5 byen, epi ou note modèl: elèv ki te ekri yon anpil tendans jwenn pousantaj segondè, ak elèv ki kite repons blan tendans jwenn pousantaj ki ba. lè l sèvi avèk modèl sa yo, ou ka estime pousantaj yo nan lòt 25 papye yo san yo pa li chak liy. Sa a se semi-supervised aprantisaj: ou sèvi ak kèk papye gradye (dat etikèt) yo konprann modèl yo, Lè sa a, aplike modèl sa yo nan moun ki pa gradye (dat san etikèt). Nan atik sa a bati yon konplèt semi-supervise imaj klasman pipeline soti nan kou. Nou pral travay nan chak etap ak eksplike detaye lè nou ale. Casestudy: detekte defè pwodiksyon sou sifas metal. Yon faktori pwodui plak asye, ak kamera foto chak plak kòm li rale soti nan liy pwodiksyon an. Pifò plak yo nòmal, kèk gen defè (krak, kranpon, pitting, enklizyon). Nou gen 10,000 imaj, men sèlman 200 etikèt yo. Casestudy: detekte defè pwodiksyon sou sifas metal. Yon faktori pwodui plak asye, ak kamera foto chak plak kòm li rale soti nan liy pwodiksyon an. Pifò plak yo nòmal, kèk gen defè (krak, kranpon, pitting, enklizyon). Nou gen 10,000 imaj, men sèlman 200 etikèt yo. Konstriksyon nan yon pipeline aprantisaj semi-supervise Anvan plonje nan nenpòt kòd, tanpri cartographies tout tiyo a. Konpreyansyon nan flux an premye pral fè chak etap santi yo apwopriye olye pou aleksyon. Pran yon moman yo etidye dyagram sa a - chak bwat se yon etap nou pral aplike: THE COMPLETE SEMI-SUPERVISED PIPELINE: [10,000 raw images] ──▶ [Exploration & Cleaning] │ ▼ [Preprocessing: resize, normalize, histogram equalization] │ ▼ [Feature Extraction: pretrained ResNet50 → 2048-dim embedding per image] │ ┌─────────┴──────────┐ │ │ ▼ ▼ [200 LABELED images] [9,800 UNLABELED images] │ │ │ ▼ │ [Clustering: K-Means, DBSCAN │ on embeddings → pseudo-labels] │ │ │ ▼ │ [WEAKLY labeled dataset] │ (cluster assignments) │ │ ▼ ▼ ┌────────────────────────────────────┐ │ SEMI-SUPERVISED TRAINING: │ │ 1. Pre-train CNN on weakly labeled │ │ 2. Fine-tune CNN on strongly labeled│ │ 3. Compare vs supervised-only │ └────────────────────────────────────┘ │ ▼ [Evaluation: F1, AUC-ROC, confusion matrix, comparison] Key insight: nou pral vire imaj ki pa etikèt nan imaj etikèt (pa kouvèti), Lè sa a, sèvi ak konesans apwopriye a bay modèl final la yon tèt kòmanse. Pens li kòm bay yon elèv yon gid etid enpòtan anvan egzamen an reyèl - li pa pral pafè, men li se pi bon pase pa gen anyen. Aprann Epitou, nou pral eksplike sou vocabulary nou pral sèvi tout antye nan atik sa a, paske konbine tèm yo se yon kay trè komen nan konfuzyon: Strictly labelled (oswa jis "labeled"): imaj ak etikèt verifye pa yon ekspè imen. Gold estanda. Nou gen 200 nan sa yo. Mal etikèt (oswa "pseudo-etikèt"): imaj ki etikèt yo te enkyete pa kouvèti. Pi bon mache men pi bruyant. Nou pral kreye 9,800 nan sa yo. Pa etikèt: imaj ki pa gen okenn etikèt nan tout. Sa a se estati yo anvan nou klase yo. Embedding: yon rezime nimewo konpatib nan yon imaj, ki te pwodwi pa yon rezo nervo prétrainé. Zouti prensipal nou an pou fè imaj konpare. Kontinye lekti: Kontinye lekti: Semi-supervise aprantisaj konvèsyon - scikit-learn Google Rechèch sou aprantisaj semi-supervise Semi-supervise aprantisaj konvèsyon - scikit-learn Google Rechèch sou aprantisaj semi-supervise 1. Done eksplorasyon: konprann done a ou ap travay ak Poukisa ou ta dwe tcheke done ou anvan fè nenpòt lòt bagay Yon seri done imaj gen mòd erè inik ou pa pral jwenn nan done tab: dosye korozyon ki kraze koule fòmasyon ou nan 3 AM, rezolisyon incoherent ki silentman distorsion imaj ou, chanèl koulè mal (grès maske kòm RGB), ak ekstreman echilib la klas kote 95% nan imaj yo "normal." Si ou pase eksplorasyon an, modèl ou pral silentman aprann misk - epi ou pa pral konnen poukisa li pèfòmans mal. Règ la lò: Jwenn yon gade nan sa nou fè fas ak. never trust data you haven't inspected. 1.1 — Loading ak kontni nan imaj yo Dataset nou an se òganize nan de dosye prensipal: youn ak imaj etikèt (subdivize nan "normal" ak "defekt" sous dosye), ak youn ak imaj san etikèt (pa sous dosye - jis yon koleksyon plat nan nan dosye). .png import os import numpy as np import pandas as pd import matplotlib.pyplot as plt from PIL import Image from pathlib import Path data_dir = Path("data/metal_surfaces") labeled_dir = data_dir / "labeled" unlabeled_dir = data_dir / "unlabeled" Nou itilize pi bon pase konkatenasyon string paske li trete separatè chemen kòrèkteman sou nenpòt sistèm operasyon. Koulye a, nou kontan: pathlib.Path labeled_files = list(labeled_dir.glob("**/*.png")) unlabeled_files = list(unlabeled_dir.glob("**/*.png")) print(f"Labeled images: {len(labeled_files)}") print(f"Unlabeled images: {len(unlabeled_files)}") print(f"Total: {len(labeled_files) + len(unlabeled_files)}") label_ratio = len(labeled_files) / (len(labeled_files) + len(unlabeled_files)) print(f"Label ratio: {label_ratio:.1%}") Ratio etikèt sa a se tipikman sou 2%. Se sèlman 2% nan done nou an gen etikèt verifye pa ekspè. 98% lòt la se yon min lò nou pa ka pèmèt yo ignore - ak sa a se eksakteman sa semi-supervised aprantisaj exploits. 1.2 — Scan pou pwoblèm: rezolisyon, koulè, koreksyon Apre sa, nou bezwen tcheke chak imaj separe. Yon sèl dosye korozyon ka kraze yon kouri fòmasyon antye. Rezolisyon inkonsistan pral distorsion imaj si ou pa atansyon. Epi mòd koulè mal (griz-scale lè modèl ou espere RGB) pral pwodwi karakteristik nonsens. Nou ekri yon ti fonksyon ki eseye louvri chak imaj ak anrejistre pwopriyete li yo: def get_image_info(filepath): """ Try to open an image and record its properties. If the file is corrupted, Pillow will throw an exception. """ try: img = Image.open(filepath) return { "path": str(filepath), "width": img.size[0], "height": img.size[1], "mode": img.mode, # 'RGB', 'L' (grayscale), 'RGBA' "filesize_kb": os.path.getsize(filepath) / 1024, "corrupted": False, } except Exception: return { "path": str(filepath), "width": None, "height": None, "mode": None, "filesize_kb": None, "corrupted": True, } nan Field se espesyalman enpòtan. vle di 3 chanèl koulè (red, gri, ble). vle di grayscale (1 chanèl). vle di RGB ak yon alfa (transparans) chanèl. Modèl pre-tren nou an espere RGB, se konsa nou pral bezwen konvèti tout bagay pita. img.mode 'RGB' 'L' 'RGBA' Koulye a, nou pral tcheke tout 10,000 imaj: all_files = labeled_files + unlabeled_files image_info = [get_image_info(f) for f in all_files] info_df = pd.DataFrame(image_info) Eske ou tcheke rezilta yo: print(f"Total images scanned: {len(info_df)}") print(f"Corrupted images: {info_df['corrupted'].sum()}") print(f"\nResolution distribution:") print(info_df[~info_df["corrupted"]][["width", "height"]].describe().round(0)) print(f"\nColor modes: {info_df['mode'].value_counts().to_dict()}") print(f"File size (KB): min={info_df['filesize_kb'].min():.0f}, " f"max={info_df['filesize_kb'].max():.0f}, " f"mean={info_df['filesize_kb'].mean():.0f}") Ki sa yo dwe wè nan pwodiksyon an: Images Korrupted > 0: Retire yo imedyatman. Menm yon sèl dosye mal ka kraze fòmasyon ou. Resolisyon diferan: Si min ≠ max pou lajè oswa segondè, imaj yo gen gwosè diferan. Nou pral redimansyon yo tout nan 224×224 pandan preprocessing. Pifò mòd koulè: Si ou wè tou de 'RGB' ak 'L', ou gen yon mix nan koulè ak grès. Nou pral konvèti tout nan RGB. Extreme gwosè dosye: Yon dosye 1KB se pwobableman vèt oswa korom. Yon dosye 50MB ta ka pa konpresyon - vo rechèch. Tanpri retire dosye yo kòrèk: corrupted_paths = set(info_df[info_df["corrupted"]]["path"].tolist()) if corrupted_paths: print(f"Removing {len(corrupted_paths)} corrupted images") labeled_files = [f for f in labeled_files if str(f) not in corrupted_paths] unlabeled_files = [f for f in unlabeled_files if str(f) not in corrupted_paths] 1.3 — Distribisyon klas: Èske seri a etikèt nou an se balans? Nan anviwònman endistriyèl ak medikal, defè yo rare. Seti a etikèt ou ta ka 90% "normal" ak 10% "defèk." Sa a enpòtan: yon modèl lazè ki toujou prezante "normal" ta jwenn 90% presizyon pandan y ap se konplètman inutil. Nou bezwen konnen balans la anvan an pou nou ka kompense pou li pita. class_counts = {} for class_dir in labeled_dir.iterdir(): if class_dir.is_dir(): count = len(list(class_dir.glob("*.png"))) class_counts[class_dir.name] = count print("Class distribution (labeled set):") for cls, count in class_counts.items(): pct = count / sum(class_counts.values()) * 100 print(f" {cls}: {count} images ({pct:.1f}%)") Si desebalans la se grav, nou pral sove li pita lè l sèvi avèk yon teknik ki rele nan fonksyon la pèdi - esansyèlman di modèl la "manke yon defèk se 4x pi mal pase yon alèt false." pos_weight Epitou, nou pral visualize li: fig, ax = plt.subplots(figsize=(6, 4)) ax.bar(class_counts.keys(), class_counts.values(), color=["#2ecc71", "#e74c3c"]) ax.set_title("Class Distribution (Labeled Images)") ax.set_ylabel("Number of images") plt.tight_layout() plt.savefig("outputs/class_distribution.png", dpi=150) plt.show() 1.4 — Visualize echantiyon imaj: Toujou gade anvan ou modèl Li kapab etap la ki pi enpòtan nan tout pipeline la. Gade done ou yo. Ou ka jwenn imaj ki klèman mal etikèt, sonje artefakt (kòt nwa, rotasyon), pwoblèm bon jan kalite (blur, overerexposure), oswa ke defè yo vizyèlman subtile ak travay ou se pi difisil pase ou te panse. fig, axes = plt.subplots(2, 5, figsize=(15, 6)) fig.suptitle("Sample Images — Top: Normal | Bottom: Defect", fontsize=14) for i, class_name in enumerate(["normal", "defect"]): class_files = list((labeled_dir / class_name).glob("*.png"))[:5] for j, filepath in enumerate(class_files): img = Image.open(filepath) axes[i, j].imshow(img, cmap="gray" if img.mode == "L" else None) axes[i, j].set_title(class_name, fontsize=10) axes[i, j].axis("off") plt.tight_layout() plt.savefig("outputs/sample_images.png", dpi=150) plt.show() Peye yon moman yo etidye imaj sa yo. Ka wè defè yo ak pwòp tèt ou? Si ou pa ka, modèl la pral batay tou. Si defè yo se evidan (ak yon kraze profond, yon gwo crack), sa a se ankouraje - modèl la ta dwe kapab aprann diferans la. Si yo se subtile (yon ti kras decoloration, yon kouch liy crack), ou pral bezwen espesyalman bon preprocessing ak ekstraksyon karakteristik. ou Kontinye lekti: Kontinye lekti: Dokiman Pillow - Bibliyotèk nan imaj Python nou itilize pou loading imaj NEU Surface Defect Database - yon reyèl mond lan jaden sifas defekt dataset ou ka pratik ak Dokiman pou Pillow Nouvo Surface Defect Database Pati 2 — Preprocessing: pale lang nan modèl la Poukisa nou pa ka sèlman manje imaj manje nan yon rezo nwa Yon CNN pretrained tankou ResNet50 te fòme sou yon kalite entwodiksyon trè espesifik: 224×224 imaj piksèl, nan koulè RGB, normalized ak valè medyeval ak estanda deviasyon espesifik yo calculated soti nan ImageNet dataset la. Si ou manje li imaj nan gwosè diferan, oswa ak valè piksèl brik olye pou yo normalized, karakteristik sa a ekstrè pral sanble sanble. Pense nan li tankou lang. ResNet50 "soti nan ImageNet." Si nou vle li konprann imaj sifas metal nou an, nou bezwen "translate" yo nan fòma ImageNet an premye. Sa a tradiksyon gen ladan kat etap: Konvèti nan RGB (3 chanèl) Pwoteje kontras pa ekivalan histogram Kòmanse nan 224×224 Normalize valè pixel yo konpare ak estatistik nan ImageNet 2.1 - Ki sa ki se ekilasyon histogram, ak poukisa li enpòtan isit la? Diferans ki genyen ant yon sifas nòmal ak yon sifas rase ka gen sèlman yon kèk nivo piksèl-invizib pou okipe, ak trè difisil pou yon modèl detekte. redistribye entansite piksèl pou ke ranje a plen (0 a 255) se itilize menm jan an. Rezilta: karakteristik subtile "pop-out" vizyèlman ak numerikman. Histogram equalization Nou itilize yon vèsyon plis avanse rele (Contrast Limited Adaptive Histogram Equalization). Nan kontrè ak global equalization (ki aplike transformasyon an menm pou tout imaj la), CLAHE divize imaj la nan ti twal ti kras (8x8 pa default) ak egale chak twal indépendantman. Sa a preserve detay lokal pi byen. CLAHE Isit la se analogye a: imajine w ap ajiste brightness la sou yon foto. Global egalizasyon se tankou lè l sèvi avèk yon sèl glise slider pou imaj la tout antye - ou ta ka illuminate koutim yo, men lave soti deja-bright sant la. CLAHE se tankou ajiste brightness nan chak rejyon otomatikman, se konsa chak pati nan imaj la vin klè. 2.2 — Kreye yon Custom PyTorch Dataset PyTorch òganize loading done sou de klas: (ki konnen ki jan yo chaje yon atik) ak (ki konnen ki jan yo batch ak shuffle atik). PyTorch bay yon entegre dataset, men li asume chak imaj gen yon etikèt. Dataset nou an gen tou de imaj etikèt ak san etikèt, se konsa nou bezwen yon klas Custom. Dataset DataLoader ImageFolder Nou pral bati li etap pa etap. Premye, skeleton an: import torch import torchvision.transforms as T from torch.utils.data import Dataset, DataLoader import cv2 class MetalSurfaceDataset(Dataset): """ Custom Dataset that handles both labeled and unlabeled images. Returns -1 as the label for unlabeled images. """ def __init__(self, image_paths, labels=None, transform=None): self.image_paths = image_paths self.labels = labels # None for unlabeled images self.transform = transform def __len__(self): return len(self.image_paths) PyTorch di ki kantite imaj nou gen. depoze vwayaj yo imaj ak (optional) etikèt yo. __len__ __init__ Koulye a, metòd prensipal la, , ki chaje ak preprocesses yon sèl imaj. Nou pral divize li nan twa etap: __getitem__ Stage 1 — Load the image and force RGB: def __getitem__(self, idx): # Load image and convert to RGB # .convert("RGB") handles grayscale → RGB conversion automatically # (it duplicates the single channel into R, G, and B) img = Image.open(self.image_paths[idx]).convert("RGB") Ki jan ? Paske ResNet50 espere 3 chanèl. Si imaj nou an se grayscale (1 chanèl), sa a duplique valè yo gri nan R, G, ak B. Si li se deja RGB, li pa fè anyen. Si li se RGBA (4 chanèl), li retire chanèl la alfa. .convert("RGB") Stage 2 — Apply CLAHE histogram equalization: # Convert PIL Image → numpy array for OpenCV processing img_np = np.array(img) # Convert RGB → LAB color space # L = Lightness (brightness), A and B = color channels # We only equalize L (brightness) to avoid distorting colors img_lab = cv2.cvtColor(img_np, cv2.COLOR_RGB2LAB) # Create CLAHE object and apply to L channel # clipLimit=2.0 prevents over-amplification of noise # tileGridSize=(8,8) means 8x8 tiles for local equalization clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) img_lab[:, :, 0] = clahe.apply(img_lab[:, :, 0]) # Convert back LAB → RGB → PIL Image img_np = cv2.cvtColor(img_lab, cv2.COLOR_LAB2RGB) img = Image.fromarray(img_np) Poukisa LAB ak pa sèlman aplike CLAHE dirèkteman sou RGB? Paske si ou egalitye R, G, ak B chanèl otomatikman, ou pral distorsion koulè yo - konvèti ble nan grenn, pou egzanp. Pa travay nan espas LAB, nou sèlman touche chanèl la limyè (L) ak kite koulè yo (A, B) san touche. Sa a se yon pratik estanda nan pwosesis imaj. Stage 3 — Apply transforms and return: # Apply resize + normalize transforms if self.transform: img = self.transform(img) # Return the label (or -1 if this image has no label) label = self.labels[idx] if self.labels is not None else -1 return img, label 2.3 — Pipe transformasyon: sa ki fè chak etap Koulye a, nou definye sekans la nan transformasyon. Chak nan gen yon objektif spesifik: preprocessing = T.Compose([ T.Resize((224, 224)), # (1) Resize to model's expected input size T.ToTensor(), # (2) PIL Image → PyTorch Tensor, scale [0,1] T.Normalize( mean=[0.485, 0.456, 0.406], # (3) Normalize with ImageNet statistics std=[0.229, 0.224, 0.225], ), ]) Li pral eksplike chak etap: Arkitektur la nan ResNet50 mande egzakman gwosè sa a. Si ou alimante yon imaj 300×400, fòm yo nan tensè a pa pral korespondan ak PyTorch pral kraze. Résizing ka distorsion rasin aspè yon ti kras, men pou travay ki baze sou tèkstur (tankou deteksyon defè) sa a se rarman yon pwoblèm. (1) Resize to 224×224. Li fè de bagay: konvèti fòma piksèl la soti nan HWC (Hight × Width × Channels) nan CHW (Channels × Height × Width), ki se sa ki PyTorch espere; ak skalè valè piksèl soti nan [0, 255] enterè nan [0.0, 1.0] floats. (2) ToTensor. Nimewo magik sa yo - ak — se mwayen an ak devwa estanda nan valè piksèl atravè tout ImageNet dataset, kalkil chèn pa chèn (R, G, B). ResNet50 te fòme ak valè sa yo egzak normalization, se konsa pwa yo enteryè li yo "attend" enpòte santre alantou 0 ak varyans inite. Sèvi ak nimewo diferan ta dwe tankou medye nan pous, men kalkil nan sentimèt - matematik la breaks. (3) Normalize with ImageNet mean and std. [0.485, 0.456, 0.406] [0.229, 0.224, 0.225] 2.4 - Kreye Datasets ak DataLoaders Koulye a, nou monte tout. Yon prensip kritik: Konbine yo ta kontwole evalyasyon nou an. labeled and unlabeled data must be kept strictly separate at all times. Premyèman, kolekte tablo imaj etikèt ak etikèt klas yo: labeled_paths = [] labeled_labels = [] for class_idx, class_name in enumerate(["normal", "defect"]): class_dir = labeled_dir / class_name for fp in class_dir.glob("*.png"): labeled_paths.append(str(fp)) labeled_labels.append(class_idx) # 0 = normal, 1 = defect Lè sa a, kolekte vwayaj imaj ki pa etikèt (pa gen etikèt nesesè): unlabeled_paths = [str(fp) for fp in unlabeled_files] Kreye objè nan Dataset PyTorch: labeled_dataset = MetalSurfaceDataset(labeled_paths, labeled_labels, preprocessing) unlabeled_dataset = MetalSurfaceDataset(unlabeled_paths, labels=None, transform=preprocessing) print(f"Labeled dataset: {len(labeled_dataset)} images") print(f"Unlabeled dataset: {len(unlabeled_dataset)} images") Finalman, ranpli yo nan DataLoaders. Yon DataLoader batch imaj ansanm (batch_size=32 vle di 32 imaj nan yon fwa) ak opsyonèlman shuffles yo: labeled_loader = DataLoader(labeled_dataset, batch_size=32, shuffle=False) unlabeled_loader = DataLoader(unlabeled_dataset, batch_size=32, shuffle=False) Ki jan Sepandan nou ap sou yo ekstrè karakteristik, epi nou bezwen embeddings yo rete nan lòd menm jan ak lis dosye nou yo. Nou pral shuffle pita, pandan fòmasyon an. shuffle=False Kontinye lekti: Kontinye lekti: transformations nan torchvision — lis la plen CLAHE eksplike (OpenCV tutoryèl) PyTorch Dataset & DataLoader fòmasyon transformations nan torchvision — lis la plen CLAHE eksplike (OpenCV tutoryèl) PyTorch Dataset & DataLoader fòmasyon Pandan 3 — Ekstraksyon karakteristik: konvèti imaj nan nimewo enteresan 3.1 — Poukisa piksèl brik yo yon reprezantan terrible Yon imaj 224×224 RGB gen 150,528 nimewo (224 × 224 × 3 chanèl). Pifò nan yo se zwazo - varyasyon ti kras nan limyè, artefak sensè, artefak kompresyon. Pifò: de foto nan koupe menm jan an, te pran soti nan angle yon ti kras diferan oswa limyè, gen valè piksèl konplètman diferan. Si nou eseye kouvèti oswa klassifye piksèl manje, imaj ki sanble menm pou nou pral parèt konplètman diferan nan algorithm la. Ki sa nou bezwen se yon reprezantan ki retire nan imaj la - "ki sa a sanble yon ranje," "ki sa a se yon sifas lis" - nan yon fòma nimewo kompak, estab. Sa a se sa ki pa gen. meaning embeddings 3.2 - Ki sa ki se yon modèl pretrained ak poukisa nou pa fòmasyon soti nan zèb la Antrenman yon rezo neural debaz soti nan zèb la mande pou yon anpil nan done - tipikman dè santèn de milye de imaj. Nou gen 200 imaj etikèt. Si nou te eseye fòmasyon nan 25 milyon paramèt nan ResNet50 sou 200 imaj, modèl la pral memorize chak imaj fòmasyon pafè, men totalman manke sou imaj nouvo. Sa a se rele . overfitting Men, nou itilize yon modèl ki te deja fòme sou ImageNet - yon dataset nan 14 milyon imaj nan 1,000 kategori (kous, chat, machin, bilding, elatriye). Modèl sa a te deja aprann yo rekonèt karakteristik vizyèl debaz: kantite, tèkstur, fòm, gradients koulè, modèl jeometrik. - yo aplike pou sifas asye menm jan yo aplike pou chat. universal Pense nan li tankou angaje yon foto ki gen eksperyans yo inspekte faktori ou. Yo pa janm te wè plato asye anvan, men yo deja konnen ki jan yo : yo ka detekte tèkstur inik, chanjman abòdab nan bon jan kalite sifas, modèl ki vire nòmal la. Yo jis bezwen aprann sa ki konsidere kòm "defekte" nan faktori espesifik ou. nan 3.3 — Loading nan ResNet50 Tcheke modèl la pretrained: import torchvision.models as models resnet = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1) Yon sèl liy download (pou premye fwa) yon modèl ResNet50 ki pwa yo te fòme sou ImageNet. Modèl sa a konnen ki jan yo klasifye 1000 kategori nan objè chak jou. 3.4 - freze paramèt yo Nou pa vle modifye nenpòt nan karakteristik yo aprann. Nou itilize ResNet kòm yon zouti lis sèlman: for param in resnet.parameters(): param.requires_grad = False PyTorch di "pa kalkil gradients pou paramèt sa yo." Sa a gen de benefis: li anpeche modifikasyon aksidan nan pwa yo pre-travay, ak li fè inferans pi vit (pa gradient tracking = mwens kalkil ak mwens memwa). requires_grad = False 3.5 - Retire tèt la nan klasizasyon an Arkitektur nan ResNet50 sanble tankou sa a: Input image (224×224×3) ↓ [Convolutional layers] — learn visual features ↓ [Average Pooling] — compress spatial dimensions → 2048-dim vector ↓ [Fully Connected layer] — classify into 1000 ImageNet categories ↓ Output (1000 probabilities) Nou vle vèktè a 2048-dimansyon soti nan Average Pooling layer - sa a se embedding nou an. Nou pa vle nan dènye a plen konekte layer, paske li se espesifik nan ImageNet a 1000 klas (kouman, chat, avyon...) ak inutil pou travay nou an. feature_extractor = torch.nn.Sequential(*list(resnet.children())[:-1]) Ki sa ki fè liy sa a: Retire tout layers nan ResNet kòm yon lis. pran tout layers san yo pa yon sèl dènye (la FC layer). Retounen yo tounen nan yon modèl. resnet.children() [:-1] Sequential(*) feature_extractor.eval() mòd desann drop-out ak sèvi ak estatistik kouri pou normalization batch. Sa a asire modèl la bay pwodiksyon - menm imaj la toujou pwodwi menm embedding. eval() deterministic device = torch.device("cuda" if torch.cuda.is_available() else "cpu") feature_extractor = feature_extractor.to(device) print(f"Feature extractor ready on {device}") Sèvi ak GPU (si disponib) fè ekstraksyon karakteristik apeprè 10x pi vit. 3.6 - fonksyon ekstraksyon Koulye a, nou pral ekri yon fonksyon ki manje batay imaj atravè ekstraksyon karakteristik ak kolekte embeddings yo. Nou pral bati li pati pa pati. estrikti a ekstèn: def extract_embeddings(dataloader, model, device): """ Feed all images through the model and collect embeddings. Returns: embeddings: numpy array, shape (n_images, 2048) labels: numpy array, shape (n_images,) — -1 if unlabeled """ all_embeddings = [] all_labels = [] Nou akumule rezilta nan lis paske nou pwosesis imaj nan batch (32 nan yon sèl fwa), pa tout nan yon sèl fwa (pa ka mete nan memwa GPU). Konpayi prensipal la: with torch.no_grad(): for batch_images, batch_labels in dataloader: batch_images = batch_images.to(device) features = model(batch_images) pèmèt tracking gradient - esansyèl paske nou se sèlman fè inferans, pa fòmasyon. Sa a sèlman koupe konsomasyon memwa nan mwatye ak akselere bagay yo. deplase imaj yo nan GPU a si nou gen youn. torch.no_grad() batch_images.to(device) pwodiksyon an nan Ki gen fòm — de dènye dimansyon yo se rezidans espesyalis soti nan poto a mwayèn. Nou bezwen squeeze yo: model(batch_images) (batch_size, 2048, 1, 1) features = features.squeeze(-1).squeeze(-1) # Now shape is (batch_size, 2048) — that's our embedding Finalman, nou deplase rezilta yo tounen nan CPU a (numpy pa ka travay ak tensors GPU) ak sove yo: all_embeddings.append(features.cpu().numpy()) all_labels.append(batch_labels.numpy()) return np.concatenate(all_embeddings), np.concatenate(all_labels) Klike tout batch yo ansanm nan yon sèl array. np.concatenate 3.7 - Kòmanse ekstraksyon print("Extracting embeddings for labeled images...") labeled_embeddings, labeled_labels_arr = extract_embeddings( labeled_loader, feature_extractor, device ) print(f" Shape: {labeled_embeddings.shape}") # Expected: (200, 2048) 200 imaj, chak reprezante pa yon vektè 2048-dimansyon. Sa a se yon kompresyon 73x konpare ak pixel brik (150,528 → 2,048) - ak reprezantan kompresyon se Pifò vle di. Far nan print("Extracting embeddings for unlabeled images...") unlabeled_embeddings, _ = extract_embeddings( unlabeled_loader, feature_extractor, device ) print(f" Shape: {unlabeled_embeddings.shape}") # Expected: (9800, 2048) Nou retire etikèt yo (sa yo tout -1 nan tout fason) ak Varyab _ 3.8 - Save embeddings (pa re-ekstraksyon chak fwa!) Ekstraksyon karakteristik se etap la ki pi chè - potansyèlman 30+ minit sou yon GPU pou 10,000 imaj. Save rezilta yo se konsa ou pa gen okenn bezwen redo li: np.save("data/labeled_embeddings.npy", labeled_embeddings) np.save("data/labeled_labels.npy", labeled_labels_arr) np.save("data/unlabeled_embeddings.npy", unlabeled_embeddings) print("Embeddings saved to disk") Apre sa, ou ka recharge imedyatman ak . np.load("data/labeled_embeddings.npy") 3.9 — Sanity tcheke: se entegre yo rezonab? Premyèman kontinye, yon verifikasyon rapid. Depo nan, Depo soti - nou pral asire w ke entegre nou yo san danje: print(f"Embedding statistics:") print(f" Mean: {labeled_embeddings.mean():.4f}") print(f" Std: {labeled_embeddings.std():.4f}") print(f" Min: {labeled_embeddings.min():.4f}") print(f" Max: {labeled_embeddings.max():.4f}") print(f" NaN: {np.isnan(labeled_embeddings).any()}") print(f" Inf: {np.isinf(labeled_embeddings).any()}") Ki sa yo espere: mete alantou 0.3-0.5, std alantou 0.5-1.0, pa gen okenn NaN, pa gen okenn Inf. Si ou wè valè nan NaN, yon imaj korozyon pwobableman glise nan etap la netwaye. Si medya a se eksakteman 0, gen yon bagay ki ba ak normalizasyon an. Vwayaje plis: Vwayaje plis: Transfòmasyon aprantisaj eksplike (Tutorial PyTorch) — the original architecture that introduced skip connections ResNet papye (He et al., 2015) Ekstraksyon karakteristik vs fini-tuning — Stanford CS231n note kou Transfòmasyon aprantisaj eksplike (Tutorial PyTorch) ResNet papye (He et al., 2015) Ekstraksyon karakteristik vs fine-tuning Pwodwi pou Telefòn Pwodwi pou Telefòn Pwodwi pou Telefòn Pwodwi pou Telefòn Pwodwi pou Telefòn Pwodwi pou Telefòn 4.1 - Ki sa kouvri fè ak poukisa nou bezwen li Koulye a, nou gen 10,000 embeddings - rezime nimewo kompak nan chak imaj. 200 nan yo gen etikèt. Anplis 9,800 pa. Objektif nou an se jwenn natirèl nan done ki espere koresponn ak "normal" ak "defekt". groupings Si entegre nou yo bon (ak entegre ResNet50 anjeneral se), imaj nan menm kalite a pral nan espas entegre. Sifas nòmal yo pral kouvri ansanm; sifas defekt yo pral kouvri ansanm. Menm san yo pa etikèt, estrikti a se gen - kouvri revele li. Kòmanse Men, an premye, yon pwoblèm pratik: 2048 dimansyon se pa posib yo vizualize ak fè kèk algorithms ralanti. Nou bezwen diminye dimansyon an premye. 4.2 - estandaize entwodiksyon yo Algorithms clustering, espesyalman K-Means, kalkil distans ant pwen. Si yon dimansyon varye soti nan 0 a 1000 ak yon lòt soti nan 0 a 0.01, premye a pral konplètman domine distans la - tankou si dezyèm-dimansyon pa egziste. Standardizasyon (mean=0, std=1 pou chak dimansyon) mete tout karakteristik sou menm pousantaj. from sklearn.preprocessing import StandardScaler # Combine labeled + unlabeled for joint standardization all_embeddings = np.concatenate([labeled_embeddings, unlabeled_embeddings], axis=0) scaler = StandardScaler() all_embeddings_scaled = scaler.fit_transform(all_embeddings) Poukisa estandaje etikèt ak san etikèt ansanm? Paske yo soti nan distribisyon an menm (fabrika menm, kamera menm). estandaje yo ansanm asire konstan skalasyon. # Split back — we'll need them separate later labeled_scaled = all_embeddings_scaled[:len(labeled_embeddings)] unlabeled_scaled = all_embeddings_scaled[len(labeled_embeddings):] print(f"After standardization: mean={all_embeddings_scaled.mean():.4f}, " f"std={all_embeddings_scaled.std():.4f}") 4.3 — Reduce dimansyon ak PCA PCA (Principal Component Analysis) jwenn direksyon yo nan maksimòm varyans nan done yo ak pwojè sou direksyon yo pi wo. Nou ale soti nan 2048 nan 50 dimansyon: from sklearn.decomposition import PCA pca = PCA(n_components=50, random_state=42) all_pca = pca.fit_transform(all_embeddings_scaled) print(f"PCA: 2048 → 50 dimensions") print(f"Variance retained: {pca.explained_variance_ratio_.sum():.1%}") ~95% varyans retabli vle di ke nou te retire sèlman 5% nan enfòmasyon an, men diminye dimensionalite pa 40x. Sa a fè t-SNE ak DBSCAN anpil pi vit ak plis ki estab. Epitou, pèmèt nou vizyèlize ki jan chak eleman kontribye: plt.figure(figsize=(10, 4)) plt.plot(np.cumsum(pca.explained_variance_ratio_), marker="o", markersize=3) plt.xlabel("Number of PCA components") plt.ylabel("Cumulative explained variance") plt.title("PCA: How many components do we need?") plt.axhline(y=0.95, color="r", linestyle="--", label="95% threshold") plt.legend() plt.grid(True, alpha=0.3) plt.tight_layout() plt.savefig("outputs/pca_variance.png", dpi=150) plt.show() Sa a "plot louvri" montre kote ajoute plis konpozan sove bay ogmantasyon enpòtan. Si koub la flattens anvan 50 konpozan, ou ta ka itilize menm mwens. 4.4 — Visualize ak t-SNE t-SNE se yon teknik rediksyon dimensionalite ki pa liy ki fèt espesyalman pou vizyalizasyon. Li konsève : imaj ki fèmen nan espas segondè-dimansyon yo pral fèmen nan plon an 2D. Sa a fè li pafè pou tcheke si imaj nòmal ak defekt natirèlman separe. local structure Yon adrès enpòtan: t-SNE distorsyon distans mondyal - espas la ant klavye yo pa vle di. Sèvi ak li sèlman pou vizyalizasyon, ak klavye sou orijinal la (oswa PCA-reduced) embeddings. never cluster on t-SNE output. from sklearn.manifold import TSNE # Apply t-SNE on PCA output (faster and more stable than on raw 2048-dim) tsne = TSNE(n_components=2, random_state=42, perplexity=30, n_iter=1000) all_tsne = tsne.fit_transform(all_pca) nan paramèt souvan kontwole "dimansyon kominote" - ki kantite pwen pwovens t-SNE konsidere. 30 se yon default rezonab pou seri done nan gwosè nou an. perplexity Koulye a, nou pral divize koordinasyon t-SNE: labeled_tsne = all_tsne[:len(labeled_embeddings)] unlabeled_tsne = all_tsne[len(labeled_embeddings):] epi yo vizualize: fig, axes = plt.subplots(1, 2, figsize=(14, 6)) # Left plot: labeled images only, colored by true label for cls_idx, cls_name, color in [(0, "Normal", "#2ecc71"), (1, "Defect", "#e74c3c")]: mask = labeled_labels_arr == cls_idx axes[0].scatter(labeled_tsne[mask, 0], labeled_tsne[mask, 1], label=cls_name, alpha=0.7, s=40, c=color) axes[0].set_title("t-SNE: Labeled Images (True Labels)") axes[0].legend() Si ou wè de nwaj diferan nan pwosesis sa a - gri sou yon bò, rouge sou yon lòt - sa a se yon sinyal ekselan. Sa vle di embeddings ResNet50 reyèlman capture diferans ki genyen ant sifas nòmal ak defekte. # Right plot: all images (unlabeled in gray, labeled overlaid) axes[1].scatter(unlabeled_tsne[:, 0], unlabeled_tsne[:, 1], c="lightgray", alpha=0.2, s=10, label="Unlabeled") for cls_idx, cls_name, color in [(0, "Normal", "#2ecc71"), (1, "Defect", "#e74c3c")]: mask = labeled_labels_arr == cls_idx axes[1].scatter(labeled_tsne[mask, 0], labeled_tsne[mask, 1], label=f"Labeled: {cls_name}", alpha=0.8, s=40, c=color) axes[1].set_title("t-SNE: All Images (Labeled in Color)") axes[1].legend() plt.tight_layout() plt.savefig("outputs/tsne_visualization.png", dpi=150) plt.show() Nan plon an dwat, nwaj gri a (images san etikèt) ta dwe kouvri ak pwen koulè. Sa a konfime ke imaj etikèt ak san etikèt vini nan distribisyon an menm - yon kondisyon nesesè pou semi-supervised aprantisaj travay. 4.5 - K-ki vle di klasman K-Means se algorithm clustering ki pi senp ak ki pi lajman itilize. Li divize done yo nan egzak k gwoup pa iteratif alokasyon chak pwen nan sant la cluster ki pi pre, Lè sa a, ajou sant yo. Kòm nou konnen nou gen 2 klas (normal ak defekt), nou kòmanse ak k = 2. Men, nou tou tès k = 3, 4, 5 yo tcheke si done yo ka gen plis estrikti (pou egzanp, diferan nan defè ki fòme gwoup separe). tipik Pou evalye ki jan byen clusters yo koresponn ak etikèt yo reyèl, nou itilize . ARI = 1.0 vle di konpatibilite pafè ak etikèt yo reyèl. ARI = 0.0 vle di kouvèti aleksyonèl. ARI < 0 vle di pi mal pase aleksyonèl. ARI (Adjusted Rand Index) from sklearn.cluster import KMeans from sklearn.metrics import adjusted_rand_score, silhouette_score print("K-Means Clustering:") print(f" {'k':<5s} {'ARI':>8s} {'Silhouette':>12s}") print(f" {'-'*27}") for k in [2, 3, 4, 5]: kmeans = KMeans(n_clusters=k, random_state=42, n_init=10) all_clusters = kmeans.fit_predict(all_embeddings_scaled) # ARI: compare clusters vs true labels (on labeled images only) labeled_clusters = all_clusters[:len(labeled_embeddings)] ari = adjusted_rand_score(labeled_labels_arr, labeled_clusters) # Silhouette: internal quality measure (no labels needed) # How well-separated are the clusters? Range: -1 to +1 sil = silhouette_score(all_embeddings_scaled, all_clusters) print(f" {k:<5d} {ari:>8.4f} {sil:>12.4f}") nan paramèt vle di K-Means pral kouri 10 fwa ak diferan initializations alegan ak kenbe rezilta a pi bon. Sa a evite antre nan yon minimòm lokal mal. n_init=10 Si k=2 bay pi wo a ARI, ki konfime done nou an gen de gwoup natirèl ki aliye ak nòmal vs defekt. 4.6 — Clustering DBSCAN (ak yon apwòch alternatif) DBSCAN travay trè diferan de K-Means. Anplis de sa, nan lòd yo spesifye kantite klavye, ou spesifye de paramèt: eps (epsilon): distans maksimòm ant de pwen pou yo dwe konsidere kòm granmoun yo. Pense nan li kòm "ki jan tou pre se ase?" min_samples: nimewo minimòm nan pwen ki nesesè yo fòme yon rejyon dense (ak yon kouch). Pense li kòm "ki jan kouch yon kominote bezwen yo dwe konekte kòm yon kouch?" DBSCAN otomatikman detèmine kantite klavye ak identifye outliers (pwen ki pa fè pati nan nenpòt klavye - etikèt kòm -1). Sa a ka itil pou jwenn imaj inòmal ki ta ka mal etikèt oswa anomali. from sklearn.cluster import DBSCAN print("\nDBSCAN Clustering:") print(f" {'eps':<6s} {'min_s':<7s} {'clusters':>9s} {'noise':>7s} {'ARI':>8s}") print(f" {'-'*40}") Nou bezwen tès konbinezon paramèt miltip paske valè yo "fè" depann sou done yo: for eps in [3.0, 5.0, 7.0, 10.0]: for min_samples in [5, 10, 20]: dbscan = DBSCAN(eps=eps, min_samples=min_samples) db_clusters = dbscan.fit_predict(all_pca) # Use PCA-reduced data n_clusters = len(set(db_clusters)) - (1 if -1 in db_clusters else 0) n_noise = (db_clusters == -1).sum() if n_clusters >= 2: labeled_db = db_clusters[:len(labeled_embeddings)] mask = labeled_db != -1 # Exclude noise points from ARI if mask.sum() > 10: ari = adjusted_rand_score(labeled_labels_arr[mask], labeled_db[mask]) print(f" {eps:<6.1f} {min_samples:<7d} {n_clusters:>9d} " f"{n_noise:>7d} {ari:>8.4f}") Remake ke nou itilize PCA-reduced done ( DBSCAN batay nan dimansyon trè wo paske tout distans yo vin menm jan an (la "malon nan dimansyonèlite"). all_pca Konpare pi bon ARI soti nan DBSCAN ak pi bon nan K-Means, epi chwazi genyen an. 4.7 — Vizualization nan klavye yo sou t-SNE plot la Tanpri, wè ki jan pi bon klasman an sanble sou t-SNE vizyalizasyon nou an: best_kmeans = KMeans(n_clusters=2, random_state=42, n_init=10) all_cluster_ids = best_kmeans.fit_predict(all_embeddings_scaled) fig, ax = plt.subplots(figsize=(8, 6)) scatter = ax.scatter(all_tsne[:, 0], all_tsne[:, 1], c=all_cluster_ids, cmap="coolwarm", alpha=0.4, s=15) ax.set_title("K-Means Clusters (k=2) on t-SNE") plt.colorbar(scatter, label="Cluster ID") plt.tight_layout() plt.savefig("outputs/kmeans_clusters_tsne.png", dpi=150) plt.show() Si de koulè nan pwosesis sa a apeprè konpare ak de gwoup ou te wè nan pwosesis la etikèt t-SNE, clustering la ap travay. 4.8 - Atribye pseudo-etikèt nan imaj san etikèt Koulye a, etap la enpòtan: nou pran atansyon cluster yo ak trete yo kòm "etikèt slab" pou imaj yo san etikèt. Men, gen yon subtilite - K-Means bay id cluster arbitre. Cluster 0 ka korespondan ak "defekte" oswa "normal." Nou bezwen tcheke. Ekstraksyon nan pseudo-etikèt yo: unlabeled_pseudo_labels = all_cluster_ids[len(labeled_embeddings):] Tcheke alignment ak etikèt reyèl: labeled_cluster_ids = all_cluster_ids[:len(labeled_embeddings)] # What fraction of labeled images in cluster 0 are actually "normal"? cluster_0_normal_rate = (labeled_labels_arr[labeled_cluster_ids == 0] == 0).mean() cluster_1_normal_rate = (labeled_labels_arr[labeled_cluster_ids == 1] == 0).mean() print(f"Cluster 0: {cluster_0_normal_rate:.1%} of labeled images are 'normal'") print(f"Cluster 1: {cluster_1_normal_rate:.1%} of labeled images are 'normal'") Si cluster 0 se sitou defè (normal_rate < 50%), nou fli mape a: if cluster_0_normal_rate < 0.5: unlabeled_pseudo_labels = 1 - unlabeled_pseudo_labels print("Cluster IDs flipped to match convention (0=normal, 1=defect)") Jwenn yon gade nan distribisyon an: print(f"\nPseudo-label distribution:") print(f" Normal (0): {(unlabeled_pseudo_labels == 0).sum()} images") print(f" Defect (1): {(unlabeled_pseudo_labels == 1).sum()} images") Nou gen kounye a de seri done separe ak karakteristik trè diferan: Strictly labelled — 200 imaj ak reyèl etikèt ekspè. bon jan kalite segondè, ti kantite. Sa a se verite a ki baze sou nou. Mal etikèt — 9,800 imaj ak klèb ki baze sou pseudo-etikèt. Pi ba bon jan kalite (pou kèk etikèt se mal), men yon kantite masiv. Règ la lò: Yo sèvi ak objektif diferan nan etap la pwochen. never mix these two. Kontinye lekti: Kontinye lekti: K-ki vle di eksplike (scikit-learn) DBSCAN eksplike (scikit-learn) Ki jan yo li t-SNE kòrèkman (Distill) — lis esansyèl K-ki vle di eksplike (scikit-learn) DBSCAN eksplike (scikit-learn) Ki jan yo li t-SNE kòrèkman (Distill) Pwodwi pou Telefòn Pwodwi pou Telefòn Pwodwi pou Telefòn Pwodwi pou Telefòn 5.1 — Lojisyèl la nan dèyè apwòch la de faz nou an Isit la se intuisyon an. Imagine ou ap fòmasyon yon nouvo enspeksyon bon jan kalite nan faktori a: : Ou montre 9,800 foto yo ak di "Mwen sa yo se nòmal ak sa yo difisil, men mwen pa 100% sèten. "Inspektè a kòmanse fòme yon modèl mantal rès. Gen kèk nan etikèt yo erè, men modèl an jeneral - sifas nòmal yo san danje ak inifòm, sifas difisil yo gen irregularite - se sitou korektè. Apre faz sa a, inspektè a gen yon decent . Phase 1 (pre-training on pseudo-labels) Pense Intuisyon : Lè sa a, ou montre yo 200 foto ki te byen tcheke pa yon ekspè: "Sa yo definitivman nòmal, ak sa yo definitivman defekte." Enspektatè a rafine modèl mental yo - koreksyon erè nan faz la 1 ak asirans yo sou ka kantite. Phase 2 (fine-tuning on real labels) Rezilta: yon inspecteur ki te gen 10,000 imaj (konstriksyon yon entèlijans lajè) ak te pa 200 egzanp verifye pa ekspè (assurance presizyon). Nou espere enspeksyon sa a pral depase yon moun ki te sèlman wè 200 egzanp verifye. wè Kalibrasyon Pou pwouve sa a, nou kouri de eksperyans paralèl: Eksperyans A - Supervised sèlman: tren sou 200 imaj etikèt sèlman Eksperyans B - Semi-supervise: Pre-tren sou 9.800 imaj pseudo-etikèt, Lè sa a, fini-tune sou 200 imaj etikèt Yon sèl diferans se si modèl la wè done yo ki pa etikèt oswa pa. 5.2 — Konstriksyon an nan klasifikatè a: arsitektur Nou itilize ResNet50 kòm yon eskilti ankò, men sa a fwa nou final la kouch ak yon klasifikasyon binè ak nou li (pa diferan de pati 3, kote nou jis ekstrè karakteristik). replace train import torch.nn as nn class DefectClassifier(nn.Module): """ Binary classifier: Normal (0) vs Defect (1). Based on ResNet50 with a custom classification head. """ def __init__(self, dropout_rate=0.5): super().__init__() self.backbone = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1) num_features = self.backbone.fc.in_features # 2048 Isit la nou ranplase orijinal ImageNet klasifikasyon tèt (2048 → 1000 klas) ak pwòp nou an: self.backbone.fc = nn.Sequential( nn.Dropout(p=dropout_rate), # Anti-overfitting nn.Linear(num_features, 1), # Binary output ) def forward(self, x): return self.backbone(x) Ki jan ? Avèk sèlman 200 imaj etikèt ak 25 milyon paramèt, overfitting se menas la prensipal la. Dropout aleatorily disable 50% nan neurons pandan chak etap fòmasyon, forcing a rezo yo aprann reprezantasyon redondant. Nan tan inference, tout neurons yo aktif. Sa a se teknik an reglemantasyon ki pi efikas pou seri ti done. Dropout(0.5) Ki jan Pou klasifikasyon an dezyèm, yon sèl neuron pwodiksyon ak yon aktivasyon sigmoid se mathematikman ekivalan ak de neuron ak softmax, men pi senp ak yon ti kras plis numerikman estab. Linear(2048, 1) 5.3 — Fonkksyon nan pèdi: manyen echilib la klas Anvan yo ekri sik la fòmasyon, nou diskite sou fonksyon la pèdi. Nou itilize (Binary Cross-Entropy ak Logits), ki konbine sigmoid aktivasyon ak binary cross-entropy nan yon sèl, numerikman estab operasyon. BCEWithLogitsLoss Yon adisyon enpòtan se : pos_weight pos_weight = torch.tensor([4.0]).to(device) criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight) Ki sa fè Li di fonksyon la pèdi: "Se yon defèk manke (false negatif) ta dwe penalize plis pase yon alèt false (false pozitif)." Sa a kompense echilib la klas. San yo pa li, modèl la ta ka jwenn 80% akòz toujou prezante "normal" - ki se inutil. pos_weight=4.0 4 times more Valè a 4.0 se yon estime enpòtan ki baze sou rapò klas la. Si ou gen 80% nòmal / 20% defèk, Lè sa a, Ou ka ajiste valè sa a, men 4.0 se yon bon pousantaj kòmanse. pos_weight = 80/20 = 4.0 5.4 — Optimizer: AdamW ak pèdi pwa import torch.optim as optim optimizer = optim.AdamW(model.parameters(), lr=lr, weight_decay=1e-4) Èske AdamW? Li se Adam ak pèdi pwa apwopriye (L2 regilasyon). byen penalize pwa gwo, ki se yon lòt kouch nan pwoteksyon kont overfitting. panse li kòm di modèl la "prefere eksplike pi senp." weight_decay=1e-4 5.5 - Kalandriye nan vitès aprantisaj scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience=3, factor=0.5) Sa a otomatikman diminye vitès la nan aprantisaj lè pèdi validasyon ap rete amelyore. Li vle di "atake 3 epòk san yo pa amelyorasyon anvan redwi." vle di "multipliye vitès la aprantisaj pa 0.5." Sa a se esansyèl pou konvergans - kòm modèl la avanse yon minimòm, etap ti yo anpeche overshooting. patience=3 factor=0.5 5.6 — Lyen fòmasyon: yon epòk nan yon fwa Koulye a, nou pral bati fonksyon an fòmasyon plen. Nou pral ale nan chak pati nan sik la separe. (yon epòk - yon pase nan tout done fòmasyon): The training phase from sklearn.metrics import f1_score def train_model(model, train_loader, val_loader, epochs, lr, device, phase_name=""): """Train the model and track validation F1 score.""" pos_weight = torch.tensor([4.0]).to(device) criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight) optimizer = optim.AdamW(model.parameters(), lr=lr, weight_decay=1e-4) scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience=3, factor=0.5) best_f1 = 0 for epoch in range(epochs): # ---- TRAINING ---- model.train() # Enable dropout, update batch norm stats for images, labels in train_loader: images = images.to(device) labels = labels.float().unsqueeze(1).to(device) # .float() because BCEWithLogitsLoss expects float targets # .unsqueeze(1) adds a dimension: shape (batch,) → (batch, 1) optimizer.zero_grad() # Reset gradients from previous batch outputs = model(images) # Forward pass loss = criterion(outputs, labels) # Compute loss loss.backward() # Compute gradients (backpropagation) optimizer.step() # Update weights Chak batch pase atravè sik la klasik: avanse pase → pèdi òdinatè → backpropagate gradients → ajou pwa → reset gradients pou batch pwochen. (Pou chak etap nan fòmasyon): The validation phase # ---- VALIDATION ---- model.eval() # Disable dropout, use fixed batch norm stats all_preds, all_true = [], [] val_loss_total = 0 with torch.no_grad(): # No gradients needed for evaluation for images, labels in val_loader: images = images.to(device) outputs = model(images) # Compute validation loss val_loss_total += criterion( outputs, labels.float().unsqueeze(1).to(device) ).item() # Convert raw logits → binary predictions # sigmoid maps logits to [0, 1], then threshold at 0.5 preds = (torch.sigmoid(outputs) >= 0.5).int().cpu().numpy().flatten() all_preds.extend(preds) all_true.extend(labels.numpy()) Remake nan sa a se enpòtan: li deaktivize droppout (toute neuron yo aktif) ak sèvi ak estatistik la kouri pou normalizasyon batch anvan estatistik batch. San yo pa li, metrik validasyon ou ta dwe bruyant ak enpòtan. model.eval() Track metrics and update scheduler: val_f1 = f1_score(all_true, all_preds, average="binary") scheduler.step(val_loss_total) # Reduce LR if loss plateaued if val_f1 > best_f1: best_f1 = val_f1 if (epoch + 1) % 5 == 0: print(f" [{phase_name}] Epoch {epoch+1}/{epochs}: val_f1={val_f1:.4f}") print(f" [{phase_name}] Best F1: {best_f1:.4f}") return best_f1 Nou retire nan F1 atravè tout epòk, pa sèlman dènye a. Modèl yo souvan piki anvan fen an nan fòmasyon an (ki apre sa yo yo ka anpeche yon ti kras). best 5.7 - Prepare divizyon done yo Nou divize done yo etikèt nan tren (70%) ak tès (30%). : li pa janm itilize pou fòmasyon nan nenpòt eksperyans. Stratifikasyon asire ratio a klas se konsève nan tou de divizyon. sacred from sklearn.model_selection import train_test_split labeled_train_idx, labeled_test_idx = train_test_split( range(len(labeled_paths)), test_size=0.3, random_state=42, stratify=labeled_labels, ) Kreye twa dataset nou bezwen: # 1. Labeled training data (for supervised training + fine-tuning) train_labeled_ds = MetalSurfaceDataset( [labeled_paths[i] for i in labeled_train_idx], [labeled_labels[i] for i in labeled_train_idx], preprocessing, ) # 2. Test data (for evaluation only — NEVER used for training) test_ds = MetalSurfaceDataset( [labeled_paths[i] for i in labeled_test_idx], [labeled_labels[i] for i in labeled_test_idx], preprocessing, ) # 3. Weakly labeled data (pseudo-labels from clustering) weakly_labeled_ds = MetalSurfaceDataset( unlabeled_paths, unlabeled_pseudo_labels.tolist(), preprocessing, ) Sou DataLoaders: train_labeled_loader = DataLoader(train_labeled_ds, batch_size=16, shuffle=True) test_loader = DataLoader(test_ds, batch_size=16, shuffle=False) weakly_labeled_loader = DataLoader(weakly_labeled_ds, batch_size=32, shuffle=True) print(f"Train (labeled): {len(train_labeled_ds)} images") print(f"Test: {len(test_ds)} images") print(f"Weakly labeled: {len(weakly_labeled_ds)} images") Remake diferan gwosè batch yo: 16 pou seri ki gen etikèt ti (fwa imaj pou chak epòk), 32 pou seri ki gen etikèt ti gwo (pwodiksyon pi vit). Tou de itilizasyon pou fòmasyon pou anpeche modèl la aprann lòd echantiyon yo. shuffle=True 5.8 — Eksperyans A: Supervised sèlman (baseline) Isit la se eksperyans la pi senp. Nou fòme yon modèl nouvo lè l sèvi avèk ONLY a 140 imaj fòmasyon etikèt (ki lòt 60 yo resève pou tès). Sa a se pèfòmans nou pral jwenn san yo pa semi-supervised aprantisaj. print("=" * 60) print("EXPERIMENT A: SUPERVISED ONLY (140 labeled images)") print("=" * 60) model_supervised = DefectClassifier(dropout_rate=0.5).to(device) f1_supervised = train_model( model_supervised, train_labeled_loader, test_loader, epochs=30, lr=1e-4, device=device, phase_name="Supervised" ) 5.9 — Eksperyans B: semi-supervise (apwòch la de faz) Koulye a, pipeline a konplè. Etap 1 bay modèl la yon entèlijans lajè soti nan 9.800 imaj pseudo-etikèt. Etap 2 afiche li ak 140 etikèt reyèl. Phase 1 — Pre-training on weakly labeled data: print("\n" + "=" * 60) print("EXPERIMENT B: SEMI-SUPERVISED") print("=" * 60) model_semi = DefectClassifier(dropout_rate=0.5).to(device) print("\nPhase 1: Pre-train on pseudo-labeled data (9,800 images)...") train_model( model_semi, weakly_labeled_loader, test_loader, epochs=10, lr=1e-4, device=device, phase_name="Pre-train" ) Nou sèlman fòmasyon pou 10 epòk isit la paske pseudo-etikèt yo soumèt. Antrenman pou tan sou etikèt soumèt pral ranfòse erè yo. Phase 2 — Fine-tuning on strongly labeled data: print("\nPhase 2: Fine-tune on real labeled data (140 images)...") f1_semi = train_model( model_semi, train_labeled_loader, test_loader, epochs=20, lr=5e-5, device=device, phase_name="Fine-tune" ) Remake nan (5e-5 vs 1e-4 nan faz la 1). Sa a se deliberatè ak kritik. Si nou sèvi ak yon vitès aprantisaj segondè pandan ajisteman fin, modèl la pral byen vit "oubli" tout sa li te aprann pandan prentan an - gradients yo ta dwe trè gwo epi yo ta dwe overwrite pwa yo prentan aprantisaj. Yon vitès aprantisaj dou pèmèt modèl la fè koreksyon ti kras nan konesans egziste li yo, kenbe modèl yo lajè soti nan faz la 1 pandan y ap fikse erè yo ak etikèt reyèl. lower learning rate Li se menm jan ak enspektatè a faktori: ou pa kòmanse fòmasyon yo soti nan zèb la nan faz la 2. Ou senpleman korekte mal konsèp yo pandan y ap kenbe entèlijans yo an jeneral. 5.10 — Final evalyasyon: moman nan verite Koulye a, nou evalye tou de modèl sou menm seri tès ak plizyè metrik. Premye, fonksyon an evalyasyon: from sklearn.metrics import roc_auc_score, classification_report def full_evaluation(model, test_loader, device, name): """ Evaluate on the test set. Returns F1 score and AUC-ROC. """ model.eval() all_preds, all_probs, all_true = [], [], [] with torch.no_grad(): for images, labels in test_loader: outputs = model(images.to(device)) probs = torch.sigmoid(outputs).cpu().numpy().flatten() all_probs.extend(probs) all_preds.extend((probs >= 0.5).astype(int)) all_true.extend(labels.numpy()) Nou kolekte tou de (pou AUC-ROC, ki mesye bon jan kalite ranking) ak (pou F1, ki medye bon jan kalite klasifikasyon nan limit 0.5): probabilities binary predictions f1 = f1_score(all_true, all_preds, average="binary") auc = roc_auc_score(all_true, all_probs) print(f"\n{name}:") print(f" F1 Score: {f1:.4f}") print(f" AUC-ROC: {auc:.4f}") print(classification_report( all_true, all_preds, target_names=["Normal", "Defect"] )) return f1, auc Ki jan ak pa presizyon? Paske ak klas ki pa balans, presizyon se enkyetid. Yon modèl ki toujou prezante "normal" resevwa 80% presizyon men 0% rekòmande sou defè. F1 se medya a harmoni nan presizyon ak rekòmande - li penalize modèl ki ignore klas la minoritè. F1 Ki jan Li mesye ki jan bon modèl la imaj (imaj defektè ta dwe jwenn pi wo chans pase yo nòmal), malgre nan prag klasman an. Yon AUC nan 1.0 vle di ranpli pafè; 0.5 vle di alegan. AUC-ROC Ranje Koulye a, konpare nan: f1_sup, auc_sup = full_evaluation( model_supervised, test_loader, device, "SUPERVISED ONLY" ) f1_semi, auc_semi = full_evaluation( model_semi, test_loader, device, "SEMI-SUPERVISED" ) Yon verdiktè final: print("=" * 60) print("FINAL COMPARISON") print("=" * 60) print(f" {'Metric':<12s} {'Supervised':>12s} {'Semi-supervised':>16s} {'Delta':>8s}") print(f" {'-'*50}") print(f" {'F1':<12s} {f1_sup:>12.4f} {f1_semi:>16.4f} {f1_semi - f1_sup:>+8.4f}") print(f" {'AUC-ROC':<12s} {auc_sup:>12.4f} {auc_semi:>16.4f} {auc_semi - auc_sup:>+8.4f}") Si kolòn la Delta montre nimewo pozitif, nou te pwouve ke done yo ki pa etikèt te itil. Pseudo-etikèt yo, malgre ke yo pafè, te bay modèl la yon tèt kòmanse ke supervizyon pur sou 200 imaj pa ka korespondan. 5.11 - Interprete rezilta yo Isit la se ki jan yo li konpare: F1 amelyore pa +0.05 oswa plis: genyen klè pou semi-supervised. Done ki pa etikèt bay siyal enteresan. F1 amelyore pa +0.01 nan +0.04: modere amelyorasyon. Semi-supervised ede men marye a se ti. Konsidere amelyore bon jan kalite a kouvri oswa sèvi ak pi sofistike pseudo-labeling. F1 san chanjman oswa pi mal: pseudo-etikèt yo te trè bruyant pou ede, oswa kouvèti a pa capture estrikti a reyèl. Tès ekstrè karakteristik diferan, algorithms kouvèti diferan, oswa prag konfyans pi wo pou pseudo-etikèt. Kontinye lekti: Kontinye lekti: Pseudo-etikèl papye (Lee, 2013) - metòd la orijinal Semi-supervise aprantisaj aprantisaj (van Engelen & Hoos) - yon revizyon konplè nan metòd PyTorch fòmasyon loop pratik yo pi bon Pèsonèl etikèt papye (Lee, 2013) Semi-surveillance aprantisaj (van Engelen & Hoos) PyTorch fòmasyon loop pratik yo pi bon Pandan 6 - Scaling nan milyon de imaj: yon Roadmap reyèl Yon kesyon nan biznis la "Proof ou nan konsèp travay sou 10,000 imaj. Nou gen 4 milyon imaj pou pwosesis. Èske nou ka anpeche pipeline sa a ak yon bije nan € 5,000?" Sa a se yon kesyon ou pral kwè nan nenpòt pwojè reyèl. Eseye li nan onètman. Pwodwi pou Telefòn se boutèy la. Sou 10,000 imaj nou an ak yon sèl GPU, li te pran apeprè 30 minit. Scaling lineyèman: Feature extraction 4,000,000 images ÷ 10,000 images × 30 min = 12,000 min = 200 GPU-hours Nan ~€2/ora pou yon instans GPU nwaj (Azure NC-seri ak yon T4 oswa A10 GPU), sa a se sou . €400 tou bezwen adapte. Standards K-Means chaje tout done nan memwa a pou kalkil distans. Avèk embeddings 4M nan 2048 dimansyon (chak yon 4 byte float): Clustering 4,000,000 × 2,048 × 4 bytes = ~32 GB just for the embeddings Li pa pral mete nan RAM sou pi fò machin yo. Solisyon: sèvi ak soti nan scikit-learn, ki pwosesis done nan bwat (ke di) 10,000 echantiyon nan yon fwa. Rezilta a menm, fraksyon nan memwa a. MiniBatchKMeans Pre-trenman sou 4M imaj pseudo-etikèlman pran sou 50 èdtan GPU . CNN training €100 Pwodwi pou magazen Raw imaj: 4M × ~50 KB mwayèn = Embeddings: 4M × 2048 × 4 byte = Sou Azure Blob Storage nan ~€0.02/GB / mwa, sa a se sou . 200 GB 32 GB €5/month Estrategi nan etikèt Si 200 etikèt pa ase nan mas, nou ta ka etikèt plis. Nan ~ € 1 pou chak imaj (ki gen ladan kontwòl bon jan kalite), 2,000 etikèt plis ta koute Men, gen yon apwòch pi entelijan: . €2,000 active learning Aktiv aprantisaj pèmèt modèl la chwazi imaj yo etikèt. Anplis de seleksyon aleksyonèl la nan 2,000 imaj, modèl la identifye moun ki li se pi enkyetid sou - imaj yo ki ta pral aprann li pi. Sa a tipikman mande pou 3-4x mwens etikèt yo jwenn menm amelyorasyon pèfòmans. Ki avèk aprantisaj aktif, nou ka sèlman bezwen 500 etikèt adisyonèl olye pou 2,000 . €500 Total anviwònman an Feature extraction (GPU): €400 CNN training (GPU): €100 Storage (year 1): €60 Additional labeling: €500 – €2,000 ────────────────────────────────────── TOTAL: €1,060 – €2,560 Bon nan budgèt la 5.000 €, ak espas pou eksperyans ak re-rele. 5 kondisyon pou siksè Sèvi ak nwaj GPUs, pa lojisyèl lokal. Rente pa èdtan, peye sèlman pou sa ou itilize. Sèvi ak MiniBatchKMeans nan plas nan KMeans regilye. menm bon jan kalite, 100x mwens memwa. Kreye yon pipeline done apwopriye ak pwosesis batch. Pa janm chaje imaj 4M nan RAM nan yon sèl fwa. Sèvi ak PyTorch DataLoader ak num_workers > 0 pou chaje paralèl. to maximize the value of every human-labeled image. Each label should be selected strategically, not randomly. Consider active learning Store ak vèsyon embeddings, pa sèlman imaj brik. Re-extraction nan 4M embeddings koute € 400; loading nan embedings sove pa koute anyen. Kontinye lekti: Kontinye lekti: MiniBatchKMeans (scikit-learn) — how to label smarter, not more Active learning overview MiniBatchKMeans (Sikit-learn) nan Aktiv aprantisaj konvèsyon Konklisyon Semi-supervise aprantisaj se pa majik - li se enjenyè. Ou pran estrikti a kache nan done san etikèt (pa embeddings ak clustering), konvèti li nan etikèt apwopriye, epi sèvi ak yo bay modèl la sipèvize ou yon kòmanse tèt. Done a san etikèt pa ranplase etikèt reyèl - li ranplase yo. Tanpri resize tout tiyo a nou te bati: Eksplorasyon - Nou te tcheke 10,000 imaj pou koripsyon, fòma inkonsistan, ak balans klas. Nou tcheke done yo ak pwòp tèt nou yo. Preprocessing - Nou estandaje chak imaj nan fòma ResNet50 espere: 224×224, RGB, CLAHE-enhanced, ImageNet-normalized. Ekstraksyon karakteristik - Nou te itilize yon ResNet50 pretrained konvèti chak imaj nan yon 2048-dimansyon embedding ki retire esans visual li yo. Clustering - Nou aplike K-Means ak DBSCAN yo grupye imaj san etikèt nan gwoup, Lè sa a, bay pseudo-etikèt ki baze sou manm nan gwoup. Semi-supervise fòmasyon - Nou pre-traved yon CNN sou 9,800 imaj pseudo-label, Lè sa a, fini-touye sou 200 etikèt reyèl, ak konpare ak yon baz sèlman supervised. Analiz Scaling - Nou estime kalkile, magazen, ak etikèt pri pou 4M imaj, konfime viabilite nan yon budgèt nan € 5,000. Klike sou takeaways: Yon CNN pretrained ka ekstrè karakteristik enteresan soti nan nenpòt domèn imaj, menm yon nan ki li pa te janm fòme sou. Clustering sou embeddings revele gwoupaj natirèl ki souvan korespondan ak klas reyèl. Pseudo-etikèl yo imperfè, men yon modèl pre-traved sou etikèt imperfè epi Lè sa a fine-tune sou etikèt reyèl depann sou yon modèl fòme sèlman sou etikèt reyèl. Epi abord la semi-supervised se pi valè kounye a lè etikèt yo rilaks ak chè - ki se situa a ou pral fè fas nan pi pwojè reyèl. Modèl la travay atravè domèn: medikal imaj, kontwòl bon jan kalite endistriyèl, imaj satelit, klasifikasyon dokiman, ak monitoring biodiversity. Tout kote etikèt yo chè ak done san etikèt gen anpil - ki, nan 2025, se prèske tout kote.