Trao đổi về dự án
Trí Tuệ Nhân Tạo

Dạy mô hình học hình từ nhiễu rồi kiểm tra chúng

Huấn luyện một mô hình nhỏ tái tạo đĩa, vành khuyên hoặc dấu thập 32×32 pixel từ nhiễu. Hướng dẫn cho thấy vì sao sai số giảm không bảo đảm một hình được chấp nhận, nhờ các kiểm tra hình học tách biệt.

Bruit → Condition → Mesure. Diffusion sous contrôle

Hãy hình dung một công cụ đồ họa nhỏ với ba lựa chọn: đĩa, vành khuyên và dấu thập. Bạn chọn « vành khuyên », rồi yêu cầu một ảnh mới. Để chỉ vẽ hình này trong ứng dụng web, vài quy tắc hình học hoặc SVG là đủ. Nhưng một mạng nơ-ron có thể học cách tạo nó như thế nào khi chỉ bắt đầu từ một ảnh nhiễu?

Đó là câu hỏi của hướng dẫn này. Chúng ta sẽ tạo các ví dụ có cấu trúc đã biết, huấn luyện một mô hình khuếch tán nhỏ rồi xem nó thực sự đã học được gì. Chương trình đi kèm là một lộ trình Python dòng lệnh, không phải giao diện đồ họa: ba hình là sân thử nghiệm.

Kết quả cần được nói rõ ngay từ đầu: mô hình học dự đoán nhiễu tốt hơn, nhưng cấu hình này không tạo ra mặt nạ nào được các tiêu chí hình học của dự án chấp nhận. Đây không phải chi tiết để che sau một đường cong giảm. Chính điều đó giúp phân biệt việc huấn luyện đang tiến bộ với bộ sinh đáp ứng nhu cầu.

Tải kho lưu trữ hướng dẫn.

Những gì chúng ta xây dựng

Chúng ta xây dựng một khuếch tán có điều kiện trên ảnh 32 × 32 pixel. « Có điều kiện » nghĩa là mạng nhận thêm thông tin: lớp được yêu cầu, đĩa, vành khuyên hoặc dấu thập. Ta không gửi mô tả bằng tiếng Pháp; điều kiện ở đây là một mã lớp.

Ảnh là các mặt nạ: mỗi pixel ước lượng phần diện tích thuộc về hình. Pixel hoàn toàn ngoài hình có giá trị 0, hoàn toàn trong hình là 1, còn pixel bị biên đi qua có thể có giá trị trung gian. Chỉ có một kênh, không phải ba kênh đỏ, lục, lam.

Lộ trình tách ba thao tác. Khi huấn luyện, ta bắt đầu từ mặt nạ đã biết, thêm nhiễu đã biết và thay đổi tham số mạng để nó học dự đoán nhiễu. Khi sinh ảnh, ta giữ cố định các tham số đó và bắt đầu từ nhiễu để dần tạo ảnh. Khi đánh giá, các quy tắc bên ngoài mạng kiểm tra đầu ra.

Nhiễu, điều kiện và khử nhiễu mặt nạ

Mạng là một U-Net, mà ta sẽ giải thích vai trò. Nó cũng học làm việc khi không chỉ định lớp, nhờ một điều kiện đặc biệt gọi là lớp rỗng. Nhờ vậy ta có thể so sánh dự đoán có và không có chỉ dẫn hình, rồi kết hợp chúng bằng guidance không bộ phân loại.

Trường hợp này chủ ý là tổng hợp: nó không tạo ảnh chụp hay bản vẽ công nghiệp. Giá trị của nó nằm ở chỗ khác. Vành khuyên phải giữ một lỗ; đĩa không được biến thành các mảnh rời. Những tính chất này đặt ra câu hỏi cụ thể cho kết quả, vượt quá « ảnh này có vẻ thú vị ».

Các khái niệm cần ghi nhớ

Ảnh nhiễu vẫn là một bảng số

Tensor là bảng số có một hoặc nhiều chiều. Với mặt nạ của ta, 1 × 32 × 32 nghĩa là một kênh, cao 32 và rộng 32. Một lô ảnh thêm một chiều ở trước: số ảnh, kênh, cao, rộng.

Nhiễu Gaussian cũng là một bảng, với một giá trị ngẫu nhiên cho mỗi pixel. « Gaussian » mô tả phân bố các giá trị: chúng tập trung quanh không, với nhiều giá trị nhỏ hơn giá trị cực trị. Nhiễu này không phải lỗi tệp hay ảnh tải hỏng. Ta cố ý thêm nó và giữ các giá trị đã lấy để biết mạng phải dự đoán gì.

Trong các phép tính của mạng, pixel sạch chuyển từ [0, 1] sang [-1, 1] bằng x * 2 - 1. Vì vậy 0 thành -1, 0.5 thành 0 và 1 vẫn là 1. Nhiễu và các trạng thái nhiễu không bị giới hạn trong khoảng này.

Hành trình xuôi: thêm một khó khăn đã biết

Khuếch tán thuận biến một mặt nạ sạch, ký hiệu x0, thành mặt nạ nhiễu xt. Chỉ số t chỉ mức khuếch tán được chọn, không phải thời lượng tính bằng giây.

Công thức được dùng là:

xt = √ᾱt x0 + √(1 − ᾱt) ε

Có thể đọc công thức mà không cần bắt đầu bằng chứng minh: một phần ảnh sạch cộng một phần nhiễu. ε, chữ cái Hy Lạp « epsilon », là nhiễu đã lấy. ᾱt, « alpha gạch ngang ở bước t », điều chỉnh lượng tín hiệu được giữ lại. Căn bậc hai xác định các hệ số thực sự áp dụng.

Lấy một ví dụ tính toán minh họa, trên một giá trị duy nhất, không liên quan tới đầu ra đo được của mô hình. Với x₀ = 1, ᾱₜ = 0.64 và ε = -0.5, ta có:

xₜ = 0.8 × 1 + 0.6 × (-0.5) = 0.5.

Kết quả không có được bằng cách xóa 36 % pixel. Mỗi giá trị được biến đổi bằng hỗn hợp. Các hệ số cũng không phải hai độ mờ mà tổng phải bằng 1.

Lịch nhiễu, hay noise schedule, quy định diễn biến các hệ số này. Dự án dùng lịch cosin 100 bước. Nó do chương trình xác định, không học cùng trọng số mạng. Nguyên lý lịch cosin được trình bày trong Improved Denoising Diffusion Probabilistic Models. Tài liệu này giải thích việc chọn họ lịch, không thay thế tham số của dự án.

Mạng thực sự học gì

Khi huấn luyện, mạng nhận ảnh nhiễu, bước t và lớp. Nó phải trả về nhiễu ước lượng, không trực tiếp là một đĩa hay vành khuyên hoàn chỉnh. Ta biết đáp án đúng vì chính ta đã lấy nhiễu ε.

Hàm mất mát, hay loss, đo chênh lệch giữa dự đoán này và nhiễu đã chèn. Ở đây là MSE, mean squared error, hay sai số bình phương trung bình: tính các chênh lệch, bình phương chúng rồi lấy trung bình.

Một ví dụ sư phạm thứ hai, chỉ với hai giá trị, đủ để theo phép tính. Nhiễu thật là [0.2, -0.4], mạng dự đoán [0.1, -0.2]. Bình phương chênh lệch là 0.01 và 0.04, nên MSE là (0.01 + 0.04) / 2 = 0.025. Sai số lớn gấp đôi đóng góp lớn gấp bốn vào mất mát này.

Trọng số là các số có thể điều chỉnh của mạng. Gradient cho biết biến thiên của các số này ảnh hưởng mất mát ra sao; bộ tối ưu dùng chúng để cập nhật. Vì thế ta không lập trình quy tắc « vẽ một vành khuyên » vào mạng: ta điều chỉnh trọng số từ ví dụ nhiễu và đáp án mong đợi.

Cẩn thận với hai nghĩa của « bước ». 100 bước khuếch tán mô tả các mức nhiễu và hành trình sinh ảnh. 2 000 lần cập nhật được nêu sau đó mô tả việc huấn luyện trọng số. Chúng không phải hai cách đếm cùng một vòng lặp.

Vì sao là U-Net?

U-Net áp dụng các phép tích chập, tức các bộ lọc đã học kết hợp các giá trị lân cận. Nhánh đi xuống giảm độ phân giải để tổng hợp thông tin; nhánh đi lên khôi phục độ phân giải tinh. Các kết nối giữa hai nhánh đưa lại chi tiết không gian. Trang của tác giả U-Net giới thiệu kiến trúc hai nhánh này.

Trong src/mask_diffusion/unet.py, ConditionalUNet.forward() kết hợp biểu diễn số của thời gian và lớp, rồi cung cấp chúng cho các khối mạng. Thời gian cho biết mức nhiễu đang xử lý. Lớp cho biết họ mặt nạ được yêu cầu.

Đầu ra có cùng dạng với ảnh nhiễu: một ước lượng nhiễu cho mỗi pixel. Sau đó chương trình lấy mẫu dùng ước lượng này để tính trạng thái tiếp theo. Sự phân biệt giữa mạng và phép tính bao quanh nó là trọng tâm của DDPM.

Một điều kiện không phải bảo đảm

Lớp rỗng nghĩa là « không chỉ định lớp ». Nó không phải hình thứ tư để vẽ, cũng không phải yêu cầu tạo ảnh trống. Cùng một mạng phải có thể đưa ra ước lượng có điều kiện và không điều kiện.

Nguyên lý của Classifier-Free Diffusion Guidance là cũng học không điều kiện, bằng cách bỏ điều kiện ở một phần ví dụ, rồi kết hợp hai ước lượng khi sinh ảnh. Ta không cần thêm bộ phân loại riêng để tính kết hợp này. Dĩ nhiên điều đó không loại bỏ nhu cầu đánh giá ảnh sau khi tạo.

Trong dự án này, tổ hợp là εu + w(εc − εu), trong đó u là dự đoán không điều kiện và c là dự đoán có điều kiện. Ta sẽ đọc trong mã: w=0 giữ u, w=1 giữ c. Giá trị lớn hơn không hứa hẹn ảnh tốt hơn.

Điều kiện tiên quyết Docker và GPU

Bạn cần đọc được hàm Python, thao tác bảng dữ liệu và chạy lệnh trong terminal. Cần kiến thức Docker cơ bản; không giả định bạn đã biết mô hình khuếch tán.

Lộ trình yêu cầu Linux x86_64, Docker Compose, NVIDIA Container Toolkit, RTX 3090 24 Gio, 16 Gio RAM và 16 Gio dung lượng trống, không kể các lớp Docker dùng chung. Đây là tài nguyên nêu cho hướng dẫn này, không phải chứng minh tương thích với mọi phần cứng.

GPU, tức card đồ họa, ở đây thực hiện tính toán mạng bằng CUDA. Tuy nhiên một card Linux nhìn thấy có thể Docker không truy cập được. Vì vậy kiểm tra đi kèm không chỉ liệt kê thiết bị: nó khởi tạo CUDA trong container và chạy một phép tính. Có thể chọn card trước lộ trình bằng export GPU_DEVICE=0, hoặc dùng UUID của card, mã định danh duy nhất của nó.

Phép tính được cô lập khỏi mạng. Container dùng hệ thống tệp chỉ đọc, no-new-privileges, bỏ các capability Linux và giới hạn CPU, RAM cùng số tiến trình. Các hạn chế này giới hạn thực thi; chúng không chứng minh chất lượng ảnh. Cũng đừng nhầm tính toán cô lập với cài đặt hoàn toàn ngoại tuyến: xây Docker image và lấy phụ thuộc có thể cần truy cập mạng.

Checkpoint là bản lưu để giữ hoặc tiếp tục một trạng thái tính toán. Các artifact Torch tạo cục bộ phải đáng tin cậy: không nạp checkpoint bên thứ ba tùy ý để thay tệp thiếu. Tài liệu torch.load nhắc lại các lưu ý khi nạp.

Dữ liệu có cấu trúc đã biết

Tạo một tham chiếu trước khi yêu cầu mô hình học

Cho thí nghiệm này, ta không cần thu thập ảnh trên Internet. Ta biết cách xác định đĩa, vành khuyên và dấu thập bằng quy tắc hình học. Hàm sau tạo ví dụ huấn luyện; nó không phải bộ sinh nơ-ron sẽ được đánh giá sau đó.

Nó nhận mã label, tâm, bán kính, độ dày và góc. Mã 0, 1 và 2 lần lượt chỉ đĩa, vành khuyên và dấu thập.

def render_mask(label, center=(0.0, 0.0), radius=0.55, thickness=0.18, angle=0.0):
    # Four subpixels in each direction; occupation fraction is the antialiased pixel.
    axis = (np.arange(128) + 0.5) / 64 - 1
    x, y = np.meshgrid(axis - center[0], axis - center[1])
    u = x * np.cos(angle) + y * np.sin(angle)
    v = -x * np.sin(angle) + y * np.cos(angle)
    distance = np.sqrt(x * x + y * y)
    if label == 0:
        inside = distance <= radius
    elif label == 1:
        inside = (distance <= radius) & (distance >= radius - thickness)
    elif label == 2:
        inside = ((abs(u) <= radius) & (abs(v) <= thickness / 2)) | (
            (abs(v) <= radius) & (abs(u) <= thickness / 2)
        )
    else:
        raise ValueError("Unknown geometric class")
    return inside.reshape(32, 4, 32, 4).mean((1, 3)).astype("float32")[None]

Hàm này vừa làm gì?

axis đặt tâm của 128 khoảng nhỏ giữa -1 và 1. meshgrid tạo tọa độ của lưới 128 × 128 vị trí. Trừ center cho phép dịch hình mà không đổi quy tắc. Tọa độ u và v áp dụng phép quay dùng để tạo dấu thập.

Với đĩa, mỗi vị trí nằm trong nếu khoảng cách đến tâm không vượt radius. Với vành khuyên, khoảng cách cũng phải ít nhất bằng radius - thickness: tâm vẫn rỗng. Với dấu thập, chương trình hợp hai hình chữ nhật vuông góc. Mã lớp không biết sẽ gây lỗi thay vì chọn một hình mặc định.

Vì sao lưới 128 × 128 trong khi đầu ra là 32 × 32? Đó là để khử răng cưa, làm giảm hiệu ứng bậc thang ở đường viền. Mỗi pixel cuối được biểu diễn bởi bốn pixel con theo mỗi hướng, tức 16 pixel con. reshape(32, 4, 32, 4).mean((1, 3)) tính mức chiếm trung bình của chúng.

Ví dụ, 12 pixel con bên trong trên 16 cho 0.75. Màu xám này không phải dự đoán không chắc chắn: nó biểu thị phần diện tích trong phương pháp render. Vì vậy khử răng cưa thuộc về dữ liệu, không phải chỉnh sửa áp dụng sau đánh giá.

Cuối cùng, astype("float32") chọn lưu số trên 32 bit. [None] thêm chiều kênh. Ta có bảng 1 × 32 × 32, chưa có chiều lô ở giai đoạn này.

Tách dữ liệu trước khi huấn luyện

Khâu chuẩn bị tạo ba tập cân bằng. Bảng sau nêu rõ số lượng của dự án; số lượng theo lớp suy ra từ cân bằng đó.

Tập Số mặt nạ Mặt nạ mỗi lớp Vai trò
Huấn luyện 3 000 1 000 Cung cấp ví dụ dùng để thay đổi trọng số.
Xác thực 600 200 Đo mất mát trên ví dụ tách khỏi huấn luyện.
Kiểm thử 600 200 Giữ một tập riêng cho đánh giá mà dự án dự kiến.

Mỗi phân vùng dùng một seed khác cho các lần rút giả ngẫu nhiên. Seed khởi tạo bộ sinh; nó không phải chú thích ảnh. Mỗi ảnh cũng được băm, và mọi bản trùng bị loại trước khi cố định các tập.

Băm tạo dấu vân tay nội dung. Nó phát hiện nội dung giống hệt, không đảm bảo hai hình khác nhau không bao giờ rất gần. Trong vũ trụ nhỏ gồm đĩa, vành khuyên và dấu thập, sắc thái này quan trọng. Lớp rỗng không thêm loại mặt nạ thứ tư vào bảng: nó can thiệp vào cách điều kiện hóa mạng.

Mã trung tâm: học nhiễu, rồi dẫn đường quay lại

Sinh ảnh không phải chạy lại phép vẽ hình học

Sampler, hay chương trình lấy mẫu, bắt đầu từ trạng thái nhiễu rồi tính các trạng thái tiếp theo theo thứ tự ngược khuếch tán. Ở đây nó gọi mô hình hai lần tại mỗi bước, kết hợp hai nhiễu dự đoán và thêm nhiễu Gaussian mới, trừ bước cuối.

Nó không gọi render_mask() để lấy hình mong đợi. Nó phải tạo ảnh từ những gì mạng đã học.

def guidance(unconditional, conditional, weight):
    return unconditional + weight * (conditional - unconditional)


def reverse_step(x, epsilon, t, schedule, noise):
    beta = schedule["beta"][t].to(x.device)
    alpha = schedule["alpha"][t].to(x.device)
    abar = schedule["abar"][t].to(x.device)
    previous = schedule["abar"][t - 1].to(x.device)
    mean = (x - beta * epsilon / (1 - abar).sqrt()) / alpha.sqrt()
    variance = beta * (1 - previous) / (1 - abar)
    return mean if t == 1 else mean + variance.sqrt() * noise


@torch.no_grad()
def ancestral_sample(
    model, initial, labels, weight, schedule, generator, start=None, stop=0
):
    x = initial.clone()
    trajectory = [x[0].cpu()]
    start = start or len(schedule["beta"]) - 1
    for t in range(start, stop, -1):
        times = torch.full((len(x),), t, device=x.device, dtype=torch.long)
        u = model(x, times, torch.full_like(labels, NULL_CLASS))
        c = model(x, times, labels)
        eps = guidance(u, c, weight)
        noise = (
            torch.randn(x.shape, device=x.device, generator=generator)
            if t > 1
            else torch.zeros_like(x)
        )
        x = reverse_step(x, eps, t, schedule, noise)
        trajectory.append(x[0].cpu())
    assert torch.isfinite(x).all()
    return x, torch.stack(trajectory)

Đọc guidance bằng ba con số

Hàm đầu tiên áp dụng đúng công thức đã công bố: unconditional + weight * (conditional - unconditional).

Lấy một ví dụ số học giả định trên một giá trị nhiễu, với u = 0.2 và c = 0.5. Đây không phải đầu ra được ghi lại từ mô hình.

Trọng số dự án Tính toán Kết quả Ý nghĩa thao tác
w=0 0.2 + 0 × (0.5 - 0.2) 0.2 Giữ dự đoán không điều kiện.
w=1 0.2 + 1 × (0.5 - 0.2) 0.5 Giữ dự đoán có điều kiện.
w=3 0.2 + 3 × (0.5 - 0.2) 1.1 Kéo dài chênh lệch vượt qua dự đoán có điều kiện.

w=3 là ngoại suy, không phải trộn 75 % / 25 %. Giá trị 1.1 không có nghĩa « 110 % độ tin cậy »: ta xử lý ước lượng nhiễu, không phải xác suất. Nếu hai nhánh trả cùng một ước lượng, chênh lệch bằng không và thay w không đổi tổ hợp.

Một bẫy ký hiệu trong tài liệu tham khảo. Bài của Ho và Salimans viết tổ hợp là (1 + w_article) × c - w_article × u. Quy ước mã của ta tương ứng w_projet = 1 + w_article. Do đó hãy giữ giá trị và công thức dự án cùng nhau: w=0 trong hướng dẫn này đúng là nhánh không điều kiện, còn w=0 trong công thức bài báo là nhánh có điều kiện.

Đọc một bước quay lại mà không lạc mạch

reverse_step() nhận trạng thái hiện tại x, nhiễu dự đoán epsilon và số bước. Lịch cung cấp beta, alpha và abar. Trong ký hiệu khuếch tán này, alpha bằng 1 - beta và abar là tích các alpha tới bước đang xét. previous lấy giá trị tích lũy ở bước trước.

Dòng mean = ... hiệu chỉnh trạng thái từ nhiễu dự đoán và hệ số lịch. Dòng variance = ... quy định biên độ nhiễu ngẫu nhiên dùng để lấy trạng thái tiếp theo. Nhiễu dự đoán epsilon và nhiễu mới lấy noise vì thế không cùng vai trò. Cái đầu đến từ mạng; cái sau duy trì một bước ngẫu nhiên trong sampler này.

Hàm trả về nhiễu mới này khi t > 1. Khi t == 1, nó chỉ trả mean. Thêm nhiễu trên đường quay lại có thể có vẻ nghịch lý, nhưng chương trình không đảo ngược lịch sử chính xác của một ảnh riêng: nó dựng một quỹ đạo sinh theo quy tắc lấy mẫu.

Theo dõi vòng lặp và những gì nó giữ

initial.clone() tạo trạng thái làm việc mà không ghi đè bảng do lời gọi cung cấp. Mỗi bước, times chứa cùng chỉ số t cho mọi ảnh trong lô. Các lời gọi u = model(...) và c = model(...) dùng cùng một mạng, lần đầu với lớp rỗng rồi với các lớp yêu cầu.

Trong đoạn này, hai lời gọi đều diễn ra cả với w=0 hay w=1: mã không có lối tắt cho các giá trị đó. Với hành trình đủ 100 bước, đó là 200 lần gọi mạng cho mỗi lô, không phải 200 lần cập nhật trọng số.

Decorator @torch.no_grad() tắt theo dõi gradient trong lúc sinh này. Không có tính mất mát huấn luyện hay cập nhật optimizer. Tuy nhiên decorator này không thay cho model.eval() nếu cần: PyTorch phân biệt chế độ đánh giá với theo dõi gradient. Đoạn trích không cho biết lời gọi chuẩn bị chế độ mạng thế nào.

Cũng hãy nhìn trajectory.append(x[0].cpu()). Quỹ đạo chỉ giữ ảnh đầu tiên của lô, trên CPU. Giá trị cuối x chứa mọi ảnh của lô. Với điểm đầu 100 và stop=0, vòng lặp xử lý 100, 99, …, 1, và quỹ đạo chứa trạng thái đầu cộng 100 trạng thái tính được, tức 101 trạng thái.

Assertion cuối torch.isfinite(x).all() đòi các giá trị số hữu hạn: không NaN, không vô cực. Nó không kiểm tra viền, lỗ, cũng không kiểm tra giá trị còn trong [0, 1]. Một bảng hợp lệ về số chưa phải một hình hợp lệ.

Cùng nhiễu ban đầu: cần thiết nhưng chưa đủ

Giao thức so sánh w=0, w=1 và w=3 với cùng nhiễu ban đầu. Điều này cần thiết để không quy cho trọng số guidance một khác biệt đã sinh ra ngay từ đầu.

Nhưng vòng lặp cũng rút nhiễu mới bằng generator. Để cô lập hoàn toàn ảnh hưởng của w trong so sánh ghép cặp, cũng phải tái tạo cùng chuỗi lần rút trung gian. Sampler nhận generator này; nó không tự khởi tạo lại. Vì vậy hai lời gọi liên tiếp với cùng đối tượng generator có thể tiêu thụ hai phần khác nhau của chuỗi.

Chi tiết này suy ra từ mã hiển thị: chỉ giữ initial không đủ để chứng minh đã kiểm soát mọi lần rút. Khởi tạo và lưu generator thuộc về mã gọi trong toàn bộ dự án.

Câu hỏi tương tự xuất hiện khi tiếp tục. Với start=100 và stop=50, vòng lặp xử lý các bước 100 đến 51 và trả trạng thái ở mức 50. Tiếp tục từ đó với start=50 cũng đòi hỏi tìm lại trạng thái ngẫu nhiên tương ứng, cùng mô hình và cùng lịch. Tài liệu PyTorch về khả năng tái lập giải thích vì sao chỉ cố định seed không bảo đảm kết quả giống nhau trong mọi môi trường.

Đo việc học bằng bài xác thực cố định

Khi học, chương trình rút thời gian, thêm ε và tối thiểu hóa sai số dự đoán ε. Trái lại, để so sánh hai trạng thái mạng, phép xác thực sau dùng thời gian và nhiễu được cố định trước. Tương tự dùng cùng dữ liệu kiểm thử sau mỗi sửa đổi ứng dụng.

def validation_mse(model, config):
    assert (
        sha("outputs/validation-noise.npz")
        == read_json("outputs/validation-lock.json")["sha256"]
    )
    data = np.load("outputs/validation.npz")
    fixed = np.load("outputs/validation-noise.npz")
    total = 0.0
    model.eval()
    with torch.no_grad():
        for i in range(0, 600, 64):
            x = torch.from_numpy(data["images"][i : i + 64]).cuda() * 2 - 1
            t = torch.from_numpy(fixed["times"][i : i + 64]).cuda()
            eps = torch.from_numpy(fixed["noise"][i : i + 64]).cuda()
            y = torch.from_numpy(data["labels"][i : i + 64]).cuda()
            noisy = sample_noisy_image(
                x, t, eps, cosine_schedule(config["diffusion_steps"])
            )
            total += float((model(noisy, t, y) - eps).square().double().sum())
    model.train()
    return total / (600 * 32 * 32)

Assertion đầu so sánh dấu vân tay SHA-256 của outputs/validation-noise.npz với dấu vân tay lưu trong outputs/validation-lock.json. Nó kiểm soát tệp nhiễu cố định này; không phải kiểm tra phổ quát mọi tệp dự án.

Chương trình sau đó nạp 600 ảnh xác thực, thời gian, nhiễu và lớp. .cuda() đặt các bảng lên GPU. Biến đổi * 2 - 1 áp dụng thay đổi thang của mặt nạ đã giải thích ở trên.

model.eval() bật chế độ đánh giá, còn torch.no_grad() tắt theo dõi gradient. Sau đó sample_noisy_image() tạo mỗi đầu vào nhiễu. Ta so sánh model(noisy, t, y) với eps, không phải ảnh sạch x: điểm số luôn liên quan tới nhiễu.

Vòng lặp tiến theo lô 64. Với 600 ảnh, có chín lô đầy và một lô cuối 24. Mã cộng sai số bình phương rồi chia cho 600 × 32 × 32 = 614 400 giá trị pixel. Nó không lấy trung bình của trung bình các lô, cách sẽ cho trọng số quá lớn cho lô cuối nhỏ hơn.

.double() chuyển sai số bình phương trước khi cộng; nó không biến toàn bộ mạng thành mô hình tính chính xác kép. Cuối cùng model.train() đưa mạng trở lại chế độ huấn luyện.

Các hằng 600, 32 và 32 thuộc giao thức. Nếu bạn đổi kích thước tập xác thực hay ảnh trong thí nghiệm khác, phép tính này phải thay đổi theo dữ liệu. Trong lộ trình tham chiếu, hãy giữ nguyên chúng.

Cài đặt và kiểm tra môi trường

Giải nén kho lưu trữ, rồi mở terminal ở thư mục gốc. Mọi lệnh sau đều xuất phát từ thư mục đó. Hãy dùng vùng giải nén dành riêng cho thí nghiệm để không trộn tệp với dự án khác.

Bắt đầu bằng việc chuẩn bị thư mục, kiểm tra Compose, xây image và thử môi trường:

sh scripts/setup.sh
docker compose config --quiet
docker compose build acquire gpu
docker compose run --rm acquire python scripts/acquire.py --config configs/minimum.yaml
docker compose run --rm gpu python scripts/check_environment.py --config configs/minimum.yaml
docker compose run --rm gpu python -m pytest -q

setup.sh chuẩn bị dự án. docker compose config --quiet kiểm tra cấu hình Compose mà không in ra; vì vậy không có đầu ra không tự nó là bất thường. build acquire gpu xây hai service có tên đó.

acquire kiểm tra các khóa của dự án. Không có mô hình tiền huấn luyện nào được tải xuống trong hướng dẫn này: mặt nạ sẽ do prepare tạo, và mạng được huấn luyện trong lộ trình. Đừng chuyển sang đây bước lấy trọng số của hướng dẫn mô hình ngôn ngữ.

check_environment.py phải xác nhận GPU thực sự dùng được từ container. Lệnh pytest sau đó chạy các kiểm thử dự án. Hãy sửa mọi lỗi trước huấn luyện; kết quả sinh ảnh không thể bù cho kiểm tra môi trường còn thiếu.

Bây giờ hãy chuẩn bị ảnh, rồi quan sát sự biến đổi của chúng bởi nhiễu:

docker compose run --rm gpu python -m mask_diffusion.cli prepare --config configs/minimum.yaml
docker compose run --rm gpu python -m mask_diffusion.cli inspect-noise --config configs/minimum.yaml

Sau prepare, các tập được công bố là 3 000 mặt nạ huấn luyện, 600 xác thực và 600 kiểm thử. inspect-noise cho phép quan sát hành trình xuôi. Ở đây, hãy xem một hình đã biết trở thành trạng thái nhiễu; bạn chưa quan sát khả năng sinh hình của mô hình.

Huấn luyện, tiếp tục và đánh giá

Chạy lộ trình tham chiếu

Trước hết ta kiểm tra kích thước và gradient của U-Net, rồi huấn luyện mô hình. Gradient hữu ích để kiểm tra phép tính thực sự có thể làm tham số thay đổi; chỉ thấy một ảnh đầu ra không đủ để xác lập điều đó.

Chuỗi lệnh cũng gồm một lần dừng chủ ý và tiếp tục. Một huấn luyện tham chiếu đầy đủ dùng để so sánh với huấn luyện đã dừng rồi tiếp tục. Vì vậy đây không phải chỉ dẫn dừng thủ công lệnh đầu tiên.

docker compose run --rm gpu python -m mask_diffusion.cli inspect-model --config configs/minimum.yaml
docker compose run --rm gpu python -m mask_diffusion.cli train --config configs/minimum.yaml
docker compose run --rm gpu python -m mask_diffusion.cli train --config configs/minimum.yaml --stop-after 1000 --run-name interrupted
docker compose run --rm gpu python -m mask_diffusion.cli train --config configs/minimum.yaml --resume artifacts/checkpoints/interrupted/state.pt --run-name resumed
docker compose run --rm gpu python -m mask_diffusion.cli compare-resume --config configs/minimum.yaml
docker compose run --rm gpu python -m mask_diffusion.cli sample --config configs/minimum.yaml
docker compose run --rm gpu python -m mask_diffusion.cli evaluate --config configs/minimum.yaml

inspect-model đi trước các tính toán dài. Lệnh train đầu tạo huấn luyện tham chiếu. Lời gọi --stop-after 1000 --run-name interrupted tạo nhánh bị dừng. Lời gọi sau tiếp tục checkpoint artifacts/checkpoints/interrupted/state.pt dưới tên resumed. compare-resume đối chiếu các trạng thái thu được.

sample sau đó dùng mô hình tạo đầu ra, còn evaluate áp dụng các phép đo. Các con số dưới đây là của bản tái lập r1 đã được người đọc xác thực. Chúng không phải ngưỡng cần đạt bằng cách đổi thiết lập tới khi có hình đẹp.

Xác định mỗi phép đo có thể cho biết điều gì

MSE xác thực so sánh nhiễu dự đoán với nhiễu tham chiếu cố định. Nó trả lời « mạng có dự đoán nhiễu tốt hơn trong bài này không? ». Nó không trả lời trực tiếp « yêu cầu vành khuyên có cho ra vành khuyên không? ».

Sự phù hợp hình học xem xét ảnh đã sinh bằng quy tắc tách biệt khỏi mạng. Dự án dùng kiểm tra thành phần, lỗ và diện tích. Thành phần là vùng liên thông; lỗ là vùng nền bị hình bao kín; diện tích đo độ rộng theo quy tắc validator.

Để hình dung, hãy nghĩ về các hình lý tưởng, không phải mô tả đầy đủ classifier dự án. Đĩa đặc, vành khuyên có lỗ, dấu thập có nhánh. Chỉ số lỗ không đủ phân biệt dấu thập với đĩa. Quyết định chính xác phụ thuộc quy tắc validator, nhất là cách diễn giải pixel trung gian. Ngưỡng không nêu trong các trích đoạn bài viết: cần đọc mã đánh giá trong kho lưu trữ, không tự bịa thêm.

Hạng unknown nghĩa là không gán được cho bất kỳ lớp hình học nào trong ba lớp. Nó không phải thành công thứ tư. Một ảnh trông hơi tròn bằng mắt vẫn có thể bị tiêu chí đã chọn từ chối.

Khoảng cách tới train, tức tập huấn luyện, là MSE giữa một đầu ra và mặt nạ huấn luyện gần nhất. Ở đây ta so sánh ảnh với nhau; không phải MSE dự đoán nhiễu. Khoảng cách nhỏ chỉ cho thấy gần theo phép đo này, không tự chứng minh ghi nhớ. Ngược lại, khoảng cách lớn không chứng minh sáng tạo thú vị: nhiễu không hình dạng có thể rất xa ví dụ.

Cuối cùng, đa dạng liên quan tới sự phong phú đầu ra. Cần đọc nó cùng sự phù hợp và độ gần dữ liệu huấn luyện. Ba trăm đầu ra khác nhau nhưng đều không hợp lệ không làm thành bộ sinh hình đa dạng có ích. Bài này không cung cấp giá trị số về đa dạng hay khoảng cách tới train: vì thế ta không suy ra kết luận về ghi nhớ.

Kết quả quan sát được

Huấn luyện và tiếp tục hoạt động trong lộ trình được đo

Huấn luyện chính thực hiện 2 000 lần cập nhật và xử lý 128 000 ví dụ với việc lặp lại ảnh của tập huấn luyện. Đây không phải 128 000 mặt nạ riêng biệt: cùng ảnh sạch có thể được trình bày nhiều lần, với thời gian và nhiễu huấn luyện được rút ra.

MSE xác thực giảm từ 1.1551 xuống 0.0211. Vì thế mạng tiến bộ trên nhiệm vụ thực sự đo được: dự đoán nhiễu xác thực.

Hai kiểm tra liên quan đến tiếp tục. Sau khi dừng ở bước 1 000, việc tiếp tục đã tìm lại cùng tham số, với sai số tối đa bằng không. Sampler tiếp tục tại t=50 cũng cho sai số tối đa bằng không. Các đẳng thức này chỉ liên quan các so sánh trong lộ trình; chúng không hứa hẹn giống từng bit trên mọi phần cứng hay phiên bản phần mềm.

Nhưng không chiến dịch nào tạo mặt nạ phù hợp

Giao thức kết hợp hai trạng thái mạng, chưa huấn luyện và đã huấn luyện, với ba trọng số guidance. Điều này cho sáu chiến dịch, mỗi chiến dịch 300 mẫu. Bảng trình bày phân bố đó mà không thêm phép đo mới:

Trạng thái mạng Guidance Phù hợp hình học Đầu ra xếp unknown
Chưa huấn luyện w=0 0/300 300
Chưa huấn luyện w=1 0/300 300
Chưa huấn luyện w=3 0/300 300
Đã huấn luyện w=0 0/300 300
Đã huấn luyện w=1 0/300 300
Đã huấn luyện w=3 0/300 300

« Chưa huấn luyện » và « không điều kiện » không cùng nghĩa. Thứ nhất mô tả trạng thái trọng số; thứ hai mô tả không có lớp trong một dự đoán. Vì vậy mạng đã huấn luyện vẫn có thể dùng ở nhánh không điều kiện với w=0.

Kết quả khuếch tán có điều kiện

300 đầu ra của mỗi chiến dịch đều được xếp unknown. Vì vậy MSE giảm không chứng minh các ảnh sinh tôn trọng hình được yêu cầu. Sẽ gây hiểu lầm nếu chỉ trình bày đường cong mất mát, hoặc chọn vài ảnh có vẻ thuận mắt để tuyên bố bộ sinh thành công.

Những gì ta đã học

Câu trả lời tốt cho một nhiệm vụ con chưa phải kết quả cuối tốt

Với nhà phát triển web, phép tương tự là một hàm có kiểm thử đơn vị tiến bộ trong khi hành trình người dùng hoàn chỉnh vẫn thất bại. Hai quan sát không mâu thuẫn: chúng không xét cùng cấp độ hệ thống.

Ở đây, mất mát đo trung bình các sai số số học trên nhiễu. Sinh ảnh nối tiếp nhiều bước tính; đánh giá cuối đòi một hình có tính chất được chấp nhận. Ba đối tượng này phải tách biệt.

Chỉ để minh họa, không phải chẩn đoán thí nghiệm này, một đứt gãy nhỏ ở viền vành khuyên có thể làm thay đổi sự tồn tại của lỗ. Do đó vài pixel có thể đảo một tiêu chí hình học trong khi sai số trung bình ít đổi. Điều này giúp hiểu vì sao điểm của một nhiệm vụ số không tự chuyển thành phù hợp hình học.

Tái lập một thất bại vẫn là thông tin hữu ích

Cơ chế khuếch tán, các đồng nhất thức guidance và việc giữ trạng thái tiếp tục đều được kiểm tra trong lộ trình báo cáo. Đồng thời quy tắc hình học chỉ ra một giới hạn rõ: cấu hình này không tạo ra các mặt nạ nhỏ phù hợp được tìm kiếm.

Việc tiếp tục giống hệt chứng minh phép tính có thể được tiếp nối nhất quán trong điều kiện kiểm tra. Nó không chứng minh kết quả tốt. Tương tự, trọng số guidance cao hơn không tự sửa được sinh ảnh không phù hợp.

Các phép đo tổng hợp không cho phép quy thất bại về một nguyên nhân duy nhất. Nói « chỉ cần huấn luyện lâu hơn » hay « validator chắc chắn quá nghiêm » sẽ vượt quá điều đã xác lập. Đó có thể là giả thuyết cần xem riêng, với các kiểm tra riêng, không phải sửa chữa đã được xác thực.

Bài tập: nêu giả thuyết trước lệnh

Trước khi chạy bất cứ thứ gì, hãy tính guidance(u, c, 0) và guidance(u, c, 1) với giá trị bạn chọn. Cũng kiểm tra khi u và c bằng nhau: trọng số còn có thể sửa tổ hợp không?

Sau đó nêu giả thuyết: chuyển từ w=1 sang w=3, với nhiễu đầu giống nhau, sẽ giữ, giảm hay dịch chuyển đa dạng? Hãy ghi điều nào là chắc chắn đại số, như các đồng nhất thức tại trọng số 0 và 1, và điều nào là dự báo về đầu ra mạng.

Bây giờ hãy chạy bài tập đi kèm:

docker compose run --rm gpu python -m mask_diffusion.cli exercise --config configs/exercise.yaml

Đầu ra kiểm tra các đồng nhất thức biên, giữ cùng nhiễu ban đầu và báo metric của w=1 và w=3. Nó không hứa hẹn cải thiện nào.

So sánh những quan sát này với giả thuyết của bạn. Để diễn giải khác biệt là tác động riêng của trọng số, cũng hãy xét trạng thái bộ sinh nhiễu trung gian, không chỉ nhiễu xuất phát. Cuối cùng, đừng thay « nhiều biến thể thấy được hơn » bằng « nhiều hình hợp lệ hơn »: đa dạng hữu ích phải tôn trọng tiêu chí nhiệm vụ.

Điều chỉnh và đưa vào sử dụng

Trước khi biến thí nghiệm này thành ứng dụng, trước hết phải xử lý giới hạn sinh ảnh. Thêm API quanh mô hình sẽ không làm mặt nạ phù hợp hơn.

Một thí nghiệm bổ sung, tách biệt với r1, có thể xét một giả thuyết mỗi lần: ví dụ nghiên cứu ảnh hưởng thời gian huấn luyện trong khi giữ dữ liệu, lịch và validator. Cần giữ đầu ra tham chiếu, ghi nhận thay đổi và đo lại nhiễu, sự phù hợp cùng đa dạng. Bài này không công bố kết quả nào của biến thể đó. Đổi quy tắc chấp nhận sau khi xem ảnh sẽ xác định nhiệm vụ khác, không phải cải thiện đã chứng minh của mô hình cũ.

Trong sử dụng thực tế, mặt nạ tổng hợp phải được thay bằng dữ liệu được phép và có phiên bản, với chú thích cùng tiêu chí nghiệp vụ rõ ràng. Cần đo thất bại theo quần thể liên quan: kiểu ảnh, lớp hay điều kiện thu nhận tùy nhu cầu. Cũng cần theo dõi drift, tức diễn biến dữ liệu hoặc kết quả sau triển khai.

Quản lý quyền, xác thực, bảo vệ API, quan sát, sao lưu, tải và chính sách từ chối vẫn cần xây dựng. Mất mát ổn định không bao phủ bất kỳ yêu cầu nào trong số đó. Hướng dẫn cung cấp một thí nghiệm có thể đo, không phải dịch vụ sẵn sàng nhận ảnh nghiệp vụ.

Khắc phục sự cố và dọn dẹp

Nếu kiểm tra GPU thất bại, hãy kiểm tra driver và cài đặt NVIDIA Container Toolkit, rồi chạy lại check_environment.py bằng lệnh ở trên. Card hiện trên máy chủ nhưng không truy cập được trong container trước hết báo vấn đề tích hợp Docker/GPU, không phải lý do sửa U-Net.

Nếu kiểm tra tệp hoặc checkpoint thất bại, hãy giữ thông báo và kiểm tra các tệp thuộc cùng lần chạy, cùng cấu hình. Đừng vượt qua lỗi toàn vẹn bằng checkpoint bên ngoài không xác định.

Nếu mất mát giảm nhưng sự phù hợp vẫn bằng không, bạn không nhất thiết chạy sai chương trình: đó chính là giới hạn quan sát trong phiên bản này. Hãy xem trạng thái và phép đo mà không biến kỳ vọng thành công thành kết quả được công bố.

Để tạo hình, kiểm tra checkpoint, xem trước dọn dẹp rồi dừng service, dùng các lệnh của lộ trình:

docker compose run --rm gpu python lesson/figures/plot.py
docker compose run --rm gpu python scripts/check_checkpoints.py
docker compose run --rm gpu python scripts/clean.py --scope generated --dry-run
docker compose down --remove-orphans

plot.py tạo hình; nó không phải một lần huấn luyện mới. check_checkpoints.py kiểm tra checkpoint. Tùy chọn --dry-run nghĩa là dọn dẹp chỉ được xem trước: nó không xóa tệp đã tạo.

Cuối cùng, docker compose down --remove-orphans dừng và gỡ tài nguyên thuộc dự án Compose. Tệp cục bộ được giữ trong lộ trình này. Không cần thực hiện dọn Docker toàn cục, điều sẽ ảnh hưởng thí nghiệm khác.

Nguồn gốc và tài liệu chính

Kết quả bài viết này là của bản tái lập r1 của dự án tải về. Các tham khảo sau giúp đào sâu cơ chế và lưu ý; chúng không phải xác thực độc lập đầu ra của dự án.

Khuếch tán và kiến trúc. Ho, Jain et Abbeel — Denoising Diffusion Probabilistic Models mô tả khung DDPM. Ho et Salimans — Classifier-Free Diffusion Guidance trình bày việc kết hợp dự đoán có và không điều kiện; chú ý quy ước trọng số đã giải thích ở trên. Nichol et Dhariwal — Improved Denoising Diffusion Probabilistic Models trình bày nhất là lịch cosin. Trang tác giả U-Net giải thích kiến trúc gốc.

Tính toán và tiếp tục. Tài liệu PyTorch phân biệt chế độ đánh giá và theo dõi gradient, nêu giới hạn khả năng tái lập và ghi tài liệu nạp bằng torch.load. Các trang chung này không thay thế phiên bản và thiết lập được giữ trong kho lưu trữ.

Môi trường. Tài liệu Docker Compose — hỗ trợ GPU và hướng dẫn cài NVIDIA Container Toolkit nêu chi tiết quyền truy cập card từ container.

Tải kho lưu trữ và theo toàn bộ lộ trình: mở hướng dẫn.

Chia sẻ bài viết