Mundarija (21)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Rasm — sonlar massivi
- 2.2. uint8 va float
- 2.3. numpy (H, W, C) va torch (C, H, W)
- 2.4. Normalizatsiya
- 2.5. PIL bilan fayllar
- 2.6. Batch va ma'lumot to'plamlari
- 2.7. Tuzoqlar
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Rasm sonlar massivi sifatida
- Misol 2 — PIL: fayl, format va o'lcham
- Misol 3 — load_digits, histogram va normalizatsiya
- Misol 4 — Sintetik shakllar va piksel bazaviysi
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
22.1-dars: Rasm ma'lumoti
22-QISM — KOMPYUTER KO'RISH · 1-dars
1. Kirish va motivatsiya
21-qismda PyTorch bilan to'liq ishlashni o'rgandik: nn.Module, Dataset va DataLoader, optimizatorlar, Trainer, checkpoint, regularizatsiya va diagnostika. Hozirgacha ma'lumotimiz asosan jadval edi — har qator bir obyekt, har ustun bir belgi. Endi yangi turdagi ma'lumotga o'tamiz: rasmlar.
Kompyuter uchun rasm — sehrli narsa emas. Bu sonlar massivi: har piksel uchun bir (kulrang) yoki uchta (qizil, yashil, ko'k) son. 1920×1080 o'lchamli rangli surat — bu 1080 × 1920 × 3 = 6 220 800 ta son. Qiyinchilik aynan shu yerda: sonlar juda ko'p, ular orasidagi fazoviy bog'lanish esa (qo'shni piksellar o'xshash) jadvalda yo'q edi.
Bu qismda kompyuter ko'rishning asosiy vositalarini o'rganamiz: konvolyutsiya, pooling, CNN arxitekturalari, transfer learning. Lekin ularning hammasi bitta poydevorga tayanadi — rasmni tensor sifatida to'g'ri tushunish. Shakl (H, W, C) mi yoki (C, H, W) mi, uint8 mi yoki float32 mi, qiymatlar 0..255 mi yoki 0..1 mi — bu savollardagi xato model "o'rganmayapti" degan eng ko'p uchraydigan shikoyatlarning sababi.
Real vaziyat. Bir jamoa mahsulot suratlarini tasniflash modelini o'rgatdi: o'quvda aniqlik 94%, ishlab chiqarishda esa 61%. Bir hafta arxitekturani o'zgartirishdi. Oxirida sabab topildi: o'quvda rasmlar PIL orqali RGB tartibida o'qilgan, ishlab chiqarish servisi esa boshqa kutubxona bilan BGR tartibida o'qigan. Qizil va ko'k kanallar almashgan edi. Bitta qator tuzatish aniqlikni qaytardi.
Bu darsda rasmni sonlar massivi sifatida ko'ramiz va uni modelga to'g'ri tayyorlashni o'rganamiz.
Bu darsda:
- Piksel, kanal va shakl:
(H, W, C)va(C, H, W) -
uint8vafloat: tur va oraliq - Normalizatsiya:
0..1va kanal bo'yicha mean/std - PIL bilan o'qish, yozish, o'lcham o'zgartirish
- Batch:
(N, C, H, W) load_digitsva sintetik shakllar- Tuzoqlar
ℹ Misollar real torch/numpy bilan (Python 3.14, torch 2.14 CPU).
2. Nazariya — chuqur tushuntirish
2.1. Rasm — sonlar massivi
KULRANG RASM: (H, W) har piksel - bitta son (yorqinlik)
RANGLI RASM: (H, W, 3) har piksel - uchta son (R, G, B)
SHAFFOF: (H, W, 4) R, G, B + alfa (shaffoflik)
H - balandlik (qatorlar soni), W - kenglik (ustunlar soni)
C - kanallar soni
rasm[y, x] -> bitta piksel: [R, G, B]
rasm[y, x, 0] -> shu pikselning qizil qiymati
rasm[:, :, 0] -> butun qizil kanal, shakl (H, W)
KOORDINATALAR:
(0, 0) - chap-YUQORI burchak
y pastga o'sadi, x o'ngga o'sadi
(matematikadagi grafikdan farqli - y teskari)
SONLAR SONI: H * W * C
28x28 kulrang -> 784
224x224 rangli -> 150 528
1080x1920 rangli -> 6 220 800 Rasm — (H, W, C) shaklli sonlar massivi; birinchi indeks qator (y), ikkinchisi ustun (x).
2.2. uint8 va float
SAQLASH: uint8 - 0..255 butun sonlar, 1 bayt
fayllar (PNG, JPEG), PIL, kamera - hammasi uint8
HISOB: float32 - 0.0..1.0 (yoki normallashgan), 4 bayt
tarmoq og'irliklari float, gradient float
uint8 TUZOG'I - arifmetika 256 bo'yicha aylanadi:
200 + 100 = 44 (300 - 256)
100 - 200 = 156 (-100 + 256)
xato chiqmaydi, jim buziladi
TO'G'RI YO'L:
x = rasm.astype(np.float32) / 255.0 # numpy
x = torch.from_numpy(rasm).float() / 255 # torch
yoki: int16 ga o'tkazib, hisoblab, clip(0, 255), uint8 ga qaytarish
XOTIRA: float32 uint8 dan 4 barobar ko'p joy oladi
-> diskda va xotirada uint8 saqlang, batch ga olganda float qiling Saqlash — uint8, hisob — float32; uint8 ustida arifmetika jim buziladi.
2.3. numpy (H, W, C) va torch (C, H, W)
numpy / PIL / matplotlib: (H, W, C) "kanal oxirida"
torch (nn.Conv2d): (C, H, W) "kanal oldinda"
batch: (N, C, H, W)
O'TKAZISH:
t = torch.from_numpy(rasm).permute(2, 0, 1) # (H,W,C) -> (C,H,W)
rasm = t.permute(1, 2, 0).numpy() # orqaga
permute - o'qlarni ALMASHTIRADI (piksellar joyida qoladi)
reshape - sonlarni YANGI shaklga QUYADI (tartib o'zgarmaydi)
reshape(3, H, W) xato: (H, W, 3) xotirada R,G,B,R,G,B,... ketma-ket
reshape uni "birinchi H*W son - 0-kanal" deb o'qiydi -> aralash
permute dan keyin tensor contiguous EMAS (faqat ko'rinish)
.contiguous() - kerak bo'lsa nusxa oladi (view() uchun)
KULRANG: (H, W) -> unsqueeze(0) -> (1, H, W)
batch: (N, H, W) -> unsqueeze(1) -> (N, 1, H, W) O'qlarni almashtirish — permute, hech qachon reshape emas; kulrang rasmga ham kanal o'qi kerak.
2.4. Normalizatsiya
1) 0..1 GA MASSHTAB:
x = rasm / 255.0
eng oddiy, ko'p hollarda yetarli
2) KANAL BO'YICHA mean/std (standartlash):
mean_c = x[:, c, :, :].mean() - butun o'quv to'plami bo'yicha
std_c = x[:, c, :, :].std()
x_norm[:, c] = (x[:, c] - mean_c) / std_c
torch da: dim=(0, 2, 3) bo'yicha - N, H, W ustidan, C qoladi
shakl: mean.view(1, C, 1, 1) - broadcasting uchun
NEGA KANAL BO'YICHA:
R, G, B kanallar yorqinligi va tarqoqligi har xil
bitta umumiy mean/std - kanallar orasidagi siljishni qoldiradi
ImageNet STATISTIKASI (torchvision namunalaridan mashhur):
mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
oldindan o'rgatilgan modelga - AYNAN o'sha statistika bilan
QOIDA: mean/std FAQAT o'quv to'plamida hisoblanadi (17-qism, leakage) Statistika kanal bo'yicha, dim=(0, 2, 3) ustidan, faqat o'quv to'plamida.
2.5. PIL bilan fayllar
from PIL import Image
img = Image.open(yol) # dangasa: piksellar kerak bo'lganda o'qiladi
img = img.convert("RGB") # RGBA, P (palitra), L -> RGB
arr = np.asarray(img) # (H, W, 3) uint8
img.size -> (W, H) !!! kenglik birinchi
arr.shape -> (H, W, 3)
Image.fromarray(arr).save("x.png")
img.resize((W, H), Image.Resampling.BILINEAR) # (kenglik, balandlik)
img.crop((chap, yuqori, o'ng, past))
img.convert("L") # kulrang: 0.299R + 0.587G + 0.114B
FORMATLAR:
PNG - yo'qotishsiz, niqoblar/yorliqlar uchun
JPEG - yo'qotishli, suratlar uchun kichik fayl
qayta saqlash har safar sifatni yana pasaytiradi
RESIZE USULLARI:
NEAREST - eng yaqin piksel, yangi qiymat yaratmaydi (niqob, yorliq)
BILINEAR - 4 qo'shni o'rtachasi, silliq
BICUBIC - 16 qo'shni, silliqroq
torchvision - rasm uchun standart kutubxona (transforms, datasets),
lekin bu kursda u yo'q: hammasini PIL + numpy + torch bilan qilamiz PIL — fayl va o'lcham uchun, hisob — numpy/torch da; img.size ning tartibi (W, H).
2.6. Batch va ma'lumot to'plamlari
BATCH: (N, C, H, W)
N - rasmlar soni, C - kanal, H x W - o'lcham
bitta batchdagi hamma rasm BIR XIL o'lchamda bo'lishi shart
-> resize/crop Dataset ichida qilinadi
MLP uchun: x.flatten(1) -> (N, C*H*W) (20-qism)
CNN uchun: (N, C, H, W) o'zicha qoladi (keyingi darslar)
KICHIK TO'PLAMLAR (internetsiz):
sklearn.datasets.load_digits - 1797 ta 8x8 raqam, 0..16 qiymatlar
.images (1797, 8, 8), .data (1797, 64), .target (1797,)
sintetik shakllar - numpy bilan: doira, kvadrat, chiziq
afzalligi: haqiqiy javobni biz bilamiz, murakkablikni boshqaramiz
KATTA TO'PLAMLAR (nazariy):
MNIST, CIFAR-10, ImageNet - odatda torchvision.datasets orqali Batch — (N, C, H, W); sintetik to'plam g'oyani tekshirishning eng arzon yo'li.
2.7. Tuzoqlar
Asosiy tuzoqlar: uint8 ustida arifmetika (200 + 100 = 44); (H, W, C) dan (C, H, W) ga reshape bilan o'tish; img.size ni (H, W) deb o'qish; RGB va BGR tartibini aralashtirish; kulrang rasmga kanal o'qini qo'shmaslik; normalizatsiya statistikasini test to'plamida hisoblash; oldindan o'rgatilgan modelga boshqa mean/std berish; niqob (yorliq) rasmini BILINEAR bilan kichraytirish; nisbatni saqlamasdan resize qilish; JPEG ni yo'qotishsiz deb o'ylash.
3. Tez ma'lumotnoma
import numpy as np
import torch
from PIL import Image
def rasm_oqi(yol):
"""Fayl -> (C, H, W) float32, 0..1."""
with Image.open(yol) as img:
arr = np.asarray(img.convert("RGB")) # (H, W, 3) uint8
return torch.from_numpy(arr).permute(2, 0, 1).float() / 255.0
def kanal_statistikasi(x):
"""x: (N, C, H, W) - faqat o'quv to'plami."""
return x.mean(dim=(0, 2, 3)), x.std(dim=(0, 2, 3))
def normallashtir(x, orta, std):
return (x - orta.view(1, -1, 1, 1)) / std.view(1, -1, 1, 1)
def tensor_rasmga(t):
"""(C, H, W) 0..1 -> PIL."""
arr = (t.clamp(0, 1) * 255).round().byte().permute(1, 2, 0).numpy()
return Image.fromarray(arr)Rasm ma'lumoti xulosasi
rasm: (H, W, C) uint8 0..255 -> torch: (C, H, W) float32 0..1
o'tkazish: permute(2, 0, 1), reshape EMAS
batch: (N, C, H, W); kulrang: unsqueeze(1)
normalizatsiya: kanal bo'yicha, dim=(0, 2, 3), faqat o'quvda
PIL: size = (W, H); resize((W, H)); niqob uchun NEAREST4. Batafsil misollar
Misollar real torch/numpy bilan (Python 3.14, torch 2.14 CPU).
Misol 1 — Rasm sonlar massivi sifatida
"""Rasm = sonlar massivi: (H, W, C), uint8, kanallar (real numpy/torch)."""
import numpy as np
import torch
def rasm_yarat(h=6, w=8):
"""Chap yarmi qizil, o'ng yarmi ko'k, o'rtada oq kvadrat."""
rasm = np.zeros((h, w, 3), dtype=np.uint8)
rasm[:, : w // 2] = [220, 30, 30]
rasm[:, w // 2:] = [30, 60, 200]
rasm[2:4, 3:5] = [255, 255, 255]
return rasm
def main() -> None:
rasm = rasm_yarat()
print("=== 1. Shakl va tur ===")
h, w, c = rasm.shape
print(f" shakl (H, W, C): {rasm.shape}")
print(f" dtype: {rasm.dtype}, min {rasm.min()}, max {rasm.max()}")
print(f" piksellar soni: {h * w}, sonlar soni: {rasm.size}")
print(f" xotira: {rasm.nbytes} bayt (har son 1 bayt)")
print("\n=== 2. Bitta piksel va bitta kanal ===")
print(f" rasm[0, 0] (chap-yuqori): {rasm[0, 0].tolist()} -> qizil")
print(f" rasm[0, 7] (o'ng-yuqori): {rasm[0, 7].tolist()} -> ko'k")
print(f" rasm[2, 3] (markaz): {rasm[2, 3].tolist()} -> oq")
print(" R kanali (rasm[:, :, 0]):")
for qator in rasm[:, :, 0]:
print(" ", " ".join(f"{v:>3}" for v in qator))
print("\n=== 3. uint8 tuzog'i ===")
a = np.array([200], dtype=np.uint8)
b = np.array([100], dtype=np.uint8)
with np.errstate(over="ignore"):
yigindi = a + b
ayirma = b - a
print(f" uint8: 200 + 100 = {int(yigindi[0])} (256 ga bo'lgandagi qoldiq)")
print(f" uint8: 100 - 200 = {int(ayirma[0])}")
yorqin = np.clip(rasm.astype(np.int16) + 100, 0, 255).astype(np.uint8)
print(f" to'g'ri yorqinlashtirish (int16 + clip): "
f"{rasm[0, 0].tolist()} -> {yorqin[0, 0].tolist()}")
print("\n=== 4. float ga o'tkazish ===")
f = rasm.astype(np.float32) / 255.0
print(f" float32 oralig'i: [{f.min():.4f}, {f.max():.4f}]")
print(f" xotira: {f.nbytes} bayt ({f.nbytes // rasm.nbytes} barobar)")
print("\n=== 5. numpy (H,W,C) -> torch (C,H,W) ===")
t = torch.from_numpy(rasm).permute(2, 0, 1)
print(f" permute(2, 0, 1) shakli: {tuple(t.shape)}")
print(f" contiguous: {t.is_contiguous()} (faqat ko'rinish o'zgardi)")
print(f" t[0] == rasm[:, :, 0]: "
f"{torch.equal(t[0], torch.from_numpy(rasm[:, :, 0]))}")
xato = torch.from_numpy(rasm).reshape(3, h, w)
print(f" reshape(3, H, W) bilan 0-kanal == R kanali: "
f"{torch.equal(xato[0], torch.from_numpy(rasm[:, :, 0]))}")
print(f" reshape 0-kanalining 1-qatori: {xato[0, 0, :6].tolist()}")
print(" reshape piksellarni aralashtirib yubordi - permute kerak")
orqaga = t.permute(1, 2, 0).numpy()
print(f" orqaga (H,W,C): {orqaga.shape}, "
f"aslidek: {np.array_equal(orqaga, rasm)}")
print("\n=== 6. Kulrang (grayscale) ===")
vazn = np.array([0.299, 0.587, 0.114])
kul = rasm.astype(np.float64) @ vazn
print(f" kul shakli: {kul.shape}")
print(f" qizil piksel -> {kul[0, 0]:.1f}, ko'k -> {kul[0, 7]:.1f}, "
f"oq -> {kul[2, 3]:.1f}")
oddiy = rasm.astype(np.float64).mean(axis=2)
print(f" oddiy o'rtacha: qizil {oddiy[0, 0]:.1f}, ko'k {oddiy[0, 7]:.1f}")
print(" ko'z yashilga eng sezgir - vaznlar teng emas")
print(" ⭐ Rasm - (H, W, C) sonlar massivi; torch uchun (C, H, W)")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Shakl va tur ===
shakl (H, W, C): (6, 8, 3)
dtype: uint8, min 30, max 255
piksellar soni: 48, sonlar soni: 144
xotira: 144 bayt (har son 1 bayt)
=== 2. Bitta piksel va bitta kanal ===
rasm[0, 0] (chap-yuqori): [220, 30, 30] -> qizil
rasm[0, 7] (o'ng-yuqori): [30, 60, 200] -> ko'k
rasm[2, 3] (markaz): [255, 255, 255] -> oq
R kanali (rasm[:, :, 0]):
220 220 220 220 30 30 30 30
220 220 220 220 30 30 30 30
220 220 220 255 255 30 30 30
220 220 220 255 255 30 30 30
220 220 220 220 30 30 30 30
220 220 220 220 30 30 30 30
=== 3. uint8 tuzog'i ===
uint8: 200 + 100 = 44 (256 ga bo'lgandagi qoldiq)
uint8: 100 - 200 = 156
to'g'ri yorqinlashtirish (int16 + clip): [220, 30, 30] -> [255, 130, 130]
=== 4. float ga o'tkazish ===
float32 oralig'i: [0.1176, 1.0000]
xotira: 576 bayt (4 barobar)
=== 5. numpy (H,W,C) -> torch (C,H,W) ===
permute(2, 0, 1) shakli: (3, 6, 8)
contiguous: False (faqat ko'rinish o'zgardi)
t[0] == rasm[:, :, 0]: True
reshape(3, H, W) bilan 0-kanal == R kanali: False
reshape 0-kanalining 1-qatori: [220, 30, 30, 220, 30, 30]
reshape piksellarni aralashtirib yubordi - permute kerak
orqaga (H,W,C): (6, 8, 3), aslidek: True
=== 6. Kulrang (grayscale) ===
kul shakli: (6, 8)
qizil piksel -> 86.8, ko'k -> 67.0, oq -> 255.0
oddiy o'rtacha: qizil 93.3, ko'k 96.7
ko'z yashilga eng sezgir - vaznlar teng emas
⭐ Rasm - (H, W, C) sonlar massivi; torch uchun (C, H, W)Nima ko'rsatdi: 2.1, 2.2, 2.3-bo'limlar.
Misol 2 — PIL: fayl, format va o'lcham
"""PIL bilan o'qish, yozish, o'lcham o'zgartirish (vaqtincha papkada)."""
import os
import shutil
import tempfile
import numpy as np
from PIL import Image
def rasm_yarat(h=48, w=64, seed=0):
"""Gradient fon + doira + shovqin: haqiqiy suratga o'xshash."""
rng = np.random.default_rng(seed)
y, x = np.mgrid[0:h, 0:w]
r = (x / (w - 1) * 255)
g = (y / (h - 1) * 255)
b = np.full((h, w), 120.0)
doira = (x - 40) ** 2 + (y - 20) ** 2 < 12 ** 2
r[doira], g[doira], b[doira] = 250, 230, 40
rasm = np.stack([r, g, b], axis=2) + rng.normal(0, 6, (h, w, 3))
return np.clip(rasm, 0, 255).astype(np.uint8)
def main() -> None:
papka = tempfile.mkdtemp()
try:
arr = rasm_yarat()
img = Image.fromarray(arr)
print("=== 1. numpy -> PIL ===")
print(f" numpy shakli (H, W, C): {arr.shape}")
print(f" PIL size (W, H): {img.size}, mode: {img.mode}")
print(" DIQQAT: PIL (kenglik, balandlik), numpy (balandlik, kenglik)")
print("\n=== 2. PNG va JPEG ga yozib, qayta o'qish ===")
print(f" {'format':<12} {'hajm, bayt':>11} {'aynan tengmi':>13} "
f"{'maks farq':>10} {'o_rtacha farq':>14}")
xom = arr.nbytes
for nom, kw in [("png", {}), ("jpg", {"quality": 95}),
("jpg", {"quality": 50})]:
yol = os.path.join(papka, f"rasm_{kw.get('quality', 0)}.{nom}")
img.save(yol, **kw)
with Image.open(yol) as o:
qayta = np.asarray(o.convert("RGB"))
farq = np.abs(qayta.astype(int) - arr.astype(int))
belgi = nom + (f" q={kw['quality']}" if kw else "")
print(f" {belgi:<12} {os.path.getsize(yol):>11} "
f"{str(np.array_equal(qayta, arr)):>13} "
f"{farq.max():>10} {farq.mean():>14.2f}")
print(f" siqilmagan hajm: {xom} bayt")
print(" PNG - yo'qotishsiz, JPEG - piksellarni o'zgartiradi")
print("\n=== 3. O'lchamni o'zgartirish ===")
kichik = img.resize((16, 12), Image.Resampling.BILINEAR)
print(f" resize((16, 12)) -> size {kichik.size}, "
f"numpy {np.asarray(kichik).shape}")
for usul in ["NEAREST", "BILINEAR", "BICUBIC"]:
katta = img.resize((128, 96), getattr(Image.Resampling, usul))
k = np.asarray(katta)
noyob = len(np.unique(k.reshape(-1, 3), axis=0))
print(f" {usul:<9} 2x kattalashtirish: noyob ranglar {noyob:>5}")
print(f" asl rasmdagi noyob ranglar: "
f"{len(np.unique(arr.reshape(-1, 3), axis=0))}")
print(" NEAREST yangi rang yaratmaydi (niqob/yorliq uchun shu kerak)")
print("\n=== 4. Nisbatni saqlash ===")
w, h = img.size
yangi_w = 32
yangi_h = round(h * yangi_w / w)
print(f" {w}x{h} -> {yangi_w}x{yangi_h} (nisbat {w / h:.3f} -> "
f"{yangi_w / yangi_h:.3f})")
chozilgan = img.resize((32, 32))
print(f" majburan 32x32: nisbat {w / h:.3f} -> 1.000 "
f"(doira ellipsga aylanadi), size {chozilgan.size}")
print("\n=== 5. Kulrang va kesish ===")
kul = img.convert("L")
k = np.asarray(kul)
print(f" convert('L'): mode {kul.mode}, numpy {k.shape}, {k.dtype}")
qolda = np.round(arr.astype(float) @ [0.299, 0.587, 0.114])
print(f" qo'lda formula bilan maks farq: "
f"{np.abs(qolda - k).max():.0f}")
kesik = img.crop((28, 8, 52, 32))
print(f" crop((chap, yuqori, o'ng, past)=(28, 8, 52, 32)) -> "
f"size {kesik.size}")
print(f" numpy ekvivalenti arr[8:32, 28:52]: "
f"{np.array_equal(np.asarray(kesik), arr[8:32, 28:52])}")
print(f" papkadagi fayllar: {len(os.listdir(papka))}")
finally:
shutil.rmtree(papka)
print(f" papka o'chirildi: {not os.path.exists(papka)}")
print(" ⭐ PIL - fayl va o'lcham, numpy/torch - hisob")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. numpy -> PIL ===
numpy shakli (H, W, C): (48, 64, 3)
PIL size (W, H): (64, 48), mode: RGB
DIQQAT: PIL (kenglik, balandlik), numpy (balandlik, kenglik)
=== 2. PNG va JPEG ga yozib, qayta o'qish ===
format hajm, bayt aynan tengmi maks farq o_rtacha farq
png 6257 True 0 0.00
jpg q=95 2291 False 102 5.34
jpg q=50 974 False 114 7.75
siqilmagan hajm: 9216 bayt
PNG - yo'qotishsiz, JPEG - piksellarni o'zgartiradi
=== 3. O'lchamni o'zgartirish ===
resize((16, 12)) -> size (16, 12), numpy (12, 16, 3)
NEAREST 2x kattalashtirish: noyob ranglar 3051
BILINEAR 2x kattalashtirish: noyob ranglar 11648
BICUBIC 2x kattalashtirish: noyob ranglar 11908
asl rasmdagi noyob ranglar: 3051
NEAREST yangi rang yaratmaydi (niqob/yorliq uchun shu kerak)
=== 4. Nisbatni saqlash ===
64x48 -> 32x24 (nisbat 1.333 -> 1.333)
majburan 32x32: nisbat 1.333 -> 1.000 (doira ellipsga aylanadi), size (32, 32)
=== 5. Kulrang va kesish ===
convert('L'): mode L, numpy (48, 64), uint8
qo'lda formula bilan maks farq: 1
crop((chap, yuqori, o'ng, past)=(28, 8, 52, 32)) -> size (24, 24)
numpy ekvivalenti arr[8:32, 28:52]: True
papkadagi fayllar: 3
papka o'chirildi: True
⭐ PIL - fayl va o'lcham, numpy/torch - hisobNima ko'rsatdi: 2.5-bo'lim.
Misol 3 — load_digits, histogram va normalizatsiya
"""load_digits, histogram, batch (N, C, H, W) va normalizatsiya."""
import numpy as np
import torch
from sklearn.datasets import load_digits
def yaxlit(t, n=4):
return [round(float(v), n) + 0.0 for v in t]
def rangli_toplam(n=200, h=16, w=16, seed=0):
"""Kanallari har xil yorqinlikdagi sintetik RGB to'plam (uint8)."""
rng = np.random.default_rng(seed)
orta = np.array([150.0, 100.0, 60.0])
tarqoq = np.array([40.0, 25.0, 15.0])
x = rng.normal(orta, tarqoq, (n, h, w, 3))
return np.clip(x, 0, 255).astype(np.uint8)
def main() -> None:
d = load_digits()
print("=== 1. load_digits ===")
print(f" images: {d.images.shape}, data: {d.data.shape}, "
f"dtype {d.images.dtype}")
print(f" qiymatlar oralig'i: {d.images.min():.0f}..{d.images.max():.0f} "
f"(16 darajali kulrang)")
print(f" sinflar: {np.unique(d.target).tolist()}")
print(f" data[0] == images[0].ravel(): "
f"{np.array_equal(d.data[0], d.images[0].ravel())}")
print(f" 0-rasm, yorliq {d.target[0]}:")
belgi = " .:-=+*#%@"
for qator in d.images[0]:
print(" " + "".join(belgi[int(v * 9 / 16)] * 2 for v in qator))
print("\n=== 2. Piksel histogrammasi ===")
soni, chegara = np.histogram(d.images, bins=[0, 1, 4, 8, 12, 16.5])
jami = soni.sum()
for i in range(len(soni)):
ulush = soni[i] / jami
print(f" [{chegara[i]:>4.0f}, {chegara[i + 1]:>4.1f}) "
f"{ulush:>6.1%} {'#' * int(ulush * 50)}")
print(f" nol piksellar: {(d.images == 0).mean():.1%} - fon ustun")
chekka = d.images[:, :, 0].mean()
markaz = d.images[:, 3:5, 3:5].mean()
print(f" chap ustun o'rtachasi {chekka:.2f}, markaz o'rtachasi "
f"{markaz:.2f}")
print("\n=== 3. Batch: (N, C, H, W) ===")
x = torch.tensor(d.images, dtype=torch.float32).unsqueeze(1)
print(f" unsqueeze(1): {tuple(x.shape)} (N, C=1, H, W)")
batch = x[:32]
print(f" bitta batch: {tuple(batch.shape)}")
print(f" bitta rasm (C, H, W): {tuple(batch[0].shape)}")
tekis = batch.flatten(1)
print(f" flatten(1) - MLP uchun: {tuple(tekis.shape)}")
print("\n=== 4. Oddiy masshtab: 0..1 ===")
x01 = x / 16.0
print(f" /16 dan keyin: [{x01.min():.1f}, {x01.max():.1f}], "
f"o'rtacha {x01.mean():.3f}")
print("\n=== 5. Kanal bo'yicha mean/std ===")
rgb = rangli_toplam()
t = torch.from_numpy(rgb).permute(0, 3, 1, 2).float() / 255.0
print(f" RGB to'plam: {tuple(t.shape)}")
orta = t.mean(dim=(0, 2, 3))
std = t.std(dim=(0, 2, 3))
print(f" kanal o'rtachalari: {yaxlit(orta)}")
print(f" kanal std lari: {yaxlit(std)}")
norm = (t - orta.view(1, 3, 1, 1)) / std.view(1, 3, 1, 1)
print(f" normallashgandan keyin o'rtacha: "
f"{yaxlit(norm.mean(dim=(0, 2, 3)))}")
print(f" normallashgandan keyin std: "
f"{yaxlit(norm.std(dim=(0, 2, 3)))}")
umumiy = (t - t.mean()) / t.std()
print(f" bitta umumiy mean/std bilan kanal o'rtachalari: "
f"{yaxlit(umumiy.mean(dim=(0, 2, 3)), 3)}")
print(" umumiy normalizatsiya kanallar orasidagi farqni qoldiradi")
print("\n=== 6. Broadcasting tuzog'i ===")
try:
_ = t - orta.view(3)
print(" view(3) bilan ayirish ishladi (kutilmagan)")
except RuntimeError as xato:
print(f" (N,3,H,W) - (3,): RuntimeError - {str(xato)[:45]}")
print(" ⭐ Statistikani (0, 2, 3) o'qlar bo'yicha, shaklni (1,3,1,1) qiling")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. load_digits ===
images: (1797, 8, 8), data: (1797, 64), dtype float64
qiymatlar oralig'i: 0..16 (16 darajali kulrang)
sinflar: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
data[0] == images[0].ravel(): True
0-rasm, yorliq 0:
::##++
##%%++%%::
..%%.. **==
::** ====
::== ++==
::** **--
..##::++**
--##++
=== 2. Piksel histogrammasi ===
[ 0, 1.0) 48.9% ########################
[ 1, 4.0) 9.0% ####
[ 4, 8.0) 9.8% ####
[ 8, 12.0) 10.1% #####
[ 12, 16.5) 22.2% ###########
nol piksellar: 48.9% - fon ustun
chap ustun o'rtachasi 0.00, markaz o'rtachasi 9.53
=== 3. Batch: (N, C, H, W) ===
unsqueeze(1): (1797, 1, 8, 8) (N, C=1, H, W)
bitta batch: (32, 1, 8, 8)
bitta rasm (C, H, W): (1, 8, 8)
flatten(1) - MLP uchun: (32, 64)
=== 4. Oddiy masshtab: 0..1 ===
/16 dan keyin: [0.0, 1.0], o'rtacha 0.305
=== 5. Kanal bo'yicha mean/std ===
RGB to'plam: (200, 3, 16, 16)
kanal o'rtachalari: [0.5866, 0.3901, 0.2332]
kanal std lari: [0.1565, 0.098, 0.059]
normallashgandan keyin o'rtacha: [0.0, 0.0, 0.0]
normallashgandan keyin std: [1.0, 1.0, 1.0]
bitta umumiy mean/std bilan kanal o'rtachalari: [1.002, -0.072, -0.93]
umumiy normalizatsiya kanallar orasidagi farqni qoldiradi
=== 6. Broadcasting tuzog'i ===
(N,3,H,W) - (3,): RuntimeError - The size of tensor a (16) must match the size
⭐ Statistikani (0, 2, 3) o'qlar bo'yicha, shaklni (1,3,1,1) qilingNima ko'rsatdi: 2.4, 2.6-bo'limlar.
Misol 4 — Sintetik shakllar va piksel bazaviysi
"""Sintetik shakllar to'plami: doira va kvadrat, piksellarda bazaviy model."""
import warnings
import numpy as np
import torch
from sklearn.linear_model import LogisticRegression
def shakl_chiz(turi, h, markaz_y, markaz_x, radius):
y, x = np.mgrid[0:h, 0:h]
if turi == 0: # doira
return ((y - markaz_y) ** 2 + (x - markaz_x) ** 2
<= radius ** 2).astype(np.float32)
return ((np.abs(y - markaz_y) <= radius)
& (np.abs(x - markaz_x) <= radius)).astype(np.float32)
def toplam(n, h=16, siljish=True, shovqin=0.15, seed=0):
"""siljish=False - hamma shakl markazda; True - tasodifiy joyda."""
rng = np.random.default_rng(seed)
x = np.zeros((n, 1, h, h), dtype=np.float32)
y = rng.integers(0, 2, n)
for i in range(n):
r = int(rng.integers(2, 5))
if siljish:
my, mx = rng.integers(r, h - r, 2)
else:
my = mx = h // 2
x[i, 0] = shakl_chiz(y[i], h, my, mx, r)
x += rng.normal(0, shovqin, x.shape).astype(np.float32)
return torch.from_numpy(np.clip(x, 0, 1)), torch.from_numpy(y)
def pikselda_bahola(x_tr, y_tr, x_te, y_te):
with warnings.catch_warnings(record=True) as ogoh:
warnings.simplefilter("always")
m = LogisticRegression(C=0.1, max_iter=3000)
m.fit(x_tr.flatten(1).numpy(), y_tr.numpy())
return m.score(x_te.flatten(1).numpy(), y_te.numpy()), len(ogoh)
def main() -> None:
print("=== 1. Bitta doira va bitta kvadrat (shovqinsiz) ===")
for turi, nom in [(0, "doira"), (1, "kvadrat")]:
s = shakl_chiz(turi, 9, 4, 4, 3)
print(f" {nom} (radius 3), yuzasi {int(s.sum())} piksel:")
for qator in s:
print(" " + "".join("##" if v else ". " for v in qator))
print("\n=== 2. To'plam ===")
x, y = toplam(1000)
print(f" x: {tuple(x.shape)} {x.dtype}, y: {tuple(y.shape)}")
print(f" sinflar balansi: doira {int((y == 0).sum())}, "
f"kvadrat {int((y == 1).sum())}")
print(f" qiymatlar oralig'i: [{x.min():.2f}, {x.max():.2f}]")
yuza = (x > 0.5).float().sum(dim=(1, 2, 3))
for k, nom in [(0, "doira"), (1, "kvadrat")]:
print(f" {nom:<8} o'rtacha yuza: {yuza[y == k].mean():.1f} piksel")
print("\n=== 3. Piksel = belgi: logistik regressiya ===")
print(f" {'holat':<28} {'o_quv':>6} {'test aniqlik':>13}")
natija = {}
for siljish in [False, True]:
for n_tr in [200, 1000]:
x_tr, y_tr = toplam(n_tr, siljish=siljish, seed=1)
x_te, y_te = toplam(1000, siljish=siljish, seed=2)
aniq, n_ogoh = pikselda_bahola(x_tr, y_tr, x_te, y_te)
natija[(siljish, n_tr)] = aniq
holat = "tasodifiy joyda" if siljish else "hammasi markazda"
print(f" {holat:<28} {n_tr:>6} {aniq:>13.3f}")
if n_ogoh:
print(f" ogohlantirishlar: {n_ogoh}")
farq = natija[(False, 1000)] - natija[(True, 1000)]
print(f" markazdan tasodifiy joyga o'tganda aniqlik "
f"{farq:.3f} ga kamaydi" if farq > 0 else
f" markazdan tasodifiy joyga o'tganda aniqlik kamaymadi "
f"({farq:+.3f})")
print("\n=== 4. Nega qiyin: bir xil shakl, boshqa piksellar ===")
a = torch.from_numpy(shakl_chiz(1, 16, 5, 5, 3))
b = torch.from_numpy(shakl_chiz(1, 16, 10, 10, 3))
kesishma = int((a * b).sum())
print(f" ikki kvadrat (har biri {int(a.sum())} piksel), faqat joyi "
f"boshqa: umumiy piksellar {kesishma}")
print(f" piksel vektorlari orasidagi kosinus: "
f"{torch.nn.functional.cosine_similarity(a.flatten(), b.flatten(), dim=0):.3f}")
print(" piksel modeli uchun bular deyarli 'boshqa' rasmlar")
print(" ⭐ Joyga bog'liq bo'lmagan belgi kerak - bu konvolyutsiya ishi")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Bitta doira va bitta kvadrat (shovqinsiz) ===
doira (radius 3), yuzasi 29 piksel:
. . . . . . . . .
. . . . ##. . . .
. . ##########. .
. . ##########. .
. ##############.
. . ##########. .
. . ##########. .
. . . . ##. . . .
. . . . . . . . .
kvadrat (radius 3), yuzasi 49 piksel:
. . . . . . . . .
. ##############.
. ##############.
. ##############.
. ##############.
. ##############.
. ##############.
. ##############.
. . . . . . . . .
=== 2. To'plam ===
x: (1000, 1, 16, 16) torch.float32, y: (1000,)
sinflar balansi: doira 463, kvadrat 537
qiymatlar oralig'i: [0.00, 1.00]
doira o'rtacha yuza: 30.6 piksel
kvadrat o'rtacha yuza: 51.5 piksel
=== 3. Piksel = belgi: logistik regressiya ===
holat o_quv test aniqlik
hammasi markazda 200 1.000
hammasi markazda 1000 1.000
tasodifiy joyda 200 0.682
tasodifiy joyda 1000 0.709
markazdan tasodifiy joyga o'tganda aniqlik 0.291 ga kamaydi
=== 4. Nega qiyin: bir xil shakl, boshqa piksellar ===
ikki kvadrat (har biri 49 piksel), faqat joyi boshqa: umumiy piksellar 4
piksel vektorlari orasidagi kosinus: 0.082
piksel modeli uchun bular deyarli 'boshqa' rasmlar
⭐ Joyga bog'liq bo'lmagan belgi kerak - bu konvolyutsiya ishiNima ko'rsatdi: 2.6-bo'lim va keyingi darslar motivatsiyasi.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
| "Rasm — maxsus obyekt" | (H, W, C) sonlar massivi |
"reshape va permute bir xil" |
reshape piksellarni aralashtiradi |
"uint8 da hisoblash xavfsiz" |
256 bo'yicha jim aylanadi |
"img.size — (H, W)" |
PIL da (W, H) |
| "Kulrang rasmga kanal kerak emas" | nn.Conv2d uchun (N, 1, H, W) |
| "Bitta umumiy mean/std yetarli" | Kanallar har xil — kanal bo'yicha |
| "JPEG ga saqlash — nusxa olish" | Piksellar o'zgaradi |
| "Piksellar — yaxshi belgilar" | Joy o'zgarsa, belgi butunlay o'zgaradi |
6. Keng tarqalgan xatolar va yechimlari
1. uint8 ustida arifmetika
yorqin = rasm + 100 # ⚠️ 200 + 100 = 44
yorqin = np.clip(rasm.astype(np.int16) + 100, 0, 255) # ✅2. reshape bilan o'q almashtirish
t = torch.from_numpy(rasm).reshape(3, h, w) # ⚠️ aralash
t = torch.from_numpy(rasm).permute(2, 0, 1) # ✅3. PIL size tartibi
h, w = img.size # ⚠️ teskari
w, h = img.size # ✅4. Kanal o'qisiz kulrang batch
x = torch.tensor(d.images) # (N, 8, 8) # ⚠️
x = torch.tensor(d.images).unsqueeze(1) # (N, 1, 8, 8) # ✅5. Noto'g'ri broadcasting
x - orta # orta (3,) # ⚠️
x - orta.view(1, 3, 1, 1) # ✅6. Statistika test to'plamida
orta = hamma_x.mean(dim=(0, 2, 3)) # ⚠️ leakage
orta = oquv_x.mean(dim=(0, 2, 3)) # ✅7. Niqobni BILINEAR bilan kichraytirish
niqob.resize((64, 64), Image.Resampling.BILINEAR) # ⚠️ 0.5 sinf
niqob.resize((64, 64), Image.Resampling.NEAREST) # ✅7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 20-qism (o'tilgan): MLP —
flatten(1)bilan rasmni vektor qilish - 21-qism (o'tilgan):
Dataset,DataLoader, augmentatsiya - 17-qism (o'tilgan): Masshtablash va leakage
- Keyingi darslar: Konvolyutsiya
(N, C, H, W)tensor ustida ishlaydi - Transfer learning mavzusida: ImageNet mean/std va oldindan o'rgatilgan modellar
- Segmentatsiya mavzusida: Niqoblar va
NEARESTresize
8. Eng yaxshi amaliyotlar
Diskda
uint8, hisobdafloat32.O'qlarni faqat
permutebilan almashtiring.Har doim shaklni chop eting (
x.shape) — birinchi batchda.Rasmni
convert("RGB")bilan o'qing — RGBA, palitra, kulrangni bir xillashtiring.Normalizatsiya statistikasini faqat o'quvda hisoblang.
Oldindan o'rgatilgan model — o'sha mean/std.
Niqob va yorliqlar uchun
NEAREST.Yangi g'oyani avval sintetik to'plamda tekshiring.
9. Amaliy topshiriq
Vazifa 1: Bashorat qiling
1. # 224x224 rangli rasmda nechta son bor?
2. # np.uint8(250) + np.uint8(10) ning natijasi?
3. # (H, W, C) -> (C, H, W) qaysi funksiya bilan?
4. # PIL img.size nimani qaytaradi?
5. # load_digits images shakli?
6. # load_digits qiymatlar oralig'i?
7. # kulrang batch (N, H, W) ni Conv2d uchun qanday tayyorlaysiz?
8. # kanal bo'yicha mean uchun qaysi dim?
9. # mean (3,) ni (N, 3, H, W) dan ayirish uchun shakl?
10. # niqobni kichraytirishda qaysi usul?
11. # PNG va JPEG ning asosiy farqi?
12. # uint8 va float32 xotira nisbati?Javoblar
- 224 × 224 × 3 = 150 528
- 4 (260 - 256)
permute(2, 0, 1)(W, H)(1797, 8, 8)- 0..16
x.unsqueeze(1)→(N, 1, H, W)dim=(0, 2, 3)view(1, 3, 1, 1)NEAREST- PNG yo'qotishsiz, JPEG yo'qotishli
- 1 : 4
Vazifa 2: Xatolarni tuzating
1. t = torch.from_numpy(rasm).reshape(3, h, w)
2. h, w = img.size
3. yorqin = rasm + 80 # rasm uint8
4. x_norm = (x - x.mean()) / x.std() # x: (N, 3, H, W), kanallar har xil
5. niqob = niqob.resize((32, 32)) # niqob - sinf raqamlariJavoblar
1. t = torch.from_numpy(rasm).permute(2, 0, 1)
2. w, h = img.size
3. yorqin = np.clip(rasm.astype(np.int16) + 80, 0, 255).astype(np.uint8)
4. orta, std = x.mean(dim=(0, 2, 3)), x.std(dim=(0, 2, 3))
x_norm = (x - orta.view(1, 3, 1, 1)) / std.view(1, 3, 1, 1)
5. niqob = niqob.resize((32, 32), Image.Resampling.NEAREST)Vazifa 3: Massiv
Modellang:
- Shakl va tur
- Piksel va kanal
uint8tuzog'ipermutevareshape
Vazifa 4: PIL
Modellang:
(W, H)va(H, W)- PNG va JPEG
- Resize usullari
- Kulrang va kesish
Vazifa 5: Normalizatsiya
Modellang:
load_digits- Histogram
- Batch shakli
- Kanal bo'yicha mean/std
Vazifa 6: Sintetik to'plam
Modellang:
- Doira va kvadrat
- Tasodifiy joy
- Piksel bazaviysi
- Kosinus o'xshashlik
Vazifa 7: O'ylash
Hamkasbingiz aytdi: "Rasmni flatten qilib, 20-qismdagi MLP ga beramiz — u universal approksimator, hammasini o'zi o'rganadi. Konvolyutsiyaga nima hojat?" 4-misol natijalariga tayanib nima deysiz?
Javob
Qisqa javob: nazariy jihatdan MLP istalgan funksiyani ifodalay oladi, lekin ma'lumot yetarli bo'lsa. Rasmlarda muammo — ifodalash emas, o'rganish uchun kerak bo'ladigan misollar soni.
4-misol nimani ko'rsatdi:
Shakl doim markazda bo'lganda, piksellardagi logistik regressiya 200 ta misolda ham xatosiz ishladi. Shakl tasodifiy joyda bo'lganda esa 1000 ta misolda ham aniqlik 0.7 atrofida qoldi. Sabab: joyi boshqa ikki kvadrat umumiy piksellarga deyarli ega emas (kosinus 0.08). Piksel modeli uchun "chap-yuqoridagi kvadrat" va "o'ng-pastdagi kvadrat" — butunlay boshqa naqshlar. U har bir joy uchun kvadratni alohida o'rganishi kerak.
Hisob:
16x16 rasmda shakl ~ 100 xil joyda turishi mumkin
MLP har joy uchun alohida misollar ko'rishi kerak
224x224 rasmda joylar soni - o'n minglab
+ o'lcham, burilish, yorug'lik o'zgarishlari
-> kerakli ma'lumot hajmi portlaydiKonvolyutsiya nima beradi:
- Og'irlik baham ko'rish — bitta kichik filtr (masalan, 3×3) rasmning hamma joyida qo'llanadi. "Burchak" ni bir joyda o'rgangan filtr uni hamma joyda taniydi.
- Lokal bog'lanish — har chiqish faqat kichik qo'shnichilikka qaraydi; qo'shni piksellar orasidagi bog'lanish tabiiy ravishda hisobga olinadi.
- Parametrlar keskin kam — 224×224×3 kirishdan 1000 neyronli
Linear150 million parametr, 3×3 filtrli 64 kanalliConv2desa 1 792 parametr.
Amaliy tavsiya:
# 1. Avval piksel bazaviysi (logistik regressiya yoki MLP) - taqqoslash uchun
# 2. Keyin kichik CNN - bir xil bo'linish, bir xil metrika
# 3. Farqni o'lchang: CNN ning ustunligi aynan siljish ko'p bo'lganda kattaHamkasbga javob:
"MLP buni ifodalay oladi, lekin o'rganish uchun har joydagi har shakl misolini ko'rishi kerak. Sintetik to'plamda shakl joyi o'zgarganda piksel modeli 0.7 da qoldi. Konvolyutsiya bitta filtrni hamma joyda ishlatadi — shu sabab u kam ma'lumotda ham siljishga chidamli. Keling, ikkalasini bir xil bo'linishda solishtiramiz."
Nimani mustahkamlaydi: 2.1, 2.3, 2.6-bo'limlar.
Xulosa
Bu darsda rasmni sonlar massivi sifatida ko'rdik.
Eng muhim uch fikr:
Rasm —
(H, W, C)massiv, torch uchun(C, H, W). 1-misolda 6×8 rangli rasm 144 ta sondan iborat bo'ldi vapermute(2, 0, 1)uni to'g'ri(3, 6, 8)ga o'tkazdi,reshape(3, H, W)esa kanallarni aralashtirib yubordi.uint8ustida arifmetika jim buziladi: 200 + 100 = 44, 100 - 200 = 156. Shuning uchun hisobdan oldinfloat32ga o'tiladi — bu 4 barobar ko'p xotira.Fayl bilan PIL, normalizatsiya kanal bo'yicha. 2-misolda PNG rasmni aynan qaytardi, JPEG esa
quality=95da ham piksellarni o'rtacha 5.34 ga (keskin chegaralarda 102 gacha) o'zgartirdi.NEARESTyangi rang yaratmadi (3051 ta noyob rang saqlandi),BILINEAResa 11 648 taga oshirdi — niqoblar uchun bu halokatli. 3-misolda kanal bo'yicha mean/std har kanalni aniq 0 va 1 ga keltirdi, bitta umumiy mean/std esa kanallar o'rtachasini 1.00, -0.07, -0.93 da qoldirdi.Piksellar joyga bog'liq belgi. 4-misolda shakl doim markazda bo'lganda piksellardagi logistik regressiya 1.000 aniqlik berdi, shakl tasodifiy joyda bo'lganda esa 1000 ta misolda ham 0.709 da qoldi. Joyi boshqa ikki bir xil kvadratning kosinus o'xshashligi atigi 0.082 — piksel modeli uchun ular boshqa rasmlar. Bu muammoni hal qiladigan vosita — konvolyutsiya.
Keyingi darsda konvolyutsiya: sirpanuvchi oynani numpy da qo'lda yozamiz, chegara va xiralashtirish yadrolarini qo'llaymiz, padding va stride bilan chiqish o'lchamini hisoblaymiz va natijani torch bilan aynan solishtiramiz.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!