Kiến trúc mạng học sâu: từ nền tảng đến LLM·Chương 10 — GAN: học sinh dữ liệu bằng đối kháng

Chế độ xem

GAN: hai mạng học bằng đối kháng

Bài này trả lời một câu hỏi hẹp: hai mạng neural, tức hai hàm nhiều tầng cùng học từ dữ liệu, tạo mẫu mới giống dữ liệu thật bằng cách nào khi ta không viết sẵn xác suất của từng điểm. Một mạng đóng vai người làm giả. Mạng kia đóng vai người giám định. Hai vai cùng học. Hàng giả khá hơn thì người giám định phải nhìn kỹ hơn. Người giám định khá hơn thì người làm giả nhận tín hiệu mới để sửa.

Bài không chữa ca huấn luyện hỏng, không chọn chỉ số chấm một lưới ảnh, và không xếp họ kiến trúc theo nhu cầu. Người chưa học cách nén một mẫu rồi dựng lại vẫn theo được. Cách lấy mẫu ở đây chỉ có một bước: rút một dãy số ngẫu nhiên, đưa qua mạng làm giả, và nhận một mẫu.

Đường đọc nhanh. Đọc mục 1, 2 và 3, rồi phần mang đi. Hai mục Đào sâu nằm ngoài đường đó: trò chơi minimax, loss không bão hòa, nghiệm của mạng giám định, và khoảng cách Jensen–Shannon.

1. Người làm giả và người giám định cùng học

Hãy hình dung một xưởng làm giả và một người giám định cùng lên tay nghề. Xưởng nhận nguyên liệu vô nghĩa, ở đây là một dãy số lấy ngẫu nhiên, rồi tạo ra một mẫu. Người giám định nhìn hai nguồn: mẫu lấy từ tập huấn luyện, và mẫu xưởng vừa tạo. Người giám định chấm mẫu nào có vẻ thật. Nếu người giám định chỉ ra được điểm giả, xưởng sửa cách tạo. Nếu xưởng qua mặt được, người giám định phải học dấu hiệu mới. Không bên nào giữ một bộ quy tắc cố định.

Mạng đóng vai xưởng là mạng sinh. Công việc của nó là biến nhiễu thành một mẫu có dạng của dữ liệu, chẳng hạn một ảnh. Mạng này được gọi là Generator. Ký hiệu của nó là GG. Mạng đóng vai người giám định là mạng phân biệt. Công việc của nó là nhận một mẫu và trả về một điểm: mẫu có vẻ đến từ dữ liệu thật, hay do mạng sinh vừa tạo. Mạng này được gọi là Discriminator. Ký hiệu của nó là DD.

Nhiễu đi vào mạng sinh là một véctơ, tức một dãy số, ký hiệu zz. Mẫu do mạng sinh tạo ra được viết x^\hat{x}. Bước sinh, nói trước bằng lời, là: lấy nhiễu, đưa qua Generator, nhận một mẫu giả. Phân phối của dữ liệu thật, tức quy luật mật độ của các mẫu huấn luyện, viết pdatap_{\mathrm{data}} trong sơ đồ dưới. Sơ đồ chỉ ghi lại hai vai vừa mô tả.

Một hình minh họa có giấy phép mở cho cơ chế này:

Minh họa cơ chế GAN

Nguồn: Mtanti, Wikimedia Commons, giấy phép CC BY-SA 4.0. Hình thể hiện noise → Generator → ảnh giả, trong khi Discriminator đồng thời nhận ảnh thật và ảnh giả.

Tài liệu Deep Learning Cơ Bản đi kèm khóa học cũng dùng hình ảnh người tạo mẫu giả và người kiểm định để giải thích trực quan hai vai trò này:

Minh họa Generator và Discriminator từ tài liệu tham khảo

Nguồn: Nguyễn Thanh Tuấn, Deep Learning Cơ Bản, v2 (2020), tr. 271. Hình được trích từ file PDF tham khảo của khóa học.

Hai mạng không học độc lập. Chúng là một hệ: mỗi lần một bên mạnh lên, bên kia nhận một bài toán hơi khác. Dữ liệu giả mà Discriminator thấy hôm nay khác với dữ liệu giả vài nghìn bước sau. Đây là khác biệt với một bộ phân loại hai lớp thông thường, nơi tập mẫu âm có thể được đóng băng trước.

GAN, viết đủ là Generative Adversarial Network, mạng sinh đối kháng, là tên của trò chơi này. Goodfellow và cộng sự đưa ra khung ấy năm 2014 [1].

2. Mẫu mới bắt đầu từ nhiễu

Ta muốn các mẫu mới mang đặc điểm của tập huấn luyện. Nếu tập là chữ số viết tay, mục tiêu không phải là nhả lại đúng một ảnh số 7 đã lưu. Mục tiêu là một số 7 mới, vẫn giống chữ người viết, mà không phải bản sao của một dòng trong tập.

GAN làm việc này mà không viết công thức xác suất p(x)p(x) cho từng điểm xx. Nó thuộc nhóm mô hình sinh ẩn, tiếng Anh implicit generative model: ta học một quy trình tạo mẫu, thay vì bắt buộc tính được xác suất của từng mẫu. Muốn một mẫu, ta rút nhiễu rồi đẩy qua Generator. Không có mạng nén mặc định, và không cần dựng lại đúng ảnh đầu vào.

Nhiễu thường được rút từ một phân phối đơn giản pzp_z, tức quy luật lấy zz. Phân phối đó có thể là phân phối chuẩn N(0,I)\mathcal{N}(0,I), hình chuông độc lập quanh gốc cho từng tọa độ, hoặc phân phối đều trên một đoạn. Câu này đi trước ký hiệu rút mẫu:

z∼pz(z)z \sim p_z(z)

Véctơ zz không chứa sẵn mắt, mũi, nét số 7 hay màu tóc. Nó là một tọa độ trong không gian tiềm ẩn, latent space: không gian các nhiễu mà Generator biết đọc. Generator học một ánh xạ từ tọa độ đó sang mẫu, với bộ tham số θ\theta:

Gθ:z↦x^G_{\theta}: z \mapsto \hat{x}

Sau khi học, các vùng khác nhau của không gian tiềm ẩn thường ứng với các kiểu mẫu khác nhau. Một phép thử đơn giản là nội suy. Lấy hai nhiễu z1z_1 và z2z_2, đi dọc đoạn nối chúng bằng một hệ số α\alpha từ 0 đến 1, rồi đưa từng điểm qua Generator. Nếu Generator đã học một cấu trúc, mẫu thường đổi tương đối mượt. Nếu nó chỉ ghi vài mẫu, ảnh có thể đứng yên hoặc nhảy giữa vài khuôn. Công thức của đoạn nối là:

z(α)=(1−α)z1+αz2,α∈[0,1]z(\alpha) = (1-\alpha)z_1 + \alpha z_2, \qquad \alpha \in [0,1]

Trong GAN gốc, điểm của Discriminator nằm trong khoảng mở từ 0 đến 1. Có thể đọc điểm đó như xác suất mà DD gán cho việc mẫu đến từ dữ liệu thật. Tham số của Discriminator ký hiệu ϕ\phi:

Dϕ(x)∈(0,1)D_{\phi}(x) \in (0,1)

Khi cập nhật Discriminator, mẫu thật được kéo về điểm cao, và mẫu G(z)G(z) được kéo về điểm thấp. Discriminator không phải cảnh sát đã tốt nghiệp. Nó đổi liên tục vì Generator đổi. Vì vậy độ chính xác của nó, tức tỉ lệ mẫu bị chấm đúng thật hoặc đúng giả, không phải một thước đứng yên của chất lượng ảnh.

3. Một vòng cập nhật tách làm hai pha

Hai mạng không trượt trên cùng một loss trong một lần truyền ngược. Loss là hàm phạt mà thuật toán tối ưu cố làm nhỏ, hoặc, trong trò chơi gốc, một điểm số mà một bên cố làm lớn. Ta tách một vòng thành hai pha. Pha đầu chỉ sửa Discriminator. Pha sau chỉ sửa Generator. Gradient là hướng đổi tham số làm loss thay đổi nhanh nhất. Ở pha đầu, gradient phải dừng trước Generator. Ở pha sau, gradient phải đi xuyên Discriminator để Generator biết nên sửa mẫu theo hướng nào, nhưng bước cập nhật của Discriminator không được thực hiện.

Batch ở sơ đồ là một nhóm mẫu được dùng trong một bước. Pseudocode dưới viết pha Discriminator bằng BCE, binary cross-entropy, loss thông dụng của phân loại hai lớp: phạt khi điểm lệch nhãn đúng. Nhãn mẫu thật là 1, nhãn mẫu giả ở pha này là 0. Ở pha Generator, nhãn mục tiêu trên mẫu giả là 1, vì mạng sinh muốn được tin là thật.

for real in dataloader:
    # 1) Update D
    z = sample_noise(batch_size)
    fake = G(z).detach()          # không cho gradient đi vào G ở bước này

    loss_d = bce(D(real), ones) + bce(D(fake), zeros)
    opt_d.zero_grad()
    loss_d.backward()
    opt_d.step()

    # 2) Update G
    z = sample_noise(batch_size)
    fake = G(z)

    loss_g = bce(D(fake), ones)   # G muốn D coi fake là real
    opt_g.zero_grad()
    loss_g.backward()
    opt_g.step()

detach cắt đường gradient. Optimizer là thuật toán thực hiện một bước sửa tham số. Hai lỗi dễ gặp nằm đúng chỗ này. Khi sửa DD, nếu quên cắt gradient, bước đó vô tình sửa cả GG. Khi sửa GG, nếu chặn gradient ngay tại DD, mạng sinh không biết Discriminator đang phản đối điều gì. Và nếu optimizer của DD vẫn step trong pha sau, ta đang sửa người giám định bằng chính loss của người làm giả.

Nói bằng lời, trước mọi công thức ở mục sau: Discriminator muốn điểm cao trên mẫu thật và điểm thấp trên mẫu giả. Generator muốn mẫu của nó được chấm cao. Mục tiêu cân bằng không đổi giữa các cách viết loss. Cách viết đổi là để gradient còn dùng được lúc Generator còn yếu.

Khi hai bên cân nhau, Discriminator có thể chấm gần như đoán mò, quanh một nửa. Mức đúng ấy vẫn có thể đến từ mạng quá yếu, từ optimizer lỗi, hoặc từ cách đo sai. Nó chưa chứng minh mẫu đã phủ dữ liệu thật. Phần đào sâu viết điều kiện lý tưởng của phát biểu này.

4. Đào sâu: trò chơi minimax và loss không bão hòa

GAN gốc là một trò chơi minimax hai người [1]. Minimax nghĩa là một bên cực đại một điểm số, bên kia cực tiểu chính điểm số đó. Điểm V(D,G)V(D,G) tăng khi Discriminator chấm cao mẫu thật và chấm thấp mẫu giả. Kỳ vọng E\mathbb{E} dưới đây là trung bình theo phân phối đang được lấy mẫu: hoặc mẫu thật từ pdatap_{data}, hoặc nhiễu từ pzp_z. Logarit của điểm thưởng cho điểm gần 1 trên mẫu thật, và log⁡(1−D(G(z)))\log(1-D(G(z))) thưởng cho điểm gần 0 trên mẫu giả. Discriminator cực đại tổng đó. Generator cực tiểu tổng đó:

min⁡Gmax⁡DV(D,G)=Ex∼pdata[log⁡D(x)]+Ez∼pz[log⁡(1−D(G(z)))]\min_G \max_D V(D,G) = \mathbb{E}_{x \sim p_{data}}[\log D(x)] + \mathbb{E}_{z \sim p_z}[\log(1-D(G(z)))]

Phần việc của riêng Discriminator là cực đại cùng một tổng. Viết tách ra cho dễ đọc:

LDvalue=Ex∼pdata[log⁡D(x)]+Ez∼pz[log⁡(1−D(G(z)))]\mathcal{L}_{D}^{value} = \mathbb{E}_{x \sim p_{data}}[\log D(x)] + \mathbb{E}_{z \sim p_z}[\log(1-D(G(z)))]

Trong code, optimizer thường đi xuống. Ta tối thiểu hóa âm của điểm số ấy, và gọi đó là loss của DD:

LD=−Ex∼pdata[log⁡D(x)]−Ez∼pz[log⁡(1−D(G(z)))]\mathcal{L}_D = -\mathbb{E}_{x \sim p_{data}}[\log D(x)] -\mathbb{E}_{z \sim p_z}[\log(1-D(G(z)))]

Theo minimax gốc, Generator chỉ đụng vào số hạng thứ hai. Nó muốn D(G(z))D(G(z)) lớn, vì khi đó log⁡(1−D(G(z)))\log(1-D(G(z))) nhỏ. Loss tương ứng là kỳ vọng của số hạng ấy:

LGminimax=Ez∼pz[log⁡(1−D(G(z)))]\mathcal{L}_{G}^{minimax} = \mathbb{E}_{z \sim p_z}[\log(1-D(G(z)))]

Goodfellow và cộng sự chỉ ra một trục trặc lúc đầu [1]. Khi Discriminator còn dễ bắt mẫu giả, D(G(z))D(G(z)) gần 0, và log⁡(1−D(G(z)))\log(1-D(G(z))) rất phẳng. Gradient gửi về Generator yếu. Thực hành thường đổi cách viết, gọi là loss không bão hòa, non-saturating loss. Generator vẫn muốn điểm D(G(z))D(G(z)) cao, nhưng loss là âm logarit của chính điểm đó, nên dốc còn dốc khi mẫu còn bị chấm thấp:

LGNS=−Ez∼pz[log⁡D(G(z))]\mathcal{L}_{G}^{NS} = -\mathbb{E}_{z \sim p_z}[\log D(G(z))]

Điểm cân bằng của trò chơi không đổi. Đổi là độ hữu ích của gradient trong giai đoạn Generator còn non.

5. Đào sâu: nghiệm của người giám định và khoảng cách Jensen–Shannon

Giữ Generator cố định. Gọi pgp_g là phân phối của mẫu do nó tạo ra. Với mỗi điểm xx, Discriminator chơi tối ưu khi chấm đúng phần mật độ thật trên tổng mật độ thật và mật độ giả. Nơi dữ liệu thật dày hơn hẳn mẫu sinh, điểm tiến về 1. Nơi hai mật độ bằng nhau, điểm là một nửa. Nghiệm đó là [1]:

DG∗(x)=pdata(x)pdata(x)+pg(x)D_G^*(x) = \frac{p_{data}(x)}{p_{data}(x)+p_g(x)}

Nếu Generator đã khớp hoàn toàn phân phối dữ liệu, hai mật độ bằng nhau tại mọi xx:

pg(x)=pdata(x)p_g(x)=p_{data}(x)

thay vào nghiệm trên thì điểm tối ưu là một nửa ở mọi nơi:

DG∗(x)=12D_G^*(x)=\frac{1}{2}

Đó là cân bằng lý tưởng: người giám định giỏi nhất cũng không hơn đoán ngẫu nhiên, vì không còn dấu hiệu thống kê để tách. Chiều ngược không thành định lý. Điểm quanh một nửa còn xảy ra khi cả hai mạng chưa học, hoặc khi Discriminator quá yếu để khai thác khác biệt đang có.

Thay chính nghiệm DG∗D_G^* vào điểm số minimax thì giá trị còn lại liên hệ với khoảng cách Jensen–Shannon. Khoảng cách này, viết JSJS, đo hai phân phối khác nhau đến mức nào. Nó bằng 0 đúng khi hai phân phối trùng nhau, và nó đối xứng theo hai vế. Sau khi người giám định đã chơi tối ưu, điểm của trò chơi bằng một hằng số cộng với hai lần khoảng cách ấy giữa dữ liệu thật và dữ liệu sinh:

V(DG∗,G)=−log⁡4+2 JS(pdata∥pg)V(D_G^*,G) = -\log 4 + 2\,JS(p_{data}\|p_g)

Vì hằng số không phụ thuộc Generator, cực tiểu trò chơi chính là kéo khoảng cách Jensen–Shannon về 0. Điều đó xảy ra đúng khi phân phối sinh trùng phân phối dữ liệu:

JS(pdata∥pg)=0⇔pg=pdataJS(p_{data}\|p_g)=0 \quad \Leftrightarrow \quad p_g=p_{data}

Generator vì vậy không được giao một công thức vẽ đẹp từng pixel. Nó được giao việc đổi phân phối mẫu cho đến khi phân phối đó khó tách khỏi dữ liệu thật. Kết quả này đứng trong điều kiện lý tưởng: Discriminator tối ưu tại chỗ, và hai phân phối được so đủ rộng. Với mạng hữu hạn, mini-batch, và hạ gradient, quỹ đạo có thể dao động, một mạng bỏ xa mạng kia, hoặc Generator lặp vài mẫu ăn điểm. Bài sau bắt đầu từ các triệu chứng nhìn thấy của các ca đó.

Đặt nhanh cạnh một cách sinh khác, để khỏi phải đã học chương trước. Autoencoder là một cặp mạng: encoder nén mẫu thành một mã, decoder dựng mã thành mẫu, và lossreconstruction phạt độ lệch giữa mẫu dựng và mẫu gốc. VAE, variational autoencoder, biến mã đó thành một phân phối để có thể rút mã mới rồi decode; hàm nó tối ưu gồm tái tạo và một số hạng kéo phân phối mã về một prior, thường viết như ELBO, cận dưới của log xác suất. GAN không bắt buộc có encoder. Mẫu mới là G(z)G(z), với zz rút từ nhiễu. Loss của nó là loss đối kháng vừa viết. Ảnh sinh một lần qua Generator thường sắc; đổi lại, trò chơi hai mạng dễ mất ổn định. Ba cột dưới chỉ để đặt cạnh, không phải một thứ tự tiến hóa.

Điểm so sánhAutoencoderVAEGAN
Thành phần chínhEncoder + DecoderEncoder xác suất + DecoderGenerator + Discriminator
Mục tiêu chínhTái tạo inputTái tạo + kéo latent về priorLàm mẫu sinh khó phân biệt với thật
Có encoder mặc định?CóCóKhông bắt buộc
Cách sinh mẫuThường không phải mục tiêu chínhLấy mẫu từ prior rồi decodeLấy zz rồi qua Generator
LossReconstructionELBOAdversarial loss
Điểm mạnh điển hìnhBiểu diễn, nénLatent có cấu trúc xác suấtMẫu ảnh sắc, sinh một lần qua mạng
Khó khăn điển hìnhChưa phải mô hình sinh đủCó thể đổi độ sắc lấy cấu trúc latentHuấn luyện bất ổn, lặp vài kiểu mẫu

6. Những điều mang đi

  • Hai mạng cùng học: Generator biến nhiễu zz thành mẫu, Discriminator chấm mẫu đó có vẻ thật hay không.
  • Muốn một mẫu mới, rút nhiễu và đưa qua Generator. Không cần công thức p(x)p(x) cho từng điểm, và không cần một mạng nén.
  • Một vòng gồm hai pha. Pha DD cắt gradient vào GG. Pha GG cho gradient đi qua DD nhưng không bước optimizer của DD.
  • Trò chơi gốc là minimax. Lúc Generator còn yếu, loss không bão hòa giữ gradient hữu ích hơn cho cùng một mục tiêu.
  • Ở cân bằng lý tưởng, pgp_g trùng pdatap_{data} và Discriminator tối ưu chấm một nửa. Độ đúng quanh mức ngẫu nhiên chưa chứng minh điều đó đã xảy ra.
  • Khi Discriminator chơi tối ưu, cực tiểu trò chơi kéo khoảng cách Jensen–Shannon giữa hai phân phối về 0.

Bài sau bắt đầu từ lưới ảnh na ná nhau hoặc từ Discriminator thắng tuyệt đối, rồi hỏi vì sao độ chính xác của mạng đó không đủ để kết luận phân phối đã được học.

Tài liệu tham khảo

[1] I. Goodfellow et al., “Generative Adversarial Nets,” NeurIPS, 2014. https://papers.nips.cc/paper_files/paper/2014/hash/f033ed80deb0234979a61f95710dbe25-Abstract.html