多層感知器(Multilayer perceptron, MLP)的一層,可以表示為 ŷ = f(xTW + b),其中的 x 為輸入向量,W 為權重矩陣,b 為偏差向量,f 為 activation function,ŷ 為輸出向量。並且由於堆疊多層時,每一層的每個輸出都是完全連接到下一層的每個輸入,所以這樣的每一層又叫做全連接層(fully connected layer)。多層感知器或者其他各種類神經網路的訓練,則首先需要使用損失函數(loss function)評估網路算出來的 ŷ 和標準答案 y 之間的差異,透過你所選用的工具(例如 PyTorch 或者 TensorFlow)進行反向傳播求出梯度以後,再使用優化器(optimizer)來更新每個 W 和 b。如此反覆進行之後,通常就可以訓練出恰當的類神經網路。
One layer of a multilayer perceptron (MLP) can be written as ŷ = f(xTW + b). Here, x is the input vector, W is the weight matrix, b is the bias vector, f is the activation function, and ŷ is the output vector. When we stack many layers, every output of one layer is connected to every input of the next layer, so each layer like this is also called a fully connected layer. To train an MLP, or any other kind of neural network, we first use a loss function to measure the difference between the network's output ŷ and the correct answer y. Then, we use the tool you choose (such as PyTorch or TensorFlow) to do backpropagation and get the gradients. After that, we use an optimizer to update every W and b. If we repeat this again and again, we can usually train a good neural network.
對於 activation function、損失函數以及最佳化方法,可以依照你要處理的問題特性,或者實際效果來選擇。常用的 activation function 有 sigmoid、ReLU、tanh,損失函數則有常用於回歸問題的 mean squared error (MSE),以及常用於分類問題的 cross entropy 等可以選擇。最佳化方法則通常有 SGD (stochastic gradient descent)、Momentum、AdaGrad、RMSProp,以及 Adam 等方式,若無特殊需求,且在可以選擇的情況下,通常你可以先試試看 Adam。
You can choose the activation function, the loss function, and the optimizer based on the problem you want to solve, or based on how well they work in practice. Common activation functions are sigmoid, ReLU, and tanh. For loss functions, you can use mean squared error (MSE), which is common for regression problems, and cross entropy, which is common for classification problems. Common optimizers are SGD (stochastic gradient descent), Momentum, AdaGrad, RMSProp, and Adam. If you have no special needs and you can choose, you can usually try Adam first.
若要使用 MLP 解決問題,有很多工具可以幫忙,常見或曾經常見的有 PyTorch、TensorFlow,以及 Chainer 等等,不過這些工具為了能讓你做更自由的調整,所以在真正的類神經網路本身和資料的事前處理,都有許多工作要自己做,因此以下先使用 scikit-learn 的 neural_network.MLPClassifier 來示範 MLP 的使用,範例的資料集是 Wine Data Set:
There are many tools that can help you solve problems with an MLP. Common ones, or ones that used to be common, are PyTorch, TensorFlow, and Chainer. But these tools let you make very flexible adjustments, so you have to do a lot of work yourself, both on the neural network itself and on the data preprocessing. So, below we will first use neural_network.MLPClassifier from scikit-learn to show how to use an MLP. The dataset in this example is the Wine Data Set:
import numpy as np from sklearn.datasets import load_wine from sklearn.model_selection import train_test_split from sklearn.neural_network import MLPClassifier dataset = load_wine() print('Data shapes:', dataset.data.shape, dataset.target.shape) X_train, X_test, y_train, y_test = train_test_split(dataset.data, dataset.target) print('Training data shapes:', X_train.shape, y_train.shape) print('Test data shapes:', X_test.shape, y_test.shape) model = MLPClassifier(hidden_layer_sizes=(256, 256)) model.fit(X_train, y_train) pred = model.predict(X_test) print('Accuracy: {:.2f}%'.format(100*np.mean(pred == y_test))) hidden = np.maximum(X_test @ model.coefs_[0] + model.intercepts_[0], 0) hidden = np.maximum(hidden @ model.coefs_[1] + model.intercepts_[1], 0) pred = np.argmax(hidden @ model.coefs_[2] + model.intercepts_[2], axis=1) print('Accuracy: {:.2f}%'.format(100*np.mean(pred == y_test)))在上述範例中,
In the example above,
- 我們只調整了隱藏層的數目,而其他如 learning rate(相當於每次更新權重時「大膽」的程度,通常太大會讓網路不容易收斂到好的結果,太小會導致網路收斂很慢)及 batch size(每次更新權重時,要看多少資料)等超參數則維持預設。
- 使用的隱藏層數目是一組有機會讓測試集準確率在 90% 上下(實際結果會隨著訓練與測試集的亂數切分而有差異)的超參數,各位可以試著跟其他的超參數一併調整看看。
- 最後的那幾行,是將 scikit-learn 學習到的模型參數抽取出來,實際用多層感知器的公式來計算的結果。計算完畢後跟 0 取「np.maximum」,是因為 MLPClassifier 的預設 activation function 是 ReLU。而最後一層不加 activation function,是因為分類問題的輸出層恰好通常不使用 activation function。
- 由於類神經網路是一個對數值變化比較敏感的模型,而 Wine dataset 剛好在數值範圍的方面有較大的變化,因此你也可以試試看先做輸入正規化以後,再用模型進行預測。
- We only adjusted the number of hidden units. Other hyperparameters stay at their defaults, such as the learning rate (how "bold" you are each time you update the weights; if it is too large, the network may not converge to a good result, and if it is too small, the network converges very slowly) and the batch size (how much data to look at each time you update the weights).
- The hidden layer sizes we used are one set of hyperparameters that can give a test accuracy of around 90% (the actual result will differ depending on how the training and test sets are randomly split). You can try adjusting them together with other hyperparameters.
- The last few lines take the model parameters learned by scikit-learn and use them to compute the result with the MLP formula directly. After each computation, we take "np.maximum" with 0, because the default activation function of MLPClassifier is ReLU. We do not add an activation function on the last layer, because the output layer of a classification problem usually does not use one.
- A neural network is sensitive to changes in numerical values, and the Wine dataset happens to have a large difference in value ranges across features. So you can also try input normalization first, and then use the model to predict.
如果你對超參數或特徵等方面的選擇不太有想法,或者想要在二維平面上,看看不同超參數在不同資料上的視覺化效果,可以前往 TensorFlow Playground 網站嘗試。
If you have no idea how to choose hyperparameters or features, or you want to see how different hyperparameters work on different data on a 2D plane, you can try the TensorFlow Playground website.
如前所述,若使用 PyTorch 等工具來訓練類神經網路,則為了能讓你做更自由的調整,有許多超參數都需要一一明確指定,以下是把前一個範的預測工作,改用 PyTorch 來進行:
As we said before, when you use tools like PyTorch to train a neural network, you need to set many hyperparameters one by one, so that you can make flexible adjustments. Below, we do the same prediction task as the previous example, but with PyTorch:
import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset from sklearn.datasets import load_wine from sklearn.model_selection import train_test_split device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') dataset = load_wine() print('Data shapes:', dataset.data.shape, dataset.target.shape) X_train, X_test, y_train, y_test = train_test_split(dataset.data, dataset.target) print('Training data shapes:', X_train.shape, y_train.shape) print('Test data shapes:', X_test.shape, y_test.shape) X_train = torch.FloatTensor(X_train) X_test = torch.FloatTensor(X_test) y_train = torch.LongTensor(y_train) train_loader = DataLoader( TensorDataset(X_train, y_train), batch_size=128, shuffle=True ) model = nn.Sequential( nn.Linear(13, 256), nn.ReLU(), nn.Linear(256, 256), nn.ReLU(), nn.Linear(256, 3) ).to(device) optim = torch.optim.Adam(model.parameters()) criterion = nn.CrossEntropyLoss() model.train() for _ in range(200): for X_batch, y_batch in train_loader: loss = criterion(model(X_batch.to(device)), y_batch.to(device)) optim.zero_grad() loss.backward() optim.step() model.eval() pred = model(X_test.to(device)).argmax(dim=1).detach().cpu().numpy() print('Accuracy: {:.2f}%'.format(100 * (pred == y_test).mean()))在上述範例中,
In the example above,
- 因為工具的預設行為不同,以及為了示範如何指定超參數等緣由,範例中的參數與 scikit-learn 的 MLPClassifier 不盡相同,例如此處的 batch size 是 128。
- 外層迴圈的 200 是 epoch 數目,意義是整批資料要看過 200 次才完成訓練。
- Epoch 數目跟 batch size 一樣,都是超參數,你可以根據自己或前人的經驗,或者在驗證集上的實驗結果調整。一般來說,epoch 數目要夠多,模型才能學得好,但是太多會造成過擬合;而 batch size 則通常會在硬體資源允許的狀況下設定較大的值,以加快訓練速度,但不一定愈大愈好。
- 網路的輸入及輸出大小也都要自己指定,輸入維度為 13 是因為 Wine dataset 的特徵有 13 維,輸出維度為 3 是因為 Wine dataset 的標籤有三種。
- 使用 GPU 做計算時,必須把資料搬到 GPU 上,算完後再視需要搬回來。
- 使用 PyTorch 必須明確寫出 optim.zero_grad、loss.backward,以及 optim.step 這三部,使用其他工具則不必然。
- 「model.eval()」的目的,是切換模型行為,例如讓 dropout 停止隨機丟棄,以及讓 batch normalization 不重新計算統計。如果你需要連梯度計算也關閉,應該要再加上「with torch.no_grad()」的區塊。Notes:關於什麼是 dropout 和 batch normalization,請自行從網路上取得資訊。
- 測試時也可以分 batch 來做(請記得不要 shuffle 即可),此處因為資料量非常少,故未示範。
- Because the tools have different default behaviors, and also to show how to set hyperparameters, the settings in this example are not exactly the same as scikit-learn's MLPClassifier. For example, the batch size here is 128.
- The 200 in the outer loop is the number of epochs. It means the whole dataset must be seen 200 times to finish training.
- The number of epochs is a hyperparameter, just like the batch size. You can adjust it based on your own experience, other people's experience, or experiments on the validation set. Generally speaking, you need enough epochs for the model to learn well, but too many will cause overfitting. For the batch size, you usually set a larger value if your hardware allows it, to make training faster, but a larger batch size is not always better.
- You also need to set the input size and output size of the network yourself. The input size is 13 because the Wine dataset has 13 features, and the output size is 3 because the Wine dataset has three labels.
- When you use a GPU for computation, you must move the data to the GPU, and then move it back after the computation if you need to.
- In PyTorch, you must write out these three steps explicitly: optim.zero_grad, loss.backward, and optim.step. Other tools may not need this.
- The purpose of "model.eval()" is to switch the model's behavior. For example, it makes dropout stop randomly dropping units, and it makes batch normalization stop recalculating statistics. If you also want to turn off gradient calculation, you should add a "with torch.no_grad()" block. Notes: For what dropout and batch normalization are, please look them up online by yourself.
- You can also test in batches (just remember not to shuffle). Here the dataset is very small, so we did not show this.