擬分3種實現方法:
1.純numpy
2.sklearn中的MLPClassifier
3.pytorch
題目:
在 MNIST 數據集上訓練 MLP 模型并比較不同的激活函數和優化算法
任務描述:
使用 MNIST 數據集中的前 20,000 個樣本訓練一個多層感知機 (MLP) 模型。你需要比較三種不同的激活函數(ReLU、Sigmoid、Tanh)和三種不同的優化算法(SGD、Momentum、Adam),以找到表現最好的組合。模型需要使用一層隱藏層,隱藏單元數量為 128。
要求:
加載并預處理數據,將每個圖像展平成 28x28 的向量,并進行標準化(除以 255)。
使用 one-hot 編碼將標簽進行轉換。
在訓練過程中,分別使用以下激活函數和優化算法:
激活函數:ReLU、Sigmoid、Tanh
優化算法:SGD、Momentum、Adam
對每種激活函數和優化算法組合,訓練模型 10000 個 epoch。
評估模型在驗證集上的準確率,并輸出最優的激活函數與優化算法組合。
輸入:
訓練數據:MNIST 數據集中的前 20,000 個樣本。
每個樣本是一個 28x28 的灰度圖像,標簽為 0-9 的分類。
輸出:
輸出最優激活函數與優化算法組合,以及在驗證集上的準確率。
要求:
不同激活函數與優化算法的組合實現。
對模型的正確率進行評估,并選擇最優方案。
提示:
你可以使用 OneHotEncoder 將標簽進行 one-hot 編碼。
在模型的反向傳播過程中,根據不同的優化算法更新權重。
激活函數可以用 ReLU、Sigmoid 和 Tanh,確保在前向傳播和反向傳播時分別計算激活值及其導數。
import numpy as np
from sklearn.preprocessing import OneHotEncoder
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
np.random.seed(999)def load_data(path="mnist.npz"):# np.load加載數據文件f = np.load(path)# 提取訓練集和測試集的圖片和標簽數據X_train, y_train = f['x_train'], f['y_train']X_test, y_test = f['x_test'], f['y_test']f.close() # 關閉文件# 返回訓練集和測試集return (X_train, y_train), (X_test, y_test)# 加載MNIST數據集
(X_train, y_train), (X_test, y_test) = load_data()X_train=X_train[:2000]
y_train=y_train[:2000]
X_test=X_test[:1000]
y_test=y_test[:1000]
# 數據預處理
X_train = X_train.reshape((-1, 28 * 28)).astype('float32') / 255
X_test = X_test.reshape((-1, 28 * 28)).astype('float32') / 255# 標簽進行獨熱編碼
def one_hot_encoding(labels, num_classes):encoded = np.zeros((len(labels), num_classes))for i, label in enumerate(labels):encoded[i][label] = 1return encodedy_train = one_hot_encoding(y_train, 10)
y_test = one_hot_encoding(y_test, 10)# MLP類的實現
class MLP:def __init__(self, input_size, hidden_size, output_size, activation='relu'):# 初始化權重(Xavier初始化適應Sigmoid/Tanh)self.W1 = np.random.randn(input_size, hidden_size) * np.sqrt(2 / input_size)self.b1 = np.zeros((1, hidden_size))self.W2 = np.random.randn(hidden_size, output_size) * np.sqrt(2 / hidden_size)self.b2 = np.zeros((1, output_size))self.activation = activation # 支持relu/sigmoid/tanhself.losses = [] # 記錄訓練損失# Adam專用初始化self.t = 0 # 時間步計數器[1,2]self.m_W1, self.v_W1 = None, None # 一階/二階矩(權重1)self.m_b1, self.v_b1 = None, None # 一階/二階矩(偏置1)self.m_W2, self.v_W2 = None, None # 一階/二階矩(權重2)self.m_b2, self.v_b2 = None, None # 一階/二階矩(偏置2)# Adam超參數(可調整)self.beta1 = 0.9 # 一階矩衰減率self.beta2 = 0.999 # 二階矩衰減率self.epsilon = 1e-8 # 防除零常數def _activate(self, x):"""激活函數及其導數"""if self.activation == 'relu':return np.maximum(0, x), np.where(x > 0, 1, 0) # 函數值 + 導數elif self.activation == 'sigmoid':s = 1 / (1 + np.exp(-x))return s, s * (1 - s)elif self.activation == 'tanh':t = np.tanh(x)return t, 1 - t**2def forward(self, X):"""前向傳播"""self.z1 = np.dot(X, self.W1) + self.b1self.a1, self.da1 = self._activate(self.z1) # 隱藏層激活值及導數self.z2 = np.dot(self.a1, self.W2) + self.b2self.a2 = np.exp(self.z2) / np.sum(np.exp(self.z2), axis=1, keepdims=True) # Softmax輸出return self.a2def compute_loss(self, y_pred, y_true):"""交叉熵損失"""return -np.mean(y_true * np.log(y_pred + 1e-8))def backward(self, X, y_true, y_pred):"""反向傳播"""m = X.shape[0]# 輸出層梯度 (dL/dz2)error_output = y_pred - y_truedW2 = np.dot(self.a1.T, error_output) / mdb2 = np.sum(error_output, axis=0, keepdims=True) / m# 隱藏層梯度 (dL/dz1)error_hidden = np.dot(error_output, self.W2.T) * self.da1dW1 = np.dot(X.T, error_hidden) / mdb1 = np.sum(error_hidden, axis=0, keepdims=True) / mreturn dW1, db1, dW2, db2def update_params(self, dW1, db1, dW2, db2, lr=0.01, optimizer='sgd', momentum=0.9):"""參數更新(支持SGD/Momentum/Adam)"""if optimizer == 'sgd':self.W1 -= lr * dW1self.b1 -= lr * db1self.W2 -= lr * dW2self.b2 -= lr * db2elif optimizer == 'momentum':# 初始化動量緩存if not hasattr(self, 'v_dW1'):self.v_dW1, self.v_db1 = np.zeros_like(dW1), np.zeros_like(db1)self.v_dW2, self.v_db2 = np.zeros_like(dW2), np.zeros_like(db2)# 更新動量self.v_dW1 = momentum * self.v_dW1 + lr * dW1self.v_db1 = momentum * self.v_db1 + lr * db1self.v_dW2 = momentum * self.v_dW2 + lr * dW2self.v_db2 = momentum * self.v_db2 + lr * db2# 應用更新self.W1 -= self.v_dW1self.b1 -= self.v_db1self.W2 -= self.v_dW2self.b2 -= self.v_db2# Adam實現elif optimizer == 'adam':# 首次調用時初始化矩估計if self.m_W1 is None:self.m_W1, self.v_W1 = np.zeros_like(dW1), np.zeros_like(dW1)self.m_b1, self.v_b1 = np.zeros_like(db1), np.zeros_like(db1)self.m_W2, self.v_W2 = np.zeros_like(dW2), np.zeros_like(dW2)self.m_b2, self.v_b2 = np.zeros_like(db2), np.zeros_like(db2)self.t += 1 # 更新時間步# 更新W1/b1的矩估計self.m_W1 = self.beta1*self.m_W1 + (1-self.beta1)*dW1self.v_W1 = self.beta2*self.v_W1 + (1-self.beta2)*(dW1**2)self.m_b1 = self.beta1*self.m_b1 + (1-self.beta1)*db1self.v_b1 = self.beta2*self.v_b1 + (1-self.beta2)*(db1**2)# 更新W2/b2的矩估計self.m_W2 = self.beta1*self.m_W2 + (1-self.beta1)*dW2self.v_W2 = self.beta2*self.v_W2 + (1-self.beta2)*(dW2**2)self.m_b2 = self.beta1*self.m_b2 + (1-self.beta1)*db2self.v_b2 = self.beta2*self.v_b2 + (1-self.beta2)*(db2**2)# 偏差校正m_W1_hat = self.m_W1 / (1 - self.beta1**self.t)v_W1_hat = self.v_W1 / (1 - self.beta2**self.t)m_b1_hat = self.m_b1 / (1 - self.beta1**self.t)v_b1_hat = self.v_b1 / (1 - self.beta2**self.t)m_W2_hat = self.m_W2 / (1 - self.beta1**self.t)v_W2_hat = self.v_W2 / (1 - self.beta2**self.t)m_b2_hat = self.m_b2 / (1 - self.beta1**self.t)v_b2_hat = self.v_b2 / (1 - self.beta2**self.t)# 參數更新self.W1 -= lr * m_W1_hat / (np.sqrt(v_W1_hat) + self.epsilon)self.b1 -= lr * m_b1_hat / (np.sqrt(v_b1_hat) + self.epsilon)self.W2 -= lr * m_W2_hat / (np.sqrt(v_W2_hat) + self.epsilon)self.b2 -= lr * m_b2_hat / (np.sqrt(v_b2_hat) + self.epsilon)def train(self, X, y, epochs=1000, lr=0.01, optimizer='sgd'):for epoch in range(epochs):y_pred = self.forward(X)loss = self.compute_loss(y_pred, y)self.losses.append(loss)dW1, db1, dW2, db2 = self.backward(X, y, y_pred)self.update_params(dW1, db1, dW2, db2, lr, optimizer)#if epoch % 100 == 0:#print(f"Epoch {epoch}: Loss={loss:.4f}")def predict(self, X):return np.argmax(self.forward(X), axis=1)# 創建MLP模型
input_size = 28 * 28
hidden_size = 128
output_size = 10# 訓練模型
lr = 0.001
epochs = 1000# 訓練和評估模型
best_accuracy = 0 # 保存最佳模型的準確率
best_model = None # 保存最佳模型
best_activation = None # 保存最佳的激活函數
best_optimizer = None # 保存最佳的優化器for activation in ['relu', 'sigmoid', 'tanh']: # 激活函數分別為ReLU、Sigmoid、Tanhfor optimizer in ['sgd', 'momentum', 'adam']: # 優化器分別為SGD、Momentum和Adamprint(f"\nTraining with {activation} activation and {optimizer} optimizer")mlp = MLP(input_size=input_size, hidden_size=hidden_size, output_size=output_size, activation=activation)mlp.train(X_train, y_train, epochs=epochs, lr=lr, optimizer=optimizer)# 評估準確率y_pred = mlp.predict(X_test)accuracy = np.mean(np.argmax(y_test, axis=1) == y_pred)print(f"測試集準確率: {accuracy * 100:.2f}%")# 保存最佳模型if accuracy > best_accuracy:best_accuracy = accuracy # 保存最佳模型的準確率best_model = None # 保存最佳模型best_activation = activation # 保存最佳的激活函數best_optimizer = optimizer # 保存最佳的優化器print(f"\nBest model with activation '{best_activation}' and optimizer '{best_optimizer}'")
print(f"Validation Accuracy: {best_accuracy * 100:.2f}%")
運行結果如下: