Table of Contents კონტაქტის მაგიდა შეტყობინებები: Labels are expensive, images are free ნაწილი 1 — მონაცემთა აღჭურვილობა: გაიგეთ მონაცემები, რომ თქვენ მუშაობს Part 2 — Preprocessing: განიხილეთ მოდელის ენაზე ნაწილი 3 — ფუნქციონირების ექსტრაქცია: სურათების გადარჩენა მნიშვნელოვანი ნომრები 4 ნაწილი — არასამთავრობო კრისტრიზებული კრისტრიზაცია: შავი სტრუქტურების აღმოაჩინება Part 5 - semi-Supervised სასწავლო: ძირითადი ექსპერიმენტი ნაწილი 6 — შეზღუდვა მილიონი სურათები: რეალური გზა რუკა კონტაქტი Labels are expensive, სურათები უფასო Perfect World- ში, თქვენს მონაცემთა კომპლექტიში თითოეულ სურათს შეიცავს ბეჭდვა. "ბედლეული". "გორმალური". "ბედლეული ტიპის A." "ბედლეული ტიპის B." მაგრამ რეალური მსოფლიოში, ბეჭდვა ძალიან ძვირადღირებულია. ერთ-ერთი სამედიცინო radiologist შეიძლება ბეჭდვა, ალბათ, 50 ტვინის სკანები საათში - € 200 / საათში. სამრეწველო ხარისხის ინსპექტორი შეუძლია ბეჭდვა, ალბათ, 100 ნაწილები საათში. სტანდარტში, ბეჭდვა 100,000 სურათები შეიძლება ღირს მეტი, ვიდრე მოდელის სასწავლო. აქ არის პრაქტიკა: კომპანიებს ხშირად აქვს მილიონი სურათები (კამერები, სენსორები, მომხმარებლის ჩამოტვირთვა), მაგრამ შეუძლიათ მხოლოდ შეუზღუდავი რაოდენობა. comes in. Instead of throwing away the unlabeled images, we use them to improve the model — combining a small labeled set with a large unlabeled set. semi-supervised learning გთხოვთ გამოიყენოთ ანალიგია. ვფიქრობ, რომ თქვენ ხართ კლასიკური 30 სტუდენტთან ერთად. თქვენ ყველა მათთვის ექსპერიმენტი, მაგრამ თქვენ მხოლოდ გაქვთ დრო, რომ რეიტინგი 5 დოკუმენტები. თქვენ რეიტინგი მათ 5 მკაცრად, და თქვენ შეამოწმოთ ნიმუშები: სტუდენტები, რომლებიც წაიკითხა ბევრი ხშირად მიიღოს მაღალი დოკუმენტები, და სტუდენტები, რომლებიც წაიკითხა თეთრი პასუხები ხშირად მიიღოს დაბალი დოკუმენტები. გამოყენებით ეს ნიმუშები, თქვენ შეგიძლიათ შეფასოთ დოკუმენტები სხვა 25 დოკუმენტები გარეშე წაიკითხეთ თითოეული ხაზი. ეს არის semi-surveilled სწავლება: თქვენ გამოიყენებთ რამდენიმე რეიტინგირებული დოკუმენტები ეს სტატია აშენებს სრული semi-supervised სურათის classification pipeline from scratch. We’ll work through every step with detailed explanations as we go. ქარხანა აწარმოებს ფოლადის ფურცელი, და კამერები სურათებს თითოეული ფურცელი, როგორც იგი rolls off წარმოების ხაზი. ყველაზე ფურცელი არის ნორმალური, ზოგიერთი აქვს შეცდომები (კრიბები, ფარები, pitting, ინტელსიები). ჩვენ გვაქვს 10,000 სურათები, მაგრამ მხოლოდ 200 labeled ones. Case სტუდენტები : ქარხანა აწარმოებს ფოლადის ფურცელი, და კამერა ჩაწერებს თითოეული ფურცელი, როგორც იგი rolls off საწარმოო ხაზი. ყველაზე ფურცელი არის ნორმალური, ზოგიერთი შეიცავს ხმაები (კრიბები, ფურცელი, pitting, შედუღებებს). ჩვენ გვაქვს 10,000 სურათები, მაგრამ მხოლოდ 200 labeled. detecting manufacturing defects on metal surfaces. შექმნა Semi-Supervised Learning Pipeline განიხილეთ, თუ როგორ უნდა გააკეთოთ ამ კოდს, შეამოწმეთ მთელი მილის ხაზს. ადრე განიხილეთ, თუ როგორ უნდა გააკეთოთ ამ კოდს, თითოეულ ნაბიჯს გულისხმობს, ვიდრე ნაცნობიერად. გთხოვთ გაიგოთ ეს დიაგრამა - თითოეული კურსი არის ნაბიჯ, რომელიც ჩვენ გააკეთებთ: 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] ძირითადი მიმოხილვა: ჩვენ გადაიხადეთ არტაქტირებული სურათები განიხილეთ ეს, როგორც გაძლევთ სტუდენტს მკურნალობის მიმოხილვა ადრე რეალური ექსპერიმენტი - ეს არ იქნება სრულყოფილი, მაგრამ ეს უკეთესი, ვიდრე არაფერი. დაახლოებით გაითვალისწინეთ ასევე, თუ როგორ უნდა გამოიყენოთ ამ სტატიაში, რადგან სიტყვების შეუზღუდავება ძალიან პოპულარული წყაროა: Strongly labelled ( ან უბრალოდ "labelled"): სურათები ბეჭდვა შეამოწმებული ადამიანის ექსპერტი. ოქროს სტანდარტი. ჩვენ გვაქვს 200 მათ შორის. რკინიგზირებული (ან "პსესოო-ტენინიგზირებული"): სურათები, რომლებიც რკინიგზირებული იყო კრისტრაციის მიერ. იაფი, მაგრამ უფრო რკინიგზირებული. ჩვენ შექმნათ 9,800 ეს. Unlabelled: სურათები არ აქვს label. ეს არის მათი სტანდარტი, სანამ ჩვენ cluster მათ. შეფუთვა: კომპიუტერული ნომერი შეტყობინება სურათს, რომელიც წარმოადგენს წინასწარ ტრენინგი ნეიროური ქსელის. ჩვენი ძირითადი ინსტრუმენტი სურათების შედარებით. დამატებითი წაიკითხვა: დამატებითი წაიკითხვა: Semi-supervised learning მიმოხილვა — scikit-learn Google- ის კვლევის შესახებ semi-supervised learning Semi-supervised learning მიმოხილვა — scikit-learn Google- ის კვლევის შესახებ semi-supervised learning 1. Data Exploration: ცოდნა მონაცემები თქვენ მუშაობს რატომ უნდა შეამოწმოთ თქვენი მონაცემები, სანამ არაფერი გააკეთებთ სურათების მონაცემთა კომპლექტი აქვს უნიკალური ცუდი რეჟიმები, რომ თქვენ არ იპოვებთ ტაბლეული მონაცემებს: გაფართოებული ფაილი, რომ crash your training loop at 3 AM, შეუზღუდავი რეზოლუცია, რომ silently distort თქვენი სურათები, ცუდი ფერი კანელები (გრიზზკალი disguised როგორც RGB), და უამრავი კლასის შეზღუდვა, სადაც 95% სურათების არის "გორმალური." ფოლადის წესები : ვხედავ, რაც ჩვენ გააკეთებთ. never trust data you haven't inspected. 1.1 — ატვირთვა და შეფასება სურათები ჩვენი მონაცემთა კომპლექტი შეიცავს ორი ძირითადი პლატფორმა: ერთ-ერთი შეიცავს labeled სურათები (გამთავრებულია "normal" და "defect" subfolders), და ერთ-ერთი შეიცავს არ labeled სურათები (არ არ არის subfolders - მხოლოდ ფართო კოლექცია ფაილი .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" ჩვენ ვიყენებთ და არა string concatenation, რადგან იგი მართავს path separators სწრაფად ნებისმიერი ოპერაციული სისტემა. ახლა წაიკითხეთ: 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%}") ეს label ratio არის ჩვეულებრივ დაახლოებით 2%. მხოლოდ 2% ჩვენი მონაცემები აქვს ექსპერტი-პრედიტირებული label. სხვა 98% არის goldmine ჩვენ არ შეგვიძლია გაქირავდეს, რომ შეუზღუდოთ - და ეს არის მხოლოდ ის, რაც semi-supervised სასწავლო გამოიყენება. 1.2 — სინათლის პრობლემები: რეზოლუცია, ფერი, კონტროლი შემდეგი, ჩვენ უნდა შეამოწმოთ თითოეულ სურათს პირდაპირი. ერთ-ერთი გაფართოებული ფაილი შეიძლება შეუწყოს მთელი ტრენინგი. შეუზღუდავი რეზოლუცია შეუზღუდავი სურათებს შეუზღუდავი იქნება, თუ თქვენ არ ხართ შეუზღუდავი. ჩვენ დააწკაპუნეთ მცირე ფუნქცია, რომელიც ეძებს თითოეული სურათი და შეამოწმოთ მისი თვისებები: 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, } ეს სფეროში განსაკუთრებით მნიშვნელოვანია. ნიშნავს 3 ფერადი ხაზები (რავი, ლურჯი, ლურჯი). განიხილება, რომ ეს არის 1 კანელი (Grey Scale). ჩვენ ვფიქრობ, რომ ჩვენი წინასწარ მოწინავე მოდელი RGB, ასე რომ ჩვენ უნდა კონვერტაცია ყველაფერი შემდეგ. img.mode 'RGB' 'L' 'RGBA' ახლა შეამოწმეთ ყველა 10,000 სურათები: all_files = labeled_files + unlabeled_files image_info = [get_image_info(f) for f in all_files] info_df = pd.DataFrame(image_info) და შეამოწმეთ შედეგებს: 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}") რა უნდა ეძებოდეს საწყისი: 0: გააუმჯობესეთ მათ დაუყოვნებლივ. მაშინაც კი ერთი ცუდი ფაილი შეიძლება შეუწყოს თქვენი სასწავლო. სხვადასხვა რეზოლუცია: თუ min ≠ max ფართობი ან სიმაღლე, სურათები განსხვავებული ზომები. ჩვენ გადაიხადოს მათ ყველა 224×224 მომზადების დროს. Multiple Colour Modes: თუ თქვენ ხედავთ ორივე "RGB" და "L", თქვენ გაქვთ შეფუთვა ფერი და შავი ზომის. ჩვენ კონვერტაცია ყველაფერი RGB. Extreme ფაილი ზომა: 1KB ფაილი შეიძლება იყოს ღია ან გაფართოებული. 50MB ფაილი შეიძლება იყოს შეკუმშული – ღირს კვლევა. გაუმჯობესებთ გაუმჯობესებული ფაილი: 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 — კლასის გადაზიდვა: არის ჩვენი labeled კომპლექტი შეესაბამებული? სამრეწველო და სამედიცინო კონფიგურაციაში, ცუდია. თქვენი labeled კომპლექტი შეიძლება იყოს 90% "დაზმივი" და 10% "დაზმივი". ეს ძალიან მნიშვნელოვანია: ლამაზი მოდელი, რომელიც ყოველთვის პროგნოზი "დაზმივი" მიიღებს 90% სიზუსტით, ხოლო სრულიად უარყოფითი. ჩვენ უნდა იცოდეთ ბალანსი წინასწარ, ასე რომ ჩვენ შეგვიძლია შეესაბამება მას შემდეგ. 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}%)") თუ შეკაბამება მნიშვნელოვანია, ჩვენ გააკეთა მას შემდეგ, გამოყენებით ტექნოლოგია, რომელიც გამოიყენება ფუნქცია დაკარგვა - ძირითადად ვთქვა, რომ მოდელი "არღეთ შეცდომა არის 4x უკეთესი, ვიდრე ფსიქიკური Alarm". pos_weight ჩვენ ასევე ვიზუალიზებთ: 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 — ვიზუალიზაცია ნიმუში სურათები: ყოველთვის იხილეთ, სანამ თქვენ მოდელი ეს შეიძლება იყოს ყველაზე მნიშვნელოვანი ნაბიჯი მთელი pipeline. შეამოწმოთ თქვენი მონაცემები. თქვენ შეიძლება აღმოაჩინოთ სურათები, რომლებიც ჩვეულებრივ შეცდომატიზებული, scanning არქიტექტები (შავი ხაზები, rotations), ხარისხის პრობლემები (შავილი, overerexposure), ან, რომ შეცდომები ვიზიტურად სინამდვილეა და თქვენი საქმიანობა უფრო რთული, ვიდრე გსურთ. 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() შეამოწმოთ ამ სურათებს. შეიძლება თუ არა, მოდელი ასევე შეხვდება. თუ შეცდომები ჩვეულებრივ არიან (ხვეწილი ჭურჭელი, დიდი ჭურჭელი), ეს არის მოწინავე - მოდელი უნდა იყოს შეუძლიათ გაიგოთ განსხვავება. თუ ისინი სინათლისა (ხვეწილი ცვლილება, hairline crack), თქვენ უნდა განსაკუთრებით კარგი წინასწარ დამუშავება და ფუნქციური ექსტრაქცია. თქვენ დამატებითი წაიკითხვა: დამატებითი წაიკითხვა: Pillow დოკუმენტაცია — Python სურათების ბიბლიოთეკა, რომ ჩვენ გამოიყენებთ სურათების ჩამოტვირთვა NEU Surface Defect Database — რეალური მსოფლიოს ფოლადის ზედაპირზე მოთხოვნების მონაცემთა სერტიფიკაცია, რომელიც შეგიძლიათ გააკეთოთ Pillow დოკუმენტი New Surface Defect მონაცემთა ბაზა Part 2 — Preprocessing: განიხილეთ მოდელის ენაზე Why We Can't Just Feed Raw Images to a Neural Network / რატომ არ შეგვიძლია მხოლოდ ნედლეულის ქსელი Pre-trained CNN, როგორიცაა ResNet50, ტრენინგი იყო ძალიან სპეციფიკური ტიპის input: 224×224 Pixel სურათები, RGB ფერი, ნორმალურიზებული კონკრეტული საშუალო და სტანდარტული deviation ღირებულებები დააკმაყოფილია ImageNet მონაცემთა კომპლექტი. თუ თქვენ feed მას სურათები სხვადასხვა ზომის, ან ნედლეული ვიდრე ნორმალურიზებული სურათების ღირებულებები, ფუნქციები იგი ატვირთვა არ არის მნიშვნელობა. ResNet50 "მუბნება ImageNet." თუ გსურთ, რომ ის განიხილოს ჩვენი ლითონის ზედაპირზე სურათები, ჩვენ უნდა "შემოთ" მათ ImageNet ფორმატში პირველი. ეს გადამცემი მოიცავს ოთხი ნაბიჯები: კონვერტაცია RGB (3 ხაზები) კონტაქტის გაუმჯობესება histogram equalization მეშვეობით ზომა 224×224 რედაქტირება Pixel ღირებულება შეესაბამება ImageNet სტატისტიკა 2.1 – რა არის histogram equalization, და რატომ ეს მნიშვნელოვანია აქ? ინდუსტრიული სურათები ხშირად აქვს ძალიან დაბალი კონტაქტი. განსხვავება ჩვეულებრივი ზედაპირზე და scratched ერთი შეიძლება იყოს მხოლოდ რამდენიმე Pixel ინტენსივობის დონეზე - უახლესი ღრმად, და ძალიან რთული მოდელი იპოვოს. ჩვენ ვცდილობთ, რომ ჩვენ ვცდილობთ, რომ ჩვენ ვცდილობთ, რომ ჩვენ ვცდილობთ, რომ ჩვენ ვცდილობთ, რომ ჩვენ ვცდილობთ, რომ ჩვენ ვცდილობთ, რომ ჩვენ ვცდილობთ, რომ ჩვენ ვართ. Histogram equalization ჩვენ ვიყენებთ უფრო გაფართოებული ვერსია, რომელიც გამოიყენება (Contrast Limited Adaptive Histogram Equalization). Global equalization- ის განსხვავებით, რომელიც იგივე ტრანსპორტირება იყენებს მთელი სურათი, CLAHE- ს სურათს მცირე ფურცელი (8x8 default) და თითოეული ფურცელი დამოუკიდებლად შეესაბამებს. ეს გააუმჯობესებს ადგილობრივი დეტალებს. CLAHE აქ არის ანალიგია: ვფიქრობ, რომ თქვენ განკუთვნილია სიჩქარე ფოტოში. Global equalization არის, როგორც გამოიყენოთ ერთი სიჩქარე slider მთელი სურათი - თქვენ შეიძლება გააუმჯობესოთ შავი კუთხეები, მაგრამ გაუმჯობესოს დასაწყისში ნათელი ცენტრი. CLAHE არის, როგორც განკუთვნილია სიჩქარე თითოეულ რეგიონში დამოუკიდებლად, ასე რომ ყველა ნაწილი სურათის გააუმჯობესება. 2.2 — შექმნა Custom PyTorch მონაცემთა კომპლექტი PyTorch ორგანიზებს მონაცემთა დატვირთვა ორი კლასის გარშემო: (სინახავს, თუ როგორ უნდა დატვირთოთ ერთი ელემენტი) და (სინახავს, თუ როგორ უნდა დააყენოთ პორტატები და shuffle) PyTorch უზრუნველყოფს ინტეგრირებული ჩვენი მონაცემთა კომპლექტი შეიცავს ორივე labeled და unlabelled სურათებს, ასე რომ ჩვენ გვჭირდება მორგებული კლასის. Dataset DataLoader ImageFolder დასაწყისში დასაწყისში დასაწყისში დასაწყისში დასაწყისი: 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 აცხადებს, რა სურათები ჩვენ გვაქვს. შეინახავს სურათების გზა და (სასურველია) მათი ბეჭდვა. __len__ __init__ ახლა ძირითადი მეთოდები, , რომელიც ჩამოტვირთვა და წინასწარ დამუშავება ერთი სურათი. ჩვენ გაზიარეთ იგი სამი ნაბიჯებში: __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") რატომ – იმიტომ, რომ ResNet50 მოითხოვს 3 კანონებს. თუ ჩვენი სურათი გრიკულა (1 კანონით), ეს გრიკულ ღირებულებები R, G და B. თუ ეს უკვე RGB, ეს არაფერი გააკეთებს. თუ ეს RGBA (4 კანონით), ეს იღებს ალფა კანონით. .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) რატომ LAB და არა უბრალოდ გამოიყენოთ CLAHE პირდაპირი RGB? რადგან თუ თქვენ შეესაბამება R, G, და B ქანები პირდაპირი, თქვენ გაბორცავს ფერები – შეცვალოს Blue Green, მაგალითად. მუშაობა LAB სფეროში, ჩვენ მხოლოდ შეკუმშოთ Lightness (L) ქანები და გაქვთ ფერები (A, B) არ შეკუმშული. ეს არის სტანდარტული პრაქტიკა სურათების დამუშავება. 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 — კონვერტაციის მილები: რა გავაკეთებს თითოეული ნაბიჯი ახლა ჩვენ განკუთვნილია ტრანსპორტირების სერვისი. თითოეული აქვს კონკრეტული მიზნით: 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], ), ]) გაითვალისწინეთ თითოეული ნაბიჯი: ResNet50- ის არქიტექტურა მოითხოვს ეს ზომა. თუ თქვენ გადაცემთ 300×400 სურათს, tensor ფორმები არ შეესაბამება და PyTorch შეჩერება. Resizing შეიძლება მოკრილებით შეკრილოთ ფუნქციონირება, მაგრამ მასშტაბით დაფუძნებული საქმიანობისთვის (მაგ. შეკრილების აღჭურვილობა) ეს არ არის პრობლემა. (1) Resize to 224×224. გააკეთებს ორი რამ: კონვერტებს Pixel ფორმატში HWC (High × Width × Channels) to CHW (Channels × Height × Width), რომელიც არის, რაც PyTorch მოითხოვს; და შეზღუდვა Pixel ღირებულებები [0, 255] ჩვეულებრივ [0.0, 1.0] floats. (2) ToTensor. ეს მექანიკური რაოდენობა - და — არის საშუალო და სტანდარტული მოთხოვნები Pixel ღირებულება მთელი ImageNet მონაცემთა კომპლექტი, კომპიუტერული kanaal-by-kanaal (R, G, B). ResNet50 უკვე ტრენინგი ამ სწორი ნორმალურიზაციის ღირებულება, ასე რომ მისი შიდა წონა "ჩვეულება" ინტენსიები დაახლოებით 0 ერთად ერთეული მოთხოვნები. გამოყენება სხვადასხვა ნომრები იქნება, როგორც ზომები inches, მაგრამ კომპიუტერული სინთამეტრი - მექანიკური დაშორებით. (3) Normalize with ImageNet mean and std. [0.485, 0.456, 0.406] [0.229, 0.224, 0.225] 2.4 - შექმნა მონაცემთა კომპლექტი და DataLoaders ახლა ჩვენ შეესაბამებთ ყველაფერი. ძირითადი პრინციპი: მათ შეფუთვა შეფუთვა ჩვენი შეფასება. labeled and unlabeled data must be kept strictly separate at all times. პირველი, კოლექციონირებული სურათების გზა და მათი კლასის label: 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 შემდეგი, შეინახეთ არ ატვირთული სურათების გზა (მე არ გჭირდებათ ატვირთვა): unlabeled_paths = [str(fp) for fp in unlabeled_files] 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") საბოლოოდ, შეფუთვა მათ DataLoaders. DataLoader შეფუთვა სურათები ერთად (batch_size=32 ნიშნავს 32 სურათები ერთხელ) და optionally shuffles მათ: labeled_loader = DataLoader(labeled_dataset, batch_size=32, shuffle=False) unlabeled_loader = DataLoader(unlabeled_dataset, batch_size=32, shuffle=False) რატომ იმიტომ, რომ ჩვენ მივიღებთ ფუნქციების ექსტრაქტი, და ჩვენ გჭირდებათ შეფუთვა, რომ შეესაბამება, როგორც ჩვენი ფაილი სიაები. ჩვენ shuffle later, დროს სასწავლო. shuffle=False დამატებითი წაიკითხვა: დამატებითი წაიკითხვა: ტურისტული ტურისტული ტურისტები — სრული სია CLAHE გააჩნია (OpenCV tutorial) PyTorch Dataset & DataLoader მიმოხილვა ტურისტული ტურისტული ტურისტები — სრული სია CLAHE გააჩნია (OpenCV tutorial) PyTorch Dataset & DataLoader მიმოხილვა ნაწილი 3 — ფუნქციონალური ექსტრაქცია: სურათების გადარჩენა მნიშვნელობის ნომრები 3.1 — რატომ მშრალი Pixels არის საოცარი რეპუტაცია 224×224 RGB სურათი აქვს 150,528 ნომერი (224 × 224 × 3 ქანები). ყველაზე მათ შორის არის ხმაური — მცირე მოვლენები სინამდვილეში, სინამდვილეში, კომბინაცია სინამდვილეში. უკეთესი: ორი სურათები იგივე სიჩქარით, ნაცვლად განსხვავებული კუთხედან ან სინამდვილეში, აქვს მთლიანად განსხვავებული Pixel ღირებულებები. თუ ჩვენ ცდილობენ კლასიკური ან სინამდვილეში სინამდვილეში სინამდვილეში, სურათები, რომლებიც ჩვენთვის იგივე ვხედავთ, ალგორტმაში მთლიანად განსხვავდება. რა უნდა გავაკეთოთ, ეს არის რეპუტაცია, რომელიც მოპოვებს სურათი - "მე ხედავთ, როგორც scratch," "ეს არის გლუვი ზედაპირზე" - კომპიუტერული, სტაბილური ნომერი ფორმით. გააკეთეთ meaning embeddings 3.2 — რა არის წინასწარ მოდელი და რატომ ჩვენ არ ტრენინგი დასაწყისში ჩვენ გვაქვს 200 ნარკოტიკული სურათები. თუ ჩვენ შეამოწმეთ ResNet50- ის 25 მილიონი პარამეტრები 200 სურათებში, მოდელი ყველა სასწავლო სურათს სრულად შეინახავს, მაგრამ ახალი სურათებს მთლიანად არ შეუწყობს. . overfitting გარდა ამისა, ჩვენ გამოიყენებთ მოდელი, რომელიც ImageNet- ზე უკვე სასწავლოდა – 14 მილიონი სურათების მონტაჟი 1000 კატეგორიაში (კონები, კაცი, მანქანები, შენობები და ა.შ.). ეს მოდელი უკვე იპოვებს ძირითადი ვირტუალური თვისებები: ხაზები, ტექსტურები, ფორმები, ფერი გრიდიანტები, geometric მოდელები. — ისინი გამოიყენება ფოლადის ზედაპირზე, ისევე, როგორც ისინი გამოიყენება კაცი. universal ვფიქრობ, რომ ეს არის, როგორიცაა გაქირავება გამოცდილი ფოტოგომა, რათა შეამოწმოს თქვენი ქარხანა. ისინი არ ვხედავთ ფოლადის ფურცელი ადრე, მაგრამ ისინი უკვე იცით, თუ როგორ უნდა : ისინი შეგვიძლია აღჭურვილობა უნიკალური ტექსტურები, მძიმე ცვლილებები ზედაპირზე ხარისხის, ნიმუშები, რომ შეცვალოს სტანდარტი. ისინი უბრალოდ უნდა იცოდეთ, რა არის "ფუნქციური" თქვენი კონკრეტული ქარხანა. ეს 3.3 — Loading ResNet50 დააჭირეთ წინასწარ მოდელი: import torchvision.models as models resnet = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1) ამ ერთი ხაზი ჩამოტვირთვა (და პირველი დროს) ResNet50 მოდელი, whose წონა უკვე სასწავლოდა ImageNet. მოდელი იცის, თუ როგორ დააკმაყოფილოს 1,000 კატეგორიები ყოველდღიური მოვლენები. 3.4 — შეზღუდვა პარამეტრები ჩვენ არ გსურთ შეცვალოთ ნებისმიერი ცოდნა ფუნქციებს. ჩვენ გამოიყენებთ ResNet როგორც მხოლოდ წაიკითხვის ინსტრუმენტი: for param in resnet.parameters(): param.requires_grad = False ეს აქვს ორი უპირატესობა: ეს თავიდან ავიცილოთ ნაცვლად შეცვალოს წინასწარ მოწინავე წონა, და ეს იძლევა შემდეგი უფრო სწრაფად (არ არ არის ნაცვლად წაიკითხვა = ნაკლებად კომპიუტერები და ნაკლებად მეხსიერება). requires_grad = False 3.5 — კლასიკური სახურავი გათავისუფლება ResNet50- ის არქიტექტურა აჩვენებს: 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) ჩვენ გსურთ 2048-dimensional vektor from the Average Pooling layer — ეს არის ჩვენი შეფუთვა. ჩვენ არ გსურთ ბოლო სრულიად დაკავშირებული layer, რადგან ეს არის სპეციფიკური ImageNet- ის 1000 კლასები (კმა, კაცი, თვითმფრინავი ...) და უარყოფითი ჩვენი სამუშაო. feature_extractor = torch.nn.Sequential(*list(resnet.children())[:-1]) რა არის ეს ხაზი: დასაწყისში დასაწყისში დასაწყისში დასაწყისში დასაწყისში ყველა კლასები, ვიდრე ბოლო კლასის (FC კლასის). დასაწყისში გადაიხადეთ ეს მოდელი. resnet.children() [:-1] Sequential(*) feature_extractor.eval() მოდელი შეუწყობს drop-out და იყენებს გაშვებული სტატისტიკა ბარიის სტანდარტიზაციისთვის. ეს უზრუნველყოფს, რომ მოდელი იძლევა ამავე სურათი ყოველთვის აწარმოებს იგივე შეფუთვა. 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}") გამოყენებით GPU (თუ ხელმისაწვდომია) ხდის ფუნქციონალური ექსტრაქცია დაახლოებით 10x სწრაფად. 3.6 – ექსტრაქციული ფუნქცია ახლა წაიკითხეთ ფუნქციას, რომელიც ხელს უწყობს სურათებს ფუნქციონალური ექსტრაქტორის მეშვეობით და შეავსებს შეფუთვა. ჩვენ აშენებთ ეს ნაწილაკს. გარე სტრუქტურა : 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 = [] ჩვენ შეავსებთ შედეგებს სიაში, რადგან ჩვენ გადამუშავებთ სურათებს ბარიშებში (32 ერთხელ), არა ყველა ერთხელ (გურთ არ შეესაბამება GPU მექანიზმი). ძირითადი loop : with torch.no_grad(): for batch_images, batch_labels in dataloader: batch_images = batch_images.to(device) features = model(batch_images) დახურვა gradient tracking – მნიშვნელოვანია, რადგან ჩვენ მხოლოდ გააკეთებთ შეტყობინება, არა ტრენინგი. ეს მხოლოდ შეამციროს მეხსიერების გამოყენება და გააუმჯობესებს ყველაფერი. გადამცემა სურათები GPU, თუ ჩვენ გვაქვს ერთი. torch.no_grad() batch_images.to(device) წარმოება ფორმირება — ბოლო ორი დიამეტრები სფეროტური ნაცვლადები საშუალო შეერთებული. ჩვენ უნდა შეკუმშოთ მათ: model(batch_images) (batch_size, 2048, 1, 1) features = features.squeeze(-1).squeeze(-1) # Now shape is (batch_size, 2048) — that's our embedding საბოლოოდ, ჩვენ გადამცემთ შედეგებს CPU- ში (numpy ვერ მუშაობს GPU tensors) და შენახვა მათ: all_embeddings.append(features.cpu().numpy()) all_labels.append(batch_labels.numpy()) return np.concatenate(all_embeddings), np.concatenate(all_labels) ყველა კომპლექტი შეიცავს ერთი კომპლექტი. np.concatenate 3.7 — აწარმოებს ექსპლუატაცია 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 სურათები, თითოეული შედგება 2048-dimensional vektor. ეს არის 73x კომბინაცია შედარებით raw px (150,528 → 2,048) - და კომბინირებული წარმოება არის უფრო მნიშვნელოვანია დიდი print("Extracting embeddings for unlabeled images...") unlabeled_embeddings, _ = extract_embeddings( unlabeled_loader, feature_extractor, device ) print(f" Shape: {unlabeled_embeddings.shape}") # Expected: (9800, 2048) ჩვენ დატოვებთ label (ეს არის ყველა -1 anyway) ერთად ვარიანტი _ 3.8 - შეინარჩუნება შეფუთვა (ნახად არ აღჭურვილობა ყოველდღიურად!) ფუნქციონირების ექსპერიმენტი არის ყველაზე ღირებულ ნაბიჯ — პოტენციურად 30+ წუთი GPU- ზე 10,000 სურათებისთვის. შეინახეთ შედეგებს, ასე რომ თქვენ არასდროს არ უნდა გააკეთოთ: 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") მას შემდეგ, რაც თქვენ შეგიძლიათ დააყენოთ ამჟამად . np.load("data/labeled_embeddings.npy") 3.9 — Sanity check: არის შეფუთვა განიცდიან? შემდგომში, სწრაფი კონფიგურაცია. გემრიელი, გემრიელი - დარწმუნდით, რომ ჩვენი შეფუთვა სინამდვილეა: 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()}") რა უნდა მოითხოვოს: დაახლოებით 0.3-0.5, std დაახლოებით 0.5-1.0, არ NaN, არ Inf. თუ თქვენ ხედავთ NaN ღირებულებები, გაქირავებული სურათი ალბათ გაქირავებული გაქირავების ნაბიჯი. უფრო მეტი : უფრო მეტი : გადაზიდვის სასწავლო აღწერა (PyTorch tutorial) ResNet paper (He et al., 2015) — ორიგინალური არქიტექტურა, რომელიც იძლევა skip connections Feature Extraction vs fine-tuning — Stanford CS231n კურსის ნომრები გადაზიდვის სასწავლო აღწერა (PyTorch tutorial) ResNet ფაილი (He et al., 2015) ფუნქციონალური ექსტრაქცია vs fine-tuning Part 4 — უძრავი კრისტრაცია: კვლევების სტრუქტურა შავიში 4.1 – რა არის კრისტრაცია და რატომ ჩვენ უნდა ახლა ჩვენ გვაქვს 10,000 შეფუთვა - თითოეული სურათის კომპიუტერული ნომერი შეტყობინებები. მათ შორის 200 მათ შორის აქვს label. სხვა 9,800 არ არის. ჩვენი მიზანია იპოვოს ბუნებრივი იმ მონაცემებს, რომლებიც hopefully შეესაბამება "normally" და "defect". groupings ძირითადი მიმოხილვა: თუ ჩვენი შეფუთვა კარგია (და ResNet50 შეფუთვა ჩვეულებრივ არის), იგივე ტიპის სურათები იქნება ჩვეულებრივი ზედაპირები მოკლე ზედაპირები მოკლე ზედაპირები მოკლე ზედაპირები მოკლე ზედაპირები მოკლე ზედაპირები მოკლე ზედაპირები მოკლე ზედაპირები მოკლე ზედაპირები მოკლე ზედაპირები მოკლე ზედაპირები მოკლე ზედაპირები მოკლე ზედაპირები მოკლე ზედაპირები მოკლე ზედაპირები მოკლე ზედაპირები მოკლე ზედაპირები მოკლე ზედაპირები მოკლე ზედაპირები მოკლე ზედაპირები მოკლე ზედაპირები მოკლე ზედაპირები დასაწყისი მაგრამ პირველ რიგში, პრაქტიკული პრობლემა: 2048 დიამეტრები არ არის შესაძლებელი იმიტომ, რომ ზოგიერთი ალგორტატები მგრძნობიარე. ჩვენ უნდა შეამციროს დიამეტრები პირველი. 4.2 – სტანდარტაცია შეფუთვა Clustering algorithms, განსაკუთრებით K-Means, კომპიუტერები distances between points. If one dimension ranges from 0 to 1000 and another from 0 to 0.01, the first one will completely dominate the distance — as if the second dimension does not exist. სტანდარტაცია (mean=0, std=1 per dimension) დააყენებს ყველა ფუნქციები ერთგული. 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) რატომ სტანდარტებს labeled და unlabelled ერთად? იმის გამო, რომ ისინი მოდის იგივე გაფართოება (ესვე ქარხანა, იგივე კამერა). სტანდარტაცია მათ ერთად უზრუნველყოფს კონფიდენციალურობის scaling. # 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 — შეამციროს ზომები PCA PCA (Principal Component Analysis) იპოვებს მაქსიმალური განსხვავების მიმართულებები მონაცემებში და პროექტებს უმაღლესი მიმართულებით. ჩვენ წავიდეთ 2048-დან 50 დონეზე: 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% ვარიანტი შეინარჩუნებული იმას ნიშნავს, რომ ჩვენ მხოლოდ 5% მონაცემების შეშფოთებული, მაგრამ შემცირებული dimensionality 40x. ეს იძლევა t-SNE და DBSCAN უფრო სწრაფად და უფრო სტაბილური. ვხედავ, თუ რამდენად თითოეული კომპონენტს შეესაბამება: 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() ამ "გარბო პლატფორმა" აჩვენებს, სადაც მეტი კომპონენტების დაამატვა შეჩერებს მნიშვნელოვანი მოგება. თუ კრემა 50 კომპონენტების წინ შეჩერდება, თქვენ შეიძლება გამოიყენოთ კიდევ ნაკლებად. 4.4 — Visualize ერთად t-SNE t-SNE არის nonlinear dimensionality reduction ტექნიკა, რომელიც სპეციალურად განკუთვნილია ვიზუალიზაციისთვის. : images that are close in high-dimensional space will be close in the 2D plot. This makes it perfect for checking whether normal and defect images naturally separate. local structure ერთი მნიშვნელოვანი შეტყობინება: T-SNE გლობალური დაშორება – კლასის შორის სივრცე არ არის მნიშვნელობა. გამოიყენეთ იგი მხოლოდ ვიზუალიზაციისთვის, და კლასის ორიგინალური (ან PCA-reduced) შეფუთვა. 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) ეს პარამეტრი დაახლოებით კონტროლებს "კონტაქტო ზომა" — რა დაახლოებით ზომები t-SNE განიხილავს. 30 არის გონივრული ნომერი მონაცემთა კომპლექტი ჩვენი ზომის. perplexity ახლა გაზიარეთ t-SNE კონდიტორები: labeled_tsne = all_tsne[:len(labeled_embeddings)] unlabeled_tsne = all_tsne[len(labeled_embeddings):] და ვიზუალური: 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() If you see two distinct clouds in this plot — green on one side, red on the other — that's an excellent sign. It means the ResNet50 embeddings genuinely capture the difference between normal and defective surfaces. # 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() ზუსტად ხართ, რომ ფერადი სურათები და ფერადი სურათები მოიცავს იგივე დისტრიბუციას, რაც აუცილებელია semi-supervised learning- ის მუშაობისთვის. 4.5 — K-Means კლასიკური კლასიკა K-Means არის ყველაზე მარტივი და პოპულარულად გამოიყენება კლასისზაციის ალგორტიმი. იგი გაზიარებს მონაცემებს სიზუსტით k ჯგუფიებში, ითერატული გაზიარებით თითოეული პუნქტი უახლოეს კლასის ცენტრიზე, შემდეგ განახლება ცენტრი. მას შემდეგ, რაც ჩვენ ვიცით, რომ ჩვენ გვაქვს 2 კლასები (გორმალური და ცუდი), ჩვენ დაიწყებთ k = 2. მაგრამ ჩვენ ასევე ტესტირება k = 3, 4, 5 რათა შეამოწმოთ, თუ მონაცემები შეიძლება იყოს უფრო სტრუქტურა (გ.შ., განსხვავებული დახვეწილი კლასიკური კლასიკური კლასიკური ფორმები). ტიპი იმისათვის, რომ შეამოწმოთ, თუ რამდენად კარგად კლასები შეესაბამება რეალური label, ჩვენ გამოიყენებთ ARI = 1.0 იმას ნიშნავს, რომ ნამდვილად შეესაბამება რეალური label. ARI = 0.0 იმას ნიშნავს, რომ არანდარტული clustering. ARI < 0 იმას ნიშნავს, რომ არანდარტული არანდარტული. 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}") ეს K-Means აწარმოებს 10 წუთს სხვადასხვა ნარკოტიკების დაწყებამდე და უზრუნველყოფს საუკეთესო შედეგს. ეს თავიდან ავიცილოთ ცუდი ადგილობრივი მინიმუმში. n_init=10 თუ k=2 უზრუნველყოფს უმაღლესი ARI, ეს უზრუნველყოფს ჩვენი მონაცემები აქვს ორი ბუნებრივი ჯგუფი, რომლებიც შეესაბამება ნორმალური vs ცუდი. 4.6 — DBSCAN კრისტრირება (ანტიკური მიმოხილვა) DBSCAN მუშაობს ძალიან განსხვავებით K-Means. ვიდრე ზოგიერთი კლასის ნომერი, თქვენ დააყენებთ ორი პარამეტრი: Eps (Epsilon): მაქსიმალური სიჩქარე შორის ორი ტკივილი, რომ მათ უნდა განიხილოთ neighbors. ვფიქრობ, რომ ეს არის, როგორც "თუ ახლოს არის საკმარისი ახლოს?" Min_samples: მინიმალური რაოდენობა ნომრები, რომელიც საჭიროა, რომ შექმნათ ზრდასრული რეგიონში (კონტაჟი). ფიქრობთ, რომ ეს არის, როგორც "კონტაჟი ზრდასრული სფეროში უნდა იყოს, როგორც კონტაჟი?" DBSCAN ავტომატურად აჩვენებს კლასის ზომის და აჩვენებს outliers (პონები, რომლებიც არ მოიცავს ნებისმიერი კლასის - დაარსდა -1). ეს შეიძლება იყოს სასარგებლო იპოვოს უარყოფითი სურათები, რომლებიც შეიძლება იყოს შეცდომატიზებული ან უარყოფითი. from sklearn.cluster import DBSCAN print("\nDBSCAN Clustering:") print(f" {'eps':<6s} {'min_s':<7s} {'clusters':>9s} {'noise':>7s} {'ARI':>8s}") print(f" {'-'*40}") ჩვენ უნდა ტესტირება მრავალფეროვანი პარამეტრის კომბინაციები, რადგან "ესასწორი" ღირებულება დამოკიდებულია მონაცემებს: 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}") შეამოწმეთ, რომ ჩვენ გამოიყენებთ PCA შემცირებული მონაცემები ( DBSCAN შეხვდება ძალიან მაღალი დიამეტრები, რადგან ყველა დიამეტრები შეიცვალა ( "მომცველი დიამეტრები"). all_pca შედარებით საუკეთესო ARI from DBSCAN ერთად საუკეთესო K-Means, და აირჩიეთ მოგზაურობა. 4.7 — Clusters ვიზუალიზაცია t-SNE პლატფორმაზე იხილეთ, თუ როგორ გამოიყურება საუკეთესო კრისტრირება ჩვენი t-SNE ვიზუალაციაში: 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() If the two colors in this plot roughly match the two groups you saw in the labeled t-SNE plot, clustering მუშაობს. 4.8 — Pseudo-labels გათავისუფლების სურათები ახლა მნიშვნელოვანი ნაბიჯა: ჩვენ მიიღებთ კლასტერული განთავსება და მათთვის "ხვეწილი ტაქსები" არ ატვირთული სურათებისთვის. მაგრამ არსებობს ნედლევა - K-Means ატვირთებს კლასტერული IDs უარყოფითად. კლასტერა 0 შეიძლება შეესაბამება "ბედლეული" ან "გორმალური". ჩვენ უნდა შეამოწმოთ. გამოქვეყნდა pseudo-labels : unlabeled_pseudo_labels = all_cluster_ids[len(labeled_embeddings):] შეამოწმეთ რეალური label: 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'") If cluster 0 is mostly defects (normal_rate < 50%), ჩვენ გადაიხადოს mapping: 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)") შეამოწმოთ გაზიარება: 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") ახლა ჩვენ გვაქვს ორი განსხვავებული მონაცემთა კომპლექტი ძალიან განსხვავებული თვისებები: მძიმე ეტიკეტები — 200 სურათები რეალური ექსპერტი ეტიკეტები. მაღალი ხარისხის, მცირე რაოდენობა. ეს არის ჩვენი ძირითადი ფაქტი. რკინიგზირებული — 9800 სურათები კრისტრირებული Pseudo-labels. დაბალი ხარისხის (სოგიერთი რკინიგზირება არ არის შეცდომა), მაგრამ დიდი რაოდენობა. ფოლადის წესები : ისინი მომსახურებენ განსხვავებული მიზნებს შემდეგი ნაბიჯში. never mix these two. დამატებითი წაიკითხვა: დამატებითი წაიკითხვა: K-Means აღწერილი (scikit-learn) DBSCAN გააჩნია (scikit-learn) როგორ წაიკითხოთ t-SNE სწრაფად (Distill) — ძირითადი წაიკითხვა K-Means აღწერილი (scikit-learn) DBSCAN გააჩნია (scikit-learn) როგორ უნდა წაიკითხოთ t-SNE სწრაფად (Distill) Part 5 — semi-supervised სასწავლო: რეალური ექსპერიმენტი 5.1 — ლოგიკა ჩვენი ორი ფაზის მიმოხილვა იმიტომ, რომ თქვენ გაქვთ სასწავლო ახალი ხარისხის ინსპექტორი ქარხანა: : თქვენ აჩვენებთ მათ 9800 ფოტო და ვთქვათ, "მე ეს არის ნორმალური და ეს არის შეცდომა, მაგრამ მე არ ვარ 100% დარწმუნებული." ინპექტორი იწყება შექმნათ რთული ნიმუში. ზოგიერთი ნიმუში არ არის შეცდომა, მაგრამ საერთო ნიმუში - ნორმალური ზედაპირები გლუვი და ერთგულა, შეცდომა ზედაპირები არ არის შეცდომა - არის ძირითადად სწორი. შემდეგ ამ ეტაპზე, ინპექტორი აქვს საკმარისი . Phase 1 (pre-training on pseudo-labels) ვფიქრობ კონტაქტი : შემდეგ გთავაზობთ მათ 200 სურათებს, რომლებიც სპეციალისტს შეამოწმებულა: "თუ უნდა იყოს NORMAL და ეს უნდა იყოს DEFINITELY defective." ინსპექტორი გააუმჯობესებს მათი ფსიქიკური მოდელი - შეცდომა გაუმჯობესება ფაზის 1 და გააუმჯობესება მათი რეზოლუცია Edge შემთხვევაში. Phase 2 (fine-tuning on real labels) შედეგად: ინვესტიციონერი, რომელიც აქვს 10,000 სურათები (გამთავრებას ფართო intuition) და იყო ჩვენ ვცდილობთ, რომ ეს ინფექტორი უპირატესობთ ის, ვინც მხოლოდ 200-ზე უპირატესობთ უპირატესობთ. ნახვა კალიბრირებული იმისათვის, რომ ეს გააკეთოთ, ჩვენ გააკეთებთ ორი პლატფორმის ექსპერიმენტი: ექსპერიმენტი A — მხოლოდ მხარდაჭერით: მხოლოდ 200 მატჩები ექსპერიმენტი B — semi-supervised: pre-train on 9,800 pseudo-labelled images, შემდეგ fine-tune on 200 labeled images იგივე მოდელი არქიტექტურა, იგივე ტესტი კომპლექტი. ერთ-ერთი განსხვავება არის, თუ მოდელი ხედავს არტიკირებული მონაცემები ან არა. 5.2 — აშენება კლასიკური: არქიტექტურა ჩვენ ვიყენებთ ResNet50 როგორც backbone კიდევ ერთხელ, მაგრამ ამ დროს ჩვენ საბოლოო კლასის ერთად binary classifier და ჩვენ ეს არის (მაგალითად, ნაწილი 3, სადაც ჩვენ უბრალოდ ატვირთეთ ფუნქციები). 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 აქ ჩვენ შეცვალოს Original ImageNet classification head (2048 → 1000 კლასები) ჩვენი საკუთარი: 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) რატომ Dropout(0.5) რატომ Linear(2048, 2) helyett? ბინარი კლასიკისთვის, ერთ-ერთი გამოცემული ნეიროონი sigmoid დატაქტიურებით შეესაბამება ორი ნეირონის softmax, მაგრამ მარტივი და ცოტა უფრო ნომერიულად სტაბილური. Linear(2048, 1) 5.3 — დაკარგული ფუნქცია: კლასის შეზღუდვა დასაწყისში დასაწყისში დასაწყისში დასაწყისში დასაწყისში დასაწყისში დასაწყისში. (Binary Cross-Entropy with Logits), რომელიც შეუერთებს sigmoid და binary cross-entropy ერთ-ერთი, ნომერიულად სტაბილური ოპერაცია. BCEWithLogitsLoss ძირითადი დამატება არის : pos_weight pos_weight = torch.tensor([4.0]).to(device) criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight) რა არის ეს ნიშნავს, რომ დაკარგული ფუნქცია: "თუ დაკარგული შეცდომა (თუ ცუდი უარყოფითი) უნდა გადაიხადოს ეს შეესაბამება კლასის შეზღუდვა. გარეშე ეს, მოდელი შეიძლება მიიღოს 80% სიზუსტით, ყოველთვის პროგნოზი "დაზღუდული" - რომელიც არ არის სასარგებლო. pos_weight=4.0 4 times more 4,0- ის ღირებულება არის კლასის ურთიერთობის მიხედვით ნომერი. თუ თქვენ გაქვთ 80% ნორმალური / 20% შეცდომა, მაშინ თქვენ შეგიძლიათ შედუღოთ ეს ღირებულება, მაგრამ 4.0 არის კარგი დასაწყისში. pos_weight = 80/20 = 4.0 5.4 — ოპტიმიზერი: AdamW ერთად წონის დაკარგვა import torch.optim as optim optimizer = optim.AdamW(model.parameters(), lr=lr, weight_decay=1e-4) რატომ არის AdamW? ეს არის Adam- ის მზად წონის დაკარგვა (L2 რეგულარაცია). განიცდიან, რომ დიდი წონა, რომელიც არის კიდევ ერთი ფართო დაცვა overfitting. ვფიქრობ, რომ ეს არის, როგორც ვთქვა, რომ მოდელი "დაწვრილებით მარტივი აღწერა." weight_decay=1e-4 5.3 - სწავლის სიჩქარე scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience=3, factor=0.5) ეს ავტომატურად შეამციროს სწავლის სიჩქარე, როდესაც validation დაკარგვა არ გაუმჯობესებს. ეს იმას ნიშნავს, რომ "სავარაუდოთ 3 ეტაპები გარეშე გაუმჯობესება, სანამ შეამციროს". იმას ნიშნავს, რომ "მრბადოს სწავლის სიჩქარე 0.5." ეს მნიშვნელოვანია კონვერტაციისთვის - როგორც მოდელი მინიმალური, მცირე ნაბიჯები თავიდან ავიცილოთ აღემატება. patience=3 factor=0.5 5.6 — სასწავლო loop: ერთი ეტაპზე ერთხელ ახლა აშენებთ სრული სასწავლო ფუნქციონირება. ჩვენ განიცდიან თითოეული ნაწილს კუნძულზე განსხვავებით. (One Epoch - ერთი წავიდა ყველა სასწავლო მონაცემები): 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 კლასიკური ციკლი: forward pass → calculate loss → backpropagate gradients → update weights → reset gradients next batch. (შვეობით თითოეული სასწავლო ეტაპზე): 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()) შეამოწმოთ ეს არის მნიშვნელოვანია: იგი შეუზღუდავი droppout (ეს ყველა ნერვონები აქტიური) და გამოიყენებს გაშვებული სტატისტიკა batch სტატისტიკა, ვიდრე batch სტატისტიკა. გარეშე მას, თქვენი validation მეტრიკები იქნება ხმაური და არ არის საიმედო. 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 ჩვენ შეამოწმოთ F1 ყველა ეტაპზე, არა მხოლოდ ბოლო. მოდელები ხშირად დასრულებენ სასწავლო დასრულების წინ (და შემდეგ, რაც ისინი შეიძლება შეუზღუდავი იყოს). best 5.7 — მონაცემთა გაზიარების მომზადება ჩვენ გაზიარეთ დატვირთული მონაცემები Train (70%) და ტესტი (30%). ტესტი კომპლექტი არის : ეს არასოდეს გამოიყენება სასწავლო ნებისმიერი ექსპერიმენტი. Stratification უზრუნველყოფს კლასის ურთიერთობა შენარჩუნება ორივე splits. 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, ) შექმნათ სამი მონაცემთა კომპლექტი, რომ ჩვენ უნდა: # 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, ) და 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") შეამოწმეთ განსხვავებული სატვირთო ზომა: 16 მცირე labeled კომპლექტი (თუ ნაკლები სურათები თითოეულ ეტაპზე), 32 დიდი დაბალი labeled კომპლექტი (თუ სწრაფი დამუშავება). სასწავლო, რათა თავიდან ავიცილოთ, რომ მოდელი გაიგოს ნიმუშების სერტიფიკაცია. shuffle=True 5.8 — ექსპერიმენტი A: მხოლოდ მონიტორინგი (ბაზინო) ეს არის მარტივი ექსპერიმენტი. ჩვენ ტრენინგი ახალი მოდელი გამოყენებით მხოლოდ 140 labeled ტრენინგი სურათები (შეს სხვა 60 შეზღუდულია ტესტირება). ეს არის შესრულება ჩვენ მიიღებთ გარეშე semi-supervised სასწავლო. 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 — ექსპერიმენტი B: semi-supervised (შოტი ფაზის ხელმისაწვდომობა) ახლა სრული pipeline. ფაზა 1 უზრუნველყოფს მოდელი ფართო intuition from 9,800 pseudo-labelled images. ფაზა 2 გააუმჯობესებს იგი 140 რეალური label. 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" ) ჩვენ მხოლოდ 10 ეტაპზე სასწავლო ვართ აქ, რადგან Pseudo-labels არის ხმაური. სასწავლო ძალიან ხანგრძლივი ხმაური labels გაუმჯობესებს შეცდომებს. 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" ) შეამოწმოთ (5e-5 vs 1e-4 in phase 1). ეს არის განიცდიან და მნიშვნელოვანია. თუ ჩვენ გამოიყენებთ მაღალი სწავლის სიჩქარით fin-tuning- ის დროს, მოდელი სწრაფად "გავიწყებს" ყველაფერი, რაც ის გაქვთ pre-training- ის დროს - gradients იქნება ძალიან დიდი და გადაიხადოს pre-trained წონა. მინიმალური სწავლის სიჩქარით მოდელი საშუალებას გაძლევთ მცირე რედაქციები მისი ხელმისაწვდომი ცოდნა, შენარჩუნება ფართო მოდელები ფაზა 1 ხოლო შეცდომები რეალური label. lower learning rate ეს არის მსგავსი ქარხანა ინფექტორი: თქვენ არ დაიწყოს მათი სასწავლო დაწყებული ნაცვლად ფაზაში 2. თქვენ მშრალი შეცვალოს მათი შეცდომა, ხოლო შენარჩუნება მათი საერთო ინტუსის. 5.10 — საბოლოო შეფასება: სიტყვის დრო ახლა ჩვენ შეფასებთ ორივე მოდელები იგივე ტესტი კომპლექტი მრავალფეროვანი მეტრიკებით. პირველი, შეფასების ფუნქცია: 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()) ჩვენ ორივე მოკლე (AUC-ROC, რომელიც შეზღუდვის ხარისხის შეზღუდვა) და (F1თვის, რომელიც შეამციროს კლასიკური ხარისხი 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 რატომ და არა სიზუსტით? იმიტომ, რომ უჯრედების კლასებში სიზუსტით გაქვთ შეცდომა. მოდელი, რომელიც ყოველთვის პროგნოზიებს "გორმალური" იღებს 80% სიზუსტით, მაგრამ 0% აღიარებს სიზუსტით. F1 არის სიზუსტით და აღიარების ჰორმონიული საშუალო - ის იღებს მოდელები, რომლებიც უარყოფს მინიმალური კლასს. F1 რატომ ეს აჩვენებს, თუ რამდენად კარგად მოდელი სურათები (ფუნქციონირებული სურათები უნდა მიიღოს უმაღლესი სიზუსტით, ვიდრე ჩვეულებრივი სურათები), მიუხედავად იმისა, რომ კვალიფიკაციის გარიგება. AUC 1.0 ნიშნავს სრულყოფილი რეიტინგზე; 0.5 ნიშნავს ნდობის. AUC-ROC რელიგიები ახლა შედარებით: 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" ) და საბოლოო რეიტინგი: 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}") თუ დოლადის კოლადის ნახვა პოტენციური ნომრები, ჩვენ შეამოწმეთ, რომ არ ატვირთული მონაცემები სასარგებლოა. ფსიქო-დატვირთვა, მიუხედავად იმისა, რომ არ არის სრულყოფილი, მოდელი გაძლევთ ძირითადი დაწყება, რომ სუფთა მონიტორინგი 200 სურათებს არ შეუძლია შეესაბამება. 5.11 - შედეგების ინტერფეისი აქ არის, თუ როგორ უნდა წაიკითხოთ შედარებით: F1 გააუმჯობესებული +0.05 ან მეტი: ჩვეულებრივი მოგზაურობა ნახევარ მონიტორინგისთვის. არ აღწერილი მონაცემები უზრუნველყოფს მნიშვნელობა. F1 გაუმჯობესებული +0.01 to +0.04: მინიმალური გაუმჯობესება. Semi-supervised ეხმარება, მაგრამ margins არის მცირე. გთხოვთ გაუმჯობესოს clustering ხარისხი ან გამოიყენოთ უფრო მოწინავე pseudo-labeling. F1 არასამთავრობო ან უკეთესი: pseudo-labels იყო ძალიან მძიმე, რომ დაგეხმაროთ, ან clustering არ მოპოვოს რეალური სტრუქტურა. შეამოწმეთ სხვადასხვა ფუნქციონირების ექსტრაქტორები, სხვადასხვა clustering ალგორტატები, ან მაღალი უსაფრთხოების სართულები pseudo-labels. დამატებითი წაიკითხვა: დამატებითი წაიკითხვა: Pseudo-labelling paper (Lee, 2013) — ორიგინალური მიმოხილვა Semi-supervised Learning Survey (van Engelen & Hoos) - მოწინავე მიმოხილვა მეთოდები PyTorch სასწავლო loop საუკეთესო პრაქტიკები Pseudo-labelling პრაქტიკა (Lee, 2013) Semi-Supervised Learning კვლევა (van Engelen & Hoos) PyTorch სასწავლო loop საუკეთესო პრაქტიკები ნაწილი 6 — შეზღუდვა მილიონი სურათები: რეალური გზა კითხვა ბიზნესის შესახებ "სავარაუდოდ, თქვენი კონცეფცია მუშაობს 10,000 სურათებს. ჩვენ გვაქვს 4 მილიონი სურათების დამუშავება. შეგვიძლია ეს მილები ფართოდება 5000 ევროში?" ეს არის კითხვა, რომელიც თქვენ შეხვდება ნებისმიერი რეალური პროექტში. გთხოვთ, გაბრძალოთ ეს სიტყვებით. კომპიუტერული ღირებულება ეს არის bottleneck. ჩვენი 10,000 სურათების ერთად ერთი GPU, ეს იღებს დაახლოებით 30 წუთი. სტანდარტულად: Feature extraction 4,000,000 images ÷ 10,000 images × 30 min = 12,000 min = 200 GPU-hours ~€2/ საათისთვის Cloud GPU instance (Azure NC სერია T4 ან A10 GPU), ეს არის დაახლოებით . €400 სტანდარტული K-Means ატვირთებს ყველა მონაცემებს მახასიათებლები, რათა დააკმაყოფილოთ distances. ერთად 4M შეფუთვა 2048 დონეზე (თუ თითოეული 4 byte float): Clustering 4,000,000 × 2,048 × 4 bytes = ~32 GB just for the embeddings ეს არ შეესაბამება RAM- ში ყველაზე მანქანებში. გადაწყვეტილება: გამოყენება SICIT-LEARN, რომელიც მონაცემების დამუშავება (მაგ) 10,000 ნიმუში ერთხელ. იგივე შედეგად, მეხსიერების დონე. MiniBatchKMeans Pre-training on 4M pseudo-labelled სურათები იღებს დაახლოებით 50 GPU საათები . CNN training €100 შენახვის ღირებულება Raw სურათები: 4M × ~50 KB საშუალო = შეფუთვა: 4M × 2048 × 4 bytes = Azure Blob Storage- ზე ~€0.02/GB / თვეში, ეს არის . 200 GB 32 GB €5/month Labels სტრატეგია თუ 200 label არ არის საკმარისი ზომით, ჩვენ შეგვიძლია label მეტი. ~ € 1 per image (მაგ. ხარისხის კონტროლი), მეტი 2000 label იქნება ღირებულება მაგრამ არსებობს უფრო სასიამოვნო მიზნით: . €2,000 active learning Active Learning საშუალებას აძლევს მოდელი აირჩიოთ იმიტომ, რომ მოდელი არ აირჩიოს 2000 სურათს, ის აირჩიებს მათ, ვინც ყველაზე არ არის დარწმუნებული - სურათები, რომლებიც ყველაზე განიცდიან. ეს მოითხოვს 3-4x ნაკლებად მაგიდაები, რათა გააკეთოთ იგივე შესრულების გაუმჯობესება. რა აქტიური სწავლა, ჩვენ შეიძლება მხოლოდ საჭირო 500 დამატებითი label instead of 2,000 . €500 Total Budget შეფასება Feature extraction (GPU): €400 CNN training (GPU): €100 Storage (year 1): €60 Additional labeling: €500 – €2,000 ────────────────────────────────────── TOTAL: €1,060 – €2,560 დაახლოებით 5 მილიონი ფულადი ფულადი ფულადი ფულადი ფულადი ფულადი ფულადი ფულადი ფულადი ფულადი ფულადი 5 პირობები წარმატებისათვის გამოიყენეთ cloud GPUs, არა ადგილობრივი hardware. rent by hour, გადაიხადოს მხოლოდ ის, რაც თქვენ გამოიყენოთ. იყენეთ MiniBatchKMeans ნაცვლად რეგულარული KMeans. იგივე ხარისხის, 100x ნაკლები მეხსიერება. დასაწყისში დასაწყისში დასაწყისში დასაწყისში დასაწყისში დასაწყისში დასაწყისში დასაწყისში. განიხილეთ აქტიური სასწავლო, რათა მაქსიმალური ღირებულება თითოეული ადამიანური labeled სურათები. თითოეული label უნდა იყოს აირჩიული სტრატეგიულად, არ ნაცვლად. სავაჭრო და ვერსია შეფუთვა, არა მხოლოდ Raw images. Re-extracting 4M შეფუთვა ღირებულება € 400; loading შეფუთვა შეფუთვა ღირებულება არაფერი. დამატებითი წაიკითხვა: დამატებითი წაიკითხვა: MiniBatchKMeans (scikit-learn) — how to label smarter, not more Active learning overview MiniBatchKMeans (გამოწმეთ) Active Learning მიმოხილვა კონტაქტი Semi-supervised learning არ არის მექანიზია - ეს არის ინჟინერი. თქვენ მიიღებთ სტრუქტურა, რომელიც შეუზღუდავი არტაქტირებული მონაცემები (შვეობით შეფუთვა და clustering), გადაიხადოს დაახლოებით label, და გამოიყენოთ მათ, რათა უზრუნველყოს თქვენი supervised მოდელი დაწყება. არტაქტირებული მონაცემები არ შეცვალოს რეალური label - ეს დამატება მათ. მიმოხილეთ სრული მილის ხაზს, რომელიც ჩვენ აშენდა: ექსპერიმენტი — ჩვენ შეამოწმეთ 10,000 სურათები შეკუმშვის, შეკუმშული ფორმატების და კლასის შეკუმშვისთვის. ჩვენ ვხედავ მონაცემებს საკუთარი თვალებით. Preprocessing - ჩვენ სტანდარტიზებული ყველა სურათს ფორმატში ResNet50 მოთხოვნებს: 224×224, RGB, CLAHE-მუმჯობესებული, ImageNet-მუმჯობესებული. ფუნქციონალური ექსპერიმენტი - ჩვენ გამოიყენეთ წინასწარ ტრენინგი ResNet50 კონვერტაციის თითოეულ სურათს 2048-dimensional შეფუთვა, რომელიც იღებს მისი ვიზუალური სინამდვილეში. Clustering — ჩვენ გამოიყენეთ K-Means და DBSCAN- ს შეერთოს არტაქტირებული სურათები კლასტერებში, და შემდეგ აირჩიეთ pseudo-labels საფუძველზე კლასტერის წევრების. Semi-supervised სასწავლო - ჩვენ წინასწარ სასწავლო CNN 9800 pseudo-labelled სურათები, შემდეგ finest-tuned 200 რეალური label, და შედარებით მხოლოდ supervised ბაზარზე. სტანდარტული ანალიზი - 4M სურათების კომპიუტერული, შენახვის და ბეჭდვის ღირებულება დააკმაყოფილია, რაც უზრუნველყოფს ხელმისაწვდომობის ღირებულება 5 000 ევრო. ძირითადი Takeaways: CNN- ის წინასწარ ტრენინგი შეუძლია ნებისმიერი სურათის დონედან მნიშვნელობის ფუნქციონირება, მაშინაც კი, თუ ის არასდროს არ არის ტრენინგი. შეფუთვა შეფუთვა განიხილავს ბუნებრივი შეფუთვა, რომელიც ხშირად შეესაბამება რეალური კლასებს. Pseudo-labels არ არის სრულყოფილი, მაგრამ მოდელი, რომელიც წინასწარი ტრენინგი არ არის სრულყოფილი და შემდეგ მინახავს რეალური label- ს, უპირატესობთ მოდელი, რომელიც მხოლოდ რეალური label- ს აქვს ტრენინგი. მოდელი მუშაობს მასშტაბით: სამედიცინო სურათები, ინდუსტრიული ხარისხის კონტროლი, პლატფორმის სურათები, დოკუმენტების კლასისზაცია და ბიოლოგიური მრავალფუნქციონირების მონიტორინგი. ყველა ადგილას ბეჭდვა ფასია და არაბეჭდილი მონაცემები ფართოა - რომელიც 2025 წელს თითქმის ყველა ადგილას არის.