生成式AI(Generative AI)代表了人工智慧領域中一個革命性的分支,其核心能力在於創造全新的內容,而非僅分析或分類別現有資料。在開始探討前,我認為有必要先釐清生成式AI與其他AI模型的根本差異。
生成式AI與判別式AI的區別
生成式AI與判別式AI(Discriminative AI)在設計目標和運作方式上有本質區別:
生成式模型專注於學習資料的內在分佈和結構,能夠生成與訓練資料相似的新樣本。例如,一個生成式模型可以學習寫作風格並創作新文章。
判別式模型則專注於學習資料類別之間的決策邊界,主要用於分類別和預測任務。例如,垃圾郵件過濾器就是典型的判別式模型。
這兩種正規化的差異可以用一個簡單的例子來説明:假設我們有一組貓和狗的圖片。判別式模型會學習如何區分貓和狗的特徵,而生成式模型則會學習如何創造新的貓或狗圖片。
在實際應用中,選擇適合的模型正規化取決於具體任務需求:
if 任務需要創造新內容:
選擇生成式模型
elif 任務需要分類別或預測:
選擇判別式模型
else:
考慮混合方法
這段簡單的偽程式碼説明瞭模型選擇的基本邏輯。在實際應用中,我們需要根據任務性質選擇合適的模型型別。有時候,我們也會看到兩種模型協同工作的情況,例如GAN(生成對抗網路)中,生成器和判別器就分別代表了這兩種正規化。
生成式AI的主要方法概覽
生成式AI領域中存在多種方法,每種都有其獨特的優勢和適用場景:
自迴歸模型(Autoregressive Models):如GPT系列,透過預測序列中的下一個元素來生成內容。
變分自編碼器(Variational Autoencoders, VAEs):學習資料的壓縮表示,並從這些表示中生成新樣本。
生成對抗網路(Generative Adversarial Networks, GANs):由生成器和判別器兩個網路組成,透過對抗訓練生成高品質內容。
擴散模型(Diffusion Models):如Stable Diffusion,透過逐步去噪過程生成高品質影像。
流模型(Flow Models):使用可逆變換學習複雜分佈。
在選擇合適的生成方法時,我發現需要考慮多個因素,包括資料型別、計算資源限制、所需生成內容的品質和多樣性等。例如,對於影像生成,擴散模型近期表現出色;而對於文字生成,Transformer架構的自迴歸模型則佔據主導地位。
生成式AI的演進歷程
生成式AI的發展歷程可以追溯到早期的統計語言模型,但真正的突破發生在近十年間。以下是這一演進的關鍵里程碑:
傳統NLP方法時代
早期的自然語言處理(NLP)主要依賴於規則型系統和統計方法:
- 規則型系統:根據語言學家定義的規則進行文書處理
- n-gram模型:使用短序列統計預測下一個詞
- 隱馬爾可夫模型(HMM):用於序列標記和生成簡單文字
這些方法雖然在特定任務上有效,但缺乏對語言深層語義的理解,與難以擴充套件到複雜任務。
Transformer架構的革命
2017年,Google研究團隊發表的《Attention is All You Need》論文引入了Transformer架構,徹底改變了NLP領域:
class TransformerBlock(nn.Module):
def __init__(self, embed_dim, num_heads, ff_dim, dropout=0.1):
super(TransformerBlock, self).__init__()
self.attention = nn.MultiheadAttention(embed_dim, num_heads)
self.norm1 = nn.LayerNorm(embed_dim)
self.norm2 = nn.LayerNorm(embed_dim)
self.ff = nn.Sequential(
nn.Linear(embed_dim, ff_dim),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(ff_dim, embed_dim)
)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
# Self-attention with residual connection and layer normalization
attn_output, _ = self.attention(x, x, x)
x = self.norm1(x + self.dropout(attn_output))
# Feed-forward with residual connection and layer normalization
ff_output = self.ff(x)
x = self.norm2(x + self.dropout(ff_output))
return x
這段程式碼展示了Transformer架構的核心元件之一:Transformer塊。它包含多頭自注意力機制(MultiheadAttention)和前饋神經網路(Feed-Forward Network)。注意力機制使模型能夠關注輸入序列中的不同部分,而不受位置限制,這是Transformer相比RNN等傳統序列模型的重大優勢。每個元件後都有殘差連線(residual connection)和層正規化(layer normalization),這有助於訓練更深的網路。
Transformer架構的關鍵創新在於:
- 自注意力機制:允許模型在處理序列時考慮所有位置,而不受距離限制
- 平行計算能力:不同於RNN的順序處理,Transformer可以平行處理整個序列
- 多頭注意力:允許模型同時關注不同的表示子網路
這些創新使Transformer成為處理序列資料的強大工具,為後續大模型語言奠定了基礎。
GPT系列的發展與影響
OpenAI的GPT(Generative Pre-trained Transformer)系列模型展示了預訓練語言模型的巨大潛力:
- GPT-1(2018):首次展示大規模預訓練加微調的效果
- GPT-2(2019):引數量增至15億,生成能力顯著提升
- GPT-3(2020):1750億引數,展現出令人驚訝的少樣本學習能力
- GPT-4(2023):多模態能力,推理能力和安全性大幅提升
GPT-4的出現標誌著大模型語言(LLM)進入了一個新階段,它不僅能夠生成流暢的文字,還展現出理解上下文、遵循複雜指令和解決問題的能力。在使用過程中,我發現GPT-4的一個顯著特點是其"思考鏈"(chain-of-thought)能力,能夠像人類一樣逐步推理解決問題。
生成式AI的風險與影響
隨著生成式AI能力的提升,其帶來的風險和社會影響也日益凸顯:
- 內容真實性挑戰:AI生成的假新聞、深度偽造等內容難以辨別
- 版權與智慧財產權問題:模型訓練資料和生成內容的版權歸屬不明確
- 就業市場變革:可能替代部分創意和知識工作
- 偏見與歧視放大:模型可能繼承並放大訓練資料中的偏見
- 資源不平等:開發和使用先進AI模型需要大量計算資源
應對這些挑戰需要技術和政策層面的共同努力,包括開發更好的AI內容檢測工具、建立明確的法律框架,以及確保AI發展的包容性和公平性。
生成式AI的應用場景
生成式AI已經在多個領域展現出變革性潛力:
- 內容創作:文章撰寫、故事創作、詩歌生成
- 程式開發:程式碼生成、程式除錯、API檔案撰寫
- 設計與創意:影像生成、產品設計、UI/UX原型
- 教育:個人化學習內容、人工智慧導師系統
- 醫療:藥物發現、醫學影像生成與分析
- 客戶服務:人工智慧客服、個人化推薦
這些應用只是冰山一角,隨著技術的進步,我們將看到更多創新應用場景的出現。
生成式AI型別與模式:GANs、擴散模型與Transformer概覽
在生成式AI領域,不同型別的模型架構各有所長。本文將探討三種主要的生成式AI模型:生成對抗網路(GANs)、擴散模型(Diffusion Models)和Transformer模型,分析它們的工作原理、特點和應用場景。
生成式AI模型的區別特徵
GANs、擴散模型和Transformer雖然都屬於生成式AI,但它們在設計理念、訓練方式和適用場景上有顯著差異:
模型型別 | 核心機制 | 訓練方式 | 主要優勢 | 典型應用 |
---|---|---|---|---|
GANs | 生成器與判別器對抗 | 對抗訓練 | 生成高品質、逼真的樣本 | 影像生成、風格轉換 |
擴散模型 | 逐步去噪過程 | 去噪訓練 | 樣本多樣性、訓練穩定性 | 高解析度影像生成、文字到影像 |
Transformer | 自注意力機制 | 自監督預訓練 | 長距離依賴建模、可擴充套件性 | 文字生成、語言理解、多模態任務 |
深入解析生成對抗網路(GANs)
生成對抗網路由Ian Goodfellow在2014年提出,其核心思想是透過兩個網路的對抗訓練來生成高品質樣本。
GANs的基本架構
GAN由兩個主要元件組成:
- 生成器(Generator):嘗試生成逼真的假樣本
- 判別器(Discriminator):嘗試區分真實樣本和生成器產生的假樣本
這兩個網路透過一個零和博弈進行訓練:生成器試圖欺騙判別器,而判別器則努力不被欺騙。
import torch
import torch.nn as nn
# 簡化的GAN架構範例
class Generator(nn.Module):
def __init__(self, latent_dim, output_dim):
super(Generator, self).__init__()
self.model = nn.Sequential(
nn.Linear(latent_dim, 128),
nn.LeakyReLU(0.2),
nn.Linear(128, 256),
nn.LeakyReLU(0.2),
nn.Linear(256, output_dim),
nn.Tanh() # 輸出範圍限制在[-1, 1]
)
def forward(self, z):
return self.model(z)
class Discriminator(nn.Module):
def __init__(self, input_dim):
super(Discriminator, self).__init__()
self.model = nn.Sequential(
nn.Linear(input_dim, 256),
nn.LeakyReLU(0.2),
nn.Linear(256, 128),
nn.LeakyReLU(0.2),
nn.Linear(128, 1),
nn.Sigmoid() # 輸出範圍[0, 1],表示真實樣本的機率
)
def forward(self, x):
return self.model(x)
這段程式碼展示了GAN的基本架構。生成器接收一個隨機噪聲向量(latent_dim維度),透過多層神經網路將其轉換為目標維度的輸出(如影像)。判別器則接收一個輸入樣本(可能是真實樣本或生成器產生的樣本),輸出一個0到1之間的值,表示該樣本是真實樣本的機率。
在訓練過程中,兩個網路的損失函式如下:
# GAN訓練迴圈範例
def train_gan(generator, discriminator, real_data, epochs, batch_size, latent_dim):
criterion = nn.BCELoss() # 二元交叉熵損失
optimizer_g = torch.optim.Adam(generator.parameters(), lr=0.0002)
optimizer_d = torch.optim.Adam(discriminator.parameters(), lr=0.0002)
for epoch in range(epochs):
for batch in range(len(real_data) // batch_size):
# 訓練判別器
optimizer_d.zero_grad()
# 真實樣本的損失
real_samples = real_data[batch * batch_size: (batch + 1) * batch_size]
real_labels = torch.ones(batch_size, 1)
real_output = discriminator(real_samples)
d_loss_real = criterion(real_output, real_labels)
# 生成樣本的損失
z = torch.randn(batch_size, latent_dim)
fake_samples = generator(z)
fake_labels = torch.zeros(batch_size, 1)
fake_output = discriminator(fake_samples.detach()) # 分離計算圖
d_loss_fake = criterion(fake_output, fake_labels)
# 總判別器損失
d_loss = d_loss_real + d_loss_fake
d_loss.backward()
optimizer_d.step()
# 訓練生成器
optimizer_g.zero_grad()
z = torch.randn(batch_size, latent_dim)
fake_samples = generator(z)
fake_output = discriminator(fake_samples)
# 生成器希望判別器將生成樣本判斷為真
g_loss = criterion(fake_output, torch.ones(batch_size, 1))
g_loss.backward()
optimizer_g.step()
print(f"Epoch {epoch}: D Loss: {d_loss.item()}, G Loss: {g_loss.item()}")
這段程式碼展示了GAN的訓練過程。每個訓練迭代包含兩個步驟:
- 訓練判別器:使用真實樣本和生成器產生的假樣本,最佳化判別器以正確區分兩者
- 訓練生成器:生成新樣本,並最佳化生成器使判別器將這些樣本誤判為真實樣本
這種對抗訓練過程是GAN的核心,透過不斷的博弈,生成器學會產生越來越逼真的樣本。
GANs的變種與進展
自2014年提出以來,GAN已發展出多種變體,解決了原始GAN的一些限制:
- DCGAN:將卷積神經網路引入GAN架構,提高影像生成品質
- WGAN:引入Wasserstein距離,改善訓練穩定性
- CycleGAN:實作無配對資料的域轉換
- StyleGAN:透過風格控制實作高品質、可控的影像生成
擴散模型深度剖析
擴散模型(Diffusion Models)是近年來在生成領域取得突破性進展的模型型別,特別是在影像生成方面。
擴散模型的工作原理
擴散模型的核心思想是透過一個逐步加噪和去噪的過程來生成資料:
- 前向過程(加噪):逐步將高斯噪聲增加到真實資料中,直到完全變為噪聲
- 反向過程(去噪):學習如何逐步從噪聲中還原始資料
這種方法的數學基礎是馬爾可夫鏈和變分推斷。
import torch
import torch.nn as nn
# 簡化的擴散模型範例
class DiffusionModel(nn.Module):
def __init__(self, input_dim, hidden_dim):
super(DiffusionModel, self).__init__()
self.net = nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.SiLU(),
nn.Linear(hidden_dim, hidden_dim),
nn.SiLU(),
nn.Linear(hidden_dim, input_dim)
)
def forward(self, x, t):
# t是時間步,通常會被嵌入到網路中
# 這裡簡化處理
t_emb = torch.ones_like(x) * t / 1000.0
x_input = torch.cat([x, t_emb], dim=-1)
return self.net(x_input)
# 擴散過程
def diffusion_forward(x_0, num_steps=1000, beta_min=1e-4, beta_max=0.02):
"""前向擴散過程:逐步向資料增加噪聲"""
betas = torch.linspace(beta_min, beta_max, num_steps)
alphas = 1 - betas
alphas_cumprod = torch.cumprod(alphas, dim=0)
# 選擇一個隨機時間步
t = torch.randint(0, num_steps, (1,))
alpha_cumprod_t = alphas_cumprod[t]
# 增加噪聲
noise = torch.randn_like(x_0)
x_t = torch.sqrt(alpha_cumprod_t) * x_0 + torch.sqrt(1 - alpha_cumprod_t) * noise
return x_t, noise, t
這段程式碼展示了擴散模型的基本結構和前向擴散過程。DiffusionModel類別定義了一個簡單的神經網路,用於預測去噪過程中的噪聲。diffusion_forward函式實作了前向擴散過程,即逐步向原始資料增加噪聲。
在實際訓練中,模型學習預測增加的噪聲,而不是直接預測去噪後的資料:
# 擴散模型訓練範例
def train_diffusion(model, dataloader, optimizer, num_steps=1000):
criterion = nn.MSELoss()
for x_0 in dataloader:
optimizer.zero_grad()
# 前向擴散
x_t, noise, t = diffusion_forward(x_0, num_steps)
# 模型預測噪聲
predicted_noise = model(x_t, t)
# 計算損失(預測噪聲與實際增加的噪聲之間的MSE)
loss = criterion(predicted_noise, noise)
loss.backward()
optimizer.step()
這段程式碼展示了擴散模型的訓練過程。關鍵步驟包括:
- 對原始資料x_0進行前向擴散,得到帶噪聲的資料x_t
- 使用模型預測增加的噪聲
- 計算預測噪聲與實際增加噪聲之間的均方誤差作為損失函式
- 最佳化模型引數
訓練完成後,生成新樣本的過程是從純噪聲開始,逐步應用學習到的去噪過程。
擴散模型的優勢與應用
擴散模型相比GANs有幾個顯著優勢:
- 訓練穩定性:不存在GAN中生成器和判別器的平衡問題
- 樣本多樣性:能夠生成更多樣化的樣本
- 可控性:提供更精細的生成過程控制
代表性的擴散模型包括:
- DDPM(Denoising Diffusion Probabilistic Models):奠定了現代擴散模型的基礎
- Stable Diffusion:結合潛在擴散和文字條件,實作高品質文字到影像生成
- DALL-E 2:OpenAI的文字到影像生成模型,部分根據擴散技術
Transformer模型與生成式應用
Transformer架構自2017年提出後,已成為自然語言處理和生成式AI的核心技術。
Transformer架構的關鍵元件
Transformer的核心創新在於自注意力機制,它使模型能夠處理序列中的長距離依賴關係:
import torch
import torch.nn as nn
import math
class SelfAttention(nn.Module):
def __init__(self, embed_dim, num_heads):
super(SelfAttention, self).__init__()
self.embed_dim = embed_dim
self.num_heads = num_heads
self.head_dim = embed_dim // num_heads
self.query = nn.Linear(embed_dim, embed_dim)
self.key = nn.Linear(embed_dim, embed_dim)
self.value = nn.Linear(embed_dim, embed_dim)
self.out = nn.Linear(embed_dim, embed_dim)
def forward(self, x):
batch_size, seq_len, _ = x.size()
# 線性投影
q = self.query(x).reshape(batch_size, seq_len, self.num_heads, self.head_dim).permute(0, 2, 1, 3)
k = self.key(x).reshape(batch_size, seq_len, self.num_heads, self.head_dim).permute(0, 2, 1, 3)
v = self.value(x).reshape(batch_size, seq_len, self.num_heads, self.head_dim).permute(0, 2, 1, 3)
# 注意力計算
scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.head_dim)
attention = torch.softmax(scores, dim=-1)
# 加權值向量
out = torch.matmul(attention, v).permute(0, 2, 1, 3).reshape(batch_size, seq_len, self.embed_dim)
return self.out(out)
這段程式碼實作了Transformer的核心元件:多頭自注意力機制。它首先將輸入向量投影到查詢(Q)、鍵(K)和值(V)空間,然後計算注意力分數,最後根據這些分數對值向量進行加權求和。多頭機制允許模型同時關注不同的表示子網路。
完整的Transformer還包括前饋神經網路、層正規化和殘差連線等元件:
class TransformerEncoderLayer(nn.Module):
def __init__(self, embed_dim, num_heads, ff_dim, dropout=0.1):
super(TransformerEncoderLayer, self).__init__()
self.attention = SelfAttention(embed_dim, num_heads)
self.norm1 = nn.LayerNorm(embed_dim)
self.norm2 = nn.LayerNorm(embed_dim)
self.ff = nn.Sequential(
nn.Linear(embed_dim, ff_dim),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(ff_dim, embed_dim)
)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
# 自注意力 + 殘差連線 + 層正規化
attn_out = self.attention(x)
x = self.norm1(x + self.dropout(attn_out))
# 前饋網路 + 殘差連線 + 層正規化
ff_out = self.ff(x)
x = self.norm2(x + self.dropout(ff_out))
return x
這段程式碼實作了Transformer編碼器層的完整結構。每個編碼器層包含一個多頭自注意力子層和一個前饋神經網路子層,每個子層都有殘差連線和層正規化。這種設計使得Transformer能夠有效處理深層網路的訓練問題,並保持梯度流動。
生成式Transformer模型
在生成式AI領域,Transformer主要以自迴歸(Autoregressive)方式應用,即逐步生成序列中的下一個元素:
class GPTModel(nn.Module):
def __init__(self, vocab_size, embed_dim, num_heads, num_layers, max_seq_len):
super(GPTModel, self).__init__()
self.token_embedding = nn.Embedding(vocab_size, embed_dim)
self.position_embedding = nn.Embedding(max_seq_len, embed_dim)
self.layers = nn.ModuleList([
TransformerEncoderLayer(embed_dim, num_heads, embed_dim * 4)
for _ in range(num_layers)
])
self.ln_f = nn.LayerNorm(embed_dim)
self.head = nn.Linear(embed_dim, vocab_size)
def forward(self, x):
batch_size, seq_len = x.size()
# 取得token和位置嵌入
token_emb = self.token_embedding(x)
pos = torch.arange(0, seq_len, device=x.device).unsqueeze(0).repeat(batch_size, 1)
pos_emb = self.position_embedding(pos)
# 組合嵌入
x = token_emb + pos_emb
# 透過Transformer層
for layer in self.layers:
x = layer(x)
x = self.ln_f(x)
logits = self.head(x)
return logits
這段程式碼實作了一個簡化版的GPT模型。它首先將輸入token轉換為嵌入向量,並增加位置嵌入以保留序列順序訊息。然後,輸入透過多個Transformer編碼器層進行處理,最後透過一個線性層對映到詞彙表大小的輸出,用於預測下一個token的機率分佈。
在生成過程中,模型通常使用自迴歸方式,即每次生成一個新token,然後將其增加到輸入序列中,再預測下一個token:
def generate_text(model, start_tokens, max_length, vocab_size):
model.eval()
current_tokens = start_tokens.clone()
with torch.no_grad():
for _ in range(max_length):
# 取得模型預測
logits = model(current_tokens)
# 只關注最後一個位置的預測
next_token_logits = logits[:, -1, :]
# 可以使用不同的取樣策略,這裡使用簡單的greedy
next_token = torch.argmax(next_token_logits, dim=-1, keepdim=True)
# 將新token增加到序列中
current_tokens = torch.cat([current_tokens, next_token], dim=1)
return current_tokens
這段程式碼展示了使用Transformer模型生成文字的過程。它從一個起始序列開始,然後反覆使用模型預測下一個token,並將其增加到序列中,直到達到指定的最大長度。在實際應用中,通常會使用更複雜的取樣策略,如溫度取樣、top-k取樣或nucleus取樣,以增加生成文字的多樣性和創造性。
生成式AI模型在影像生成中的應用
為了具體展示不同生成式AI模型的應用,讓我們看它們在影像生成任務中的表現。
使用Jupyter Notebook和Google Colab
在實際開發中,Jupyter Notebook和Google Colab是探索生成式AI的常用工具。以下是在Google Colab中使用Stable Diffusion模型生成影像的範例:
# 安裝必要的函式庫
!pip install diffusers transformers accelerate
import torch
from diffusers import StableDiffusionPipeline
from PIL import Image
import matplotlib.pyplot as plt
# 載入預訓練的Stable Diffusion模型
model_id = "CompVis/stable-diffusion-v1-4"
pipe = StableDiffusionPipeline.from_pretrained(model_id, torch_dtype=torch.float16)
pipe = pipe.to("cuda")
# 生成影像
prompt = "一隻黑貓坐在滿月下的古老城堡屋頂上,背景是星空"
image = pipe(prompt).images[0]
# 顯示影像
plt.figure(figsize=(10, 10))
plt.imshow(image)
plt.axis('off')
plt.title(prompt)
plt.show()
# 儲存影像
image.save("black_cat_castle.png")
這段程式碼展示瞭如何使用Hugging Face的Diffusers函式庫載入預訓練的Stable Diffusion模型,並用它根據文字提示生成影像。Stable Diffusion是一種潛在擴散模型,它在壓縮的潛在空間中執行擴散過程,而不是在畫素空間中,這使得它能夠高效地生成高解析度影像。
使用CLIP模型評估生成影像
CLIP(Contrastive Language-Image Pretraining)模型可以用來評估生成影像與文字提示的比對度:
from transformers import CLIPProcessor, CLIPModel
# 載入CLIP模型
model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
# 準備文字和影像
texts = ["一隻黑貓", "一座城堡", "星空", "滿月"]
inputs = processor(text=texts, images=image, return_tensors="pt", padding=True)
# 計算相似度分數
with torch.no_grad():
outputs = model(**inputs)
logits_per_image = outputs.logits_per_image
probs = logits_per_image.softmax(dim=1)
# 顯示結果
for i, text in enumerate(texts):
print(f"'{text}' 的比對度: {probs[0][i].item():.4f}")
這段程式碼使用OpenAI的CLIP模型來評估生成影像與不同文字描述的比對度。CLIP是一個多模態模型,經過訓練可以理解影像和文字之間的關係。透過計算影像與各個文字描述之間的相似度分數,我們可以評估生成影像是否成功捕捉了提示中的關鍵元素。這種評估方法在開發和最佳化生成模型時非常有用。
自然語言處理的基礎與Transformer的影響
自然語言處理(NLP)領域在過去十年經歷了翻天覆地的變化,從早期的統計方法到現代的神經網路架構,特別是Transformer的出現,徹底改變了這一領域。本文將探討NLP的發展歷程,並深入分析Transformer架構如何成為現代語言模型的根本。
NLP的早期方法
在深度學習革命之前,NLP主要依賴於統計方法和手工特徵工程:
分散式表示的興起
早期的一個重要突破是分散式表示(Distributed Representations)的概念,它根據"相似的詞出現在相似的上下文中"的假設:
# Word2Vec模型的簡化實作
import torch
import torch.nn as nn
import torch.optim as optim
class SkipGramModel(nn.Module):
def __init__(self, vocab_size, embedding_dim):
super(SkipGramModel, self).__init__()
self.embeddings = nn.Embedding(vocab_size, embedding_dim)
self.output_layer = nn.Linear(embedding_dim, vocab_size)
def forward(self, inputs):
embeds = self.embeddings(inputs)
output = self.output_layer(embeds)
return output
這段程式碼實作了Word2Vec的Skip-gram模型,這是一種早期但影響深遠的詞嵌入方法。該模型學習將詞對映到密集向量空間,使得語義相似的詞在該空間中彼此接近。Skip-gram模型透過預測給定中心詞的上下文詞來學習這些表示。
這種分散式表示方法相比早期的one-hot編碼有幾個優勢:
- 它捕捉了詞之間的語義關係
- 它產生了密集而非稀疏的表示
- 它允許進行詞向量算術運算(如"king" - “man” + “woman” ≈ “queen”)
遷移學習的出現
另一個重要發展是遷移學習(Transfer Learning)的應用,即在一個任務上預訓練模型,然後將其應用到其他相關任務:
# 遷移學習範例:使用預訓練的詞嵌入
import torch.nn as nn
class SentimentClassifier(nn.Module):
def __init__(self, pretrained_embeddings, hidden_dim, output_dim):
super(SentimentClassifier, self).__init__()
# 凍結預訓練的嵌入
self.embedding = nn.Embedding.from_pretrained(
pretrained_embeddings,
freeze=True
)
self.lstm = nn.LSTM(pretrained_embeddings.shape[1], hidden_dim, batch_first=True)
self.fc = nn.Linear(hidden_dim, output_dim)
def forward(self, text):
embedded = self.embedding(text)
output, (hidden, _) = self.lstm(embedded)
return self.fc(hidden.squeeze(0))
這段程式碼展示瞭如何使用預訓練的詞嵌入來構建情感分類別器。透過使用在大規模語料函式庫上預訓練的詞嵌入,模型可以利用這些嵌入捕捉的語義知識,即使在較小的標記資料集上也能取得良好的效能。這是早期遷移學習在NLP中的應用範例。
神經網路在NLP中的應用
在Transformer出現之前,迴圈神經網路(RNN)特別是LSTM和GRU是NLP任務的主要架構:
# 根據LSTM的語言模型
class LSTMLanguageModel(nn.Module):
def __init__(self, vocab_size, embedding_dim, hidden_dim, num_layers, dropout_rate):
super(LSTMLanguageModel, self).__init__()
self.embedding = nn.Embedding(vocab_size, embedding_dim)
self.lstm = nn.LSTM(embedding_dim, hidden_dim, num_layers,
batch_first=True, dropout=dropout_rate)
self.dropout = nn.Dropout(dropout_rate)
self.fc = nn.Linear(hidden_dim, vocab_size)
def forward(self, text):
# text: [batch_size, seq_len]
embedded = self.embedding(text) # [batch_size, seq_len, embedding_dim]
output, (hidden, cell) = self.lstm(embedded)
# output: [batch_size, seq_len, hidden_dim]
output = self.dropout(output)
prediction = self.fc(output) # [batch_size, seq_len, vocab_size]
return prediction
這段程式碼實作了一個根據LSTM的語言模型。LSTM(Long Short-Term Memory)是一種特殊的RNN,能夠更好地處理長距離依賴問題。在這個模型中,輸入文字首先透過嵌入層轉換為密集向量,然後透過LSTM層處理序列訊息,最後透過全連線層預測下一個詞的機率分佈。
雖然LSTM在當時取得了很好的效果,但它仍然存在一些限制:
- 序列處理是順序的,難以平行化
- 即使用門控機制,長距離依賴問題仍然存在
- 梯度消失/爆炸問題在長序列中依然明顯
Transformer架構的出現與影響
2017年,Google研究團隊發表了《Attention is All You Need》論文,引入了Transformer架構,這是NLP領域的一個重大突破。
Transformer架構的核心元件
Transformer的核心創新在於完全根據注意力機制,摒棄了迴圈結構:
import torch
import torch.nn as nn
import math
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
super(MultiHeadAttention, self).__init__()
assert d_model % num_heads == 0
self.d_model = d_model
self.num_heads = num_heads
self.d_k = d_model // num_heads
self.W_q = nn.Linear(d_model, d_model)
self.W_k = nn.Linear(d_model, d_model)
self.W_v = nn.Linear(d_model, d_model)
self.W_o = nn.Linear(d_model, d_model)
def scaled_dot_product_attention(self, Q, K, V, mask=None):
# Q, K, V: [batch_size, num_heads, seq_len, d_k]
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
# scores: [batch_size, num_heads, seq_len, seq_len]
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
attention = torch.softmax(scores, dim=-1)
output = torch.matmul(attention, V)
# output: [batch_size, num_heads, seq_len, d_k]
return output
def forward(self, query, key, value, mask=None):
batch_size = query.shape[0]
# 線性投影和重塑
Q = self.W_q(query).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
K = self.W_k(key).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
V = self.W_v(value).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
# 注意力計算
output = self.scaled_dot_product_attention(Q, K, V, mask)
# 重塑和最終線性投影
output = output.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model)
return self.W_o(output)
這段程式碼實作了Transformer的多頭注意力機制。多頭注意力允許模型同時關注不同位置的不同表示子網路,增強了模型的表達能力。關鍵步驟包括:
- 將輸入投影到查詢(Q)、鍵(K)和值(V)空間
- 將每個投影分割成多個頭
- 對每個頭執行縮放點積注意力
- 合併所有頭的輸出並進行最終投影
縮放點積注意力是Transformer的核心操作,它計算查詢與所有鍵的相似度,然後用這些相似度對值進行加權求和。
除了多頭注意力外,Transformer還包含前饋神經網路、層正規化和殘差連線等元件:
class PositionwiseFeedForward(nn.Module):
def __init__(self, d_model, d_ff):
super(PositionwiseFeedForward, self).__init__()
self.fc1 = nn.Linear(d_model, d_ff)
self.fc2 = nn.Linear(d_ff, d_model)
self.relu = nn.ReLU()
def forward(self, x):
return self.fc2(self.relu(self.fc1(x)))
class EncoderLayer(nn.Module):
def __init__(self, d_model, num_heads, d_ff, dropout):
super(EncoderLayer, self).__init__()
self.self_attention = MultiHeadAttention(d_model, num_heads)
self.feed_forward = PositionwiseFeedForward(d_model, d_ff)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x, mask=None):
# 自注意力子層
attn_output = self.self_attention(x, x, x, mask)
x = self.norm1(x + self.dropout(attn_output))
# 前饋神經網路子層
ff_output = self.feed_forward(x)
x = self.norm2(x + self.dropout(ff_output))
return x
這段程式碼實作了Transformer編碼器層的兩個主要元件:位置前饋網路和完整的編碼器層。每個編碼器層包含兩個子層:多頭自注意力和前饋神經網路,每個子層都有殘差連線和層正規化。這種設計使得Transformer能夠有效處理深層網路的訓練問題。
序列到序列學習
Transformer最初是為序列到序列(Sequence-to-Sequence)任務設計的,如機器翻譯:
class Encoder(nn.Module):
def __init__(self, vocab_size, d_model, num_heads, d_ff, num_layers, dropout, max_seq_len):
super(Encoder, self).__init__()
self.embedding = nn.Embedding(vocab_size, d_model)
self.pos_encoding = PositionalEncoding(d_model, max_seq_len)
self.layers = nn.ModuleList([
EncoderLayer(d_model, num_heads, d_ff, dropout)
for _ in range(num_layers)
])
self.dropout = nn.Dropout(dropout)
def forward(self, src, src_mask=None):
# src: [batch_size, src_len]
src_embedded = self.embedding(src)
src_embedded = self.pos_encoding(src_embedded)
x = self.dropout(src_embedded)
for layer in self.layers:
x = layer(x, src_mask)
return x
class Decoder(nn.Module):
def __init__(self, vocab_size, d_model, num_heads, d_ff, num_layers, dropout, max_seq_len):
super(Decoder, self).__init__()
self.embedding = nn.Embedding(vocab_size, d_model)
self.pos_encoding = PositionalEncoding(d_model, max_seq_len)
self.layers = nn.ModuleList([
DecoderLayer(d_model, num_heads, d_ff, dropout)
for _ in range(num_layers)
])
self.fc_out = nn.Linear(d_model, vocab_size)
self.dropout = nn.Dropout(dropout)
def forward(self, trg, enc_output, trg_mask=None, src_mask=None):
# trg: [batch_size, trg_len]
trg_embedded = self.embedding(trg)
trg_embedded = self.pos_encoding(trg_embedded)
x = self.dropout(trg_embedded)
for layer in self.layers:
x = layer(x, enc_output, trg_mask, src_mask)
output = self.fc_out(x)
return output
這段程式碼實作了Transformer的編碼器和解碼器模組。編碼器將源序列轉換為連續表示,而解碼器則使用這些表示和之前生成的輸出來預測下一個token。關鍵元件包括:
- 嵌入層:將離散token轉換為連續向量
- 位置編碼:增加位置訊息,因為Transformer沒有迴圈結構
- 多層編碼器/解碼器:逐層處理序列訊息
- 輸出層:將解碼器輸出對映到詞彙表大小
位置編碼是Transformer的另一個重要元件,它為模型提供序列中token的位置訊息:
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_seq_len=5000):
super(PositionalEncoding, self).__init__()
# 建立位置編碼矩陣
pe = torch.zeros(max_seq_len, d_model)
position = torch.arange(0, max_seq_len, dtype=torch.float).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0)
# 註冊為非引數緩衝區
self.register_buffer('pe', pe)
def forward(self, x):
# x: [batch_size, seq_len, d_model]
return x + self.pe[:, :x.size(1), :]
這段程式碼實作了Transformer的位置編碼。由於Transformer沒有迴圈結構,它需要額外的位置訊息來區分序列中不同位置的token。位置編碼使用正弦和餘弦函式的組合,具有以下特性:
- 每個位置有唯一的編碼
- 不同位置之間的相對關係可以透過線性變換學習
- 可以擴充套件到訓練中未見過的序列長度
自迴歸Transformer與生成式AI
在生成式AI中,Transformer主要以自迴歸(Autoregressive)方式應用,即逐步生成序列中的下一個元素:
def generate_sequence(model, start_tokens, max_length, vocab_size):
model.eval()
current_tokens = start_tokens.clone()
with torch.no_grad():
for _ in range(max_length):
# 建立注意力遮罩
trg_mask = generate_square_subsequent_mask(current_tokens.size(1)).to(current_tokens.device)
# 取得模型預測
output = model(current_tokens, trg_mask)
# 只關注最後一個位置的預測
next_token_logits = output[:, -1, :]
# 取樣下一個token
next_token = sample_next_token(next_token_logits, temperature=0.8)
# 將新token增加到序列中
current_tokens = torch.cat([current_tokens, next_token.unsqueeze(1)], dim=1)
return current_tokens
def generate_square_subsequent_mask(sz):
"""生成方形後續遮罩"""
mask = (torch.triu(torch.ones(sz, sz)) == 1).transpose(0, 1)
mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))
return mask
def sample_next_token(logits, temperature=1.0):
"""使用溫度取樣下一個token"""
if temperature == 0:
# 貪婪解碼
return torch.argmax(logits, dim=-1)
else:
# 溫度取樣
logits = logits / temperature
probs = torch.softmax(logits, dim=-1)
return torch.multinomial(probs, num_samples=1).squeeze(-1)
這段程式碼展示了使用Transformer模型生成序列的過程。關鍵步驟包括:
- 從起始token開始
- 使用模型預測下一個token的機率分佈
- 根據某種取樣策略選擇下一個token
- 將新token增加到序列中
- 重複步驟2-4直到達到最大長度或生成結束標記
注意遮罩(attention mask)確保模型在預測位置i時只能看到位置0到i-1的token,這是自迴歸生成的關鍵。溫度引數控制取樣的隨機性:較高的溫度產生更多樣化但可能不太連貫的輸出,較低的溫度產生更確定但可能重複的輸出。
實作原始Transformer
為了更深入理解Transformer架構,讓我們實作一個完整的Transformer模型用於機器翻譯任務:
# 資料載入與準備
from torchtext.datasets import Multi30k
from torchtext.data.utils import get_tokenizer
from torchtext.vocab import build_vocab_from_iterator
# 定義tokenizer
SRC_LANGUAGE = 'de'
TGT_LANGUAGE = 'en'
token_transform = {}
vocab_transform = {}
token_transform[SRC_LANGUAGE] = get_tokenizer('spacy', language='de_core_news_sm')
token_transform[TGT_LANGUAGE] = get_tokenizer('spacy', language='en_core_web_sm')
# 建立詞彙表
def yield_tokens(data_iter, language):
for data_sample in data_iter:
yield token_transform[language](data_sample[language])
for language in [SRC_LANGUAGE, TGT_LANGUAGE]:
train_iter = Multi30k(split='train', language_pair=(SRC_LANGUAGE, TGT_LANGUAGE))
vocab_transform[language] = build_vocab_from_iterator(
yield_tokens(train_iter, language),
min_freq=2,
specials=['<unk>', '<pad>', '<bos>', '<eos>']
)
這段程式碼展示瞭如何準備機器翻譯任務的資料。它使用Multi30k資料集(德語到英語的翻譯任務),並為源語言和目標語言定義tokenizer和詞彙表。特殊token包括:
<unk>
: 未知詞<pad>
: 填充標記<bos>
: 序列開始標記<eos>
: 序列結束標記
接下來,我們需要將文字轉換為模型可以處理的張量:
# 資料張量化
from torch.nn.utils.rnn import pad_sequence
def sequential_transforms(*transforms):
def func(txt_input):
for transform in transforms:
txt_input = transform(txt_input)
return txt_input
return func
def tensor_transform(token_ids):
return torch.cat((torch.tensor([BOS_IDX]), token_ids, torch.tensor([EOS_IDX])))
# 特殊標記索引
SRC_VOCAB_SIZE = len(vocab_transform[SRC_LANGUAGE])
TGT_VOCAB_SIZE = len(vocab_transform[TGT_LANGUAGE])
UNK_IDX = 0
PAD_IDX = 1
BOS_IDX = 2
EOS_IDX = 3
# 轉換函式
text_transform = {}
for language in [SRC_LANGUAGE, TGT_LANGUAGE]:
text_transform[language] = sequential_transforms(
token_transform[language],
lambda x: [vocab_transform[language][token] for token in x],
tensor_transform
)
# 批次處理函式
def collate_fn(batch):
src_batch, tgt_batch = [], []
for src_sample, tgt_sample in batch:
src_batch.append(text_transform[SRC_LANGUAGE](src_sample.rstrip("\n")))
tgt_batch.append(text_transform[TGT_LANGUAGE](tgt_sample.rstrip("\n")))
src_batch = pad_sequence(src_batch, padding_value=PAD_IDX)
tgt_batch = pad_sequence(tgt_batch, padding_value=PAD_IDX)
return src_batch, tgt_batch
這段程式碼定義了將文字轉換為模型輸入張量的函式。主要步驟包括:
- 文字標記化(tokenization)
- 將標記轉換為詞彙表索引
- 增加序列開始和結束標記
- 對批次中的序列進行填充,使它們具有相同長度
現在,我們可以實作完整的Transformer模型:
# 完整的Transformer模型
class Transformer(nn.Module):
def __init__(self, src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_len, dropout):
super(Transformer, self).__init__()
self.encoder_embedding = nn.Embedding(src_vocab_size, d_model)
self.decoder_embedding = nn.Embedding(tgt_vocab_size, d_model)
self.positional_encoding = PositionalEncoding(d_model, max_seq_len)
self.transformer = nn.Transformer(
d_model=d_model,
nhead=num_heads,
num_encoder_layers=num_layers,
num_decoder_layers=num_layers,
dim_feedforward=d_ff,
dropout=dropout
)
self.output_layer = nn.Linear(d_model, tgt_vocab_size)
self.d_model = d_model
def create_mask(self, src, tgt):
src_seq_len = src.shape[0]
tgt_seq_len = tgt.shape[0]
# 源序列填充遮罩
src_mask = torch.zeros((src_seq_len, src_seq_len), device=src.device).type(torch.bool)
# 目標序列填充遮罩和後續遮罩
tgt_mask = generate_square_subsequent_mask(tgt_seq_len).to(tgt.device)
src_padding_mask = (src == PAD_IDX).transpose(0, 1)
tgt_padding_mask = (tgt == PAD_IDX).transpose(0, 1)
return src_mask, tgt_mask, src_padding_mask, tgt_padding_mask
def forward(self, src, tgt):
# src, tgt: [seq_len, batch_size]
# 建立遮罩
src_mask, tgt_mask, src_padding_mask, tgt_padding_mask = self.create_mask(src, tgt)
# 嵌入和位置編碼
src_embedded = self.positional_encoding(self.encoder_embedding(src) * math.sqrt(self.d_model))
tgt_embedded = self.positional_encoding(self.decoder_embedding(tgt) * math.sqrt(self.d_model))
# Transformer處理
output = self.transformer(
src_embedded, tgt_embedded,
src_mask, tgt_mask,
None, src_padding_mask, tgt_padding_mask
)
# 輸出層
return self.output_layer(output)
這段程式碼實作了完整的Transformer模型,使用PyTorch的nn.Transformer模組。主要元件包括:
- 源語言和目標語言的嵌入層
- 位置編碼
- Transformer編碼器和解碼器
- 輸出線性層
create_mask方法建立了四種遮罩:
- src_mask:源序列的自注意力遮罩(在翻譯任務中通常不需要)
- tgt_mask:目標序列的自注意力遮罩,確保自迴歸屬性
- src_padding_mask:源序列的填充遮罩,忽略填充標記
- tgt_padding_mask:目標序列的填充遮罩,忽略填充標記
最後,我們定義訓練和翻譯函式:
# 訓練函式
def train_epoch(model, optimizer, train_dataloader, criterion):
model.train()
losses = 0
for src, tgt in train_dataloader:
src = src.to(device)
tgt = tgt.to(device)
# 目標輸入和輸出
tgt_input = tgt[:-1, :] # 移除最後一個token (EOS)
tgt_output = tgt[1:, :] # 移除第一個token (BOS)
# 前向傳播
logits = model(src, tgt_input)
# 重塑輸出和目標
output_dim = logits.shape[-1]
logits = logits.contiguous().view(-1, output_dim)
tgt_output = tgt_output.contiguous().view(-1)
# 計算損失
loss = criterion(logits, tgt_output)
# 反向傳播和最佳化
optimizer.zero_grad()
loss.backward()
optimizer.step()
losses += loss.item()
return losses / len(train_dataloader)
# 翻譯函式
def translate(model, src_sentence, src_vocab, tgt_vocab, device, max_len=50):
model.eval()
# 處理源句子
src_tokens = text_transform[SRC_LANGUAGE](src_sentence).unsqueeze(1).to(device)
# 初始化目標序列
tgt_tokens = torch.tensor([[BOS_IDX]]).to(device)
for i in range(max_len):
# 預測下一個token
logits = model(src_tokens, tgt_tokens)
next_token = logits.argmax(2)[-1, :]
# 增加預測的token
tgt_tokens = torch.cat([tgt_tokens, next_token.unsqueeze(0)], dim=0)
# 如果預測到EOS,停止生成
if next_token.item() == EOS_IDX:
break
# 將索引轉換回詞
tgt_tokens = tgt_tokens.squeeze(1).tolist()
tgt_words = []
for token in tgt_tokens[1:]: # 跳過BOS
if token == EOS_IDX:
break
tgt_words.append(tgt_vocab.get_itos()[token])
return " ".join(tgt_words)
這段程式碼定義了Transformer模型的訓練和翻譯函式:
訓練函式(train_epoch):
- 將源序列和目標序列移動到裝置(CPU或GPU)
- 準備目標輸入(移除EOS)和目標輸出(移除BOS)
- 執行前向傳播
- 計算損失
- 執行反向傳播和最佳化
翻譯函式(translate):
- 處理源句子
- 從BOS標記開始
- 反覆預測下一個token並增加到序列中
- 如果預測到EOS或達到最大長度,停止生成
- 將token索引轉換回詞
最後,我們可以執行主程式來訓練和評估模型:
# 主執行函式
def main():
# 模型引數
SRC_VOCAB_SIZE = len(vocab_transform[SRC_LANGUAGE])
TGT_VOCAB_SIZE = len(vocab_transform[TGT_LANGUAGE])
EMB_SIZE = 512
NHEAD = 8
FFN_HID_DIM = 2048
NUM_ENCODER_LAYERS = 6
NUM_DECODER_LAYERS = 6
DROPOUT = 0.1
MAX_SEQ_LEN = 100
# 初始化模型
transformer = Transformer(
SRC_VOCAB_SIZE, TGT_VOCAB_SIZE,
EMB_SIZE, NHEAD, NUM_ENCODER_LAYERS,
FFN_HID_DIM, MAX_SEQ_LEN, DROPOUT
).to(device)
# 損失函式和最佳化器
criterion = nn.CrossEntropyLoss(ignore_index=PAD_IDX)
optimizer = torch.optim.Adam(transformer.parameters(), lr=0.0001, betas=(0.9, 0.98), eps=1e-9)
# 資料載入器
train_iter = Multi30k(split='train', language_pair=(SRC_LANGUAGE, TGT_LANGUAGE))
train_dataloader = DataLoader(list(train_iter), batch_size=128, collate_fn=collate_fn)
# 訓練迴圈
NUM_EPOCHS = 10
for epoch in range(NUM_EPOCHS):
train_loss = train_epoch(transformer, optimizer, train_dataloader, criterion)
print(f"Epoch: {epoch+1}, Train loss: {train_loss:.3f}")
# 翻譯範例
test_iter = Multi30k(split='test', language_pair=(SRC_LANGUAGE, TGT_LANGUAGE))
for src, tgt in list(test_iter)[:5]:
print(f"Source: {src}")
print(f"Target: {tgt}")
translation = translate(transformer, src, vocab_transform[SRC_LANGUAGE],
vocab_transform[TGT_LANGUAGE], device)
print(f"Predicted: {translation}")
print("-" * 50)
這段程式碼定義了主執行函式,包括:
- 設定模型引數
- 初始化Transformer模型
- 定義損失函式(交叉熵)和最佳化器(Adam)
- 建立訓練資料載入器
- 執行訓練迴圈
- 使用訓練好的模型翻譯測試集中的例子
這個實作展示了Transformer架構的完整工作流程,從資料準備到模型訓練再到推理。雖然這是一個簡化版本,但它包含了Transformer的所有核心元件和機制。