1. 程式人生 > >MXNET:分類模型

MXNET:分類模型

實現 線性回歸 一個 puts bat pick .data val att

線性回歸模型適用於輸出為連續值的情景,例如輸出為房價。在其他情景中,模型輸出還可以是一個離散值,例如圖片類別。對於這樣的分類問題,我們可以使用分類模型,例如softmax回歸。

為了便於討論,讓我們假設輸入圖片的尺寸為2×2,並設圖片的四個特征值,即像素值分別為\(x_1,x_2,x_3,x_4\)。假設訓練數據集中圖片的真實標簽為狗、貓或雞,這些標簽分別對應離散值\(y_1,y_2,y_3\)

單樣本分類的矢量計算表達式

針對上面的問題,假設分類模型的權重和偏差參數分別為:

\[W=\begin{bmatrix} w_11 & w_12 & w_13 \w_21 & w_22 & w_23 \w_31 & w_32 & w_33 \w_41 & w_42 & w_43 \end{bmatrix}, b=\begin{bmatrix} b_1 & b_2 & b_3 \end{bmatrix} \]

設某樣本為:\(x^{(i)}=[x_1^{(i)}, x_2^{(i)}, x_3^{(i)}, x_4^{(i)}]\),輸出層為\(o^{(i)}=[o_1^{(i)}, o_2^{(i)}, o_3^{(i)}]\),預測為狗、貓或雞的概率為\(y^{(i)}=[y_1^{(i)}, y_2^{(i)}, y_3^{(i)}]\)

小批量樣本的適量計算表達式

如果每個批次有n個樣本,假設輸入特征數為x,輸出類別數為y,則:X的size為nx,W的維度為xy,b的維度為1*y。

\[O=XW+b, \hat(Y)=softmax(O)\]
這裏的加法運算使用了廣播機制。

交叉熵損失函數

Softmax回歸使用了交叉熵損失函數(cross-entropy
loss)。給定分類問題的類別個數m。當樣本i的標簽類別為\(y_j\)時 (\(1 \leq j \leq m\)),設\(q_j^{(i)}=1\)且當\(k \neq j, 1 \leq k \leq m\)\(q_k^{(i)}=0\)
設模型對樣本i在類別\(y_j\)上的預測概率為\(p_j^{(i)}\)\(1 \leq j \leq m\))。假設訓練數據集的樣本數為n,交叉熵損失函數定義為
\[ \ell(\boldsymbol{\Theta}) = -\frac{1}{n} \sum_{i=1}^n \sum_{j=1}^m q_j^{(i)} \log p_j^{(i)}, \]

其中\(\boldsymbol{\Theta}\)代表模型參數。在訓練softmax回歸時,我們將使用優化算法來叠代模型參數並不斷降低損失函數的值。

不妨從另一個角度來理解交叉熵損失函數。在訓練模型時,對於訓練數據集上每個樣本,總體上我們希望模型對這些樣本真實的標簽類別的預測概率盡可能大,也就是希望模型盡可能容易輸出真實的標簽類別。

\(p_{\text{label}_i}\)是模型對樣本i的標簽類別的預測概率,並設訓練數據集的樣本數為n。由於對數函數單調遞增,最大化訓練數據集所有標簽類別的聯合預測概率\(\prod_{i=1}^n p_{\text{label}_i}\)等價於最大化<\(\sum_{i=1}^n \log p_{\text{label}_i}\),即最小化\(-\sum_{i=1}^n \log p_{\text{label}_i}\),因此也等價於最小化以上定義的交叉熵損失函數。

softmax回歸

獲取Fashion-MNIST數據集

我們使用一個類別為數字的數據集MNIST。該數據集中,圖片尺寸為:math:28 times 28,一共包括了10個類別。

from mxnet.gluon import data as gdata

def transform(feature, label):
    return feature.astype('float32') / 255, label.astype('float32')

mnist_train = gdata.vision.MNIST(train=True, transform=transform)
mnist_test = gdata.vision.MNIST(train=False, transform=transform)

feature, label = mnist_train[0]
print 'feature shape: ', feature.shape, 'label: ', label

讀取數據

batch_size = 256
train_iter = gdata.DataLoader(mnist_train, batch_size, shuffle=True)
test_iter = gdata.DataLoader(mnist_test, batch_size, shuffle=False)

初始化模型參數

模型的輸入向量的長度是28×28=784:該向量的每個元素對應圖片中每個像素。由於圖片有10個類別,單層神經網絡輸出層的輸出個數為10。由上一節可知,Softmax回歸的權重和偏差參數分別為784×10和1×10的矩陣。

num_inputs = 784
num_outputs = 10

W = nd.random_normal(scale=0.01, shape=(num_inputs, num_outputs))
b = nd.zeros(num_outputs)
params = [W, b]
for param in params:
    param.attach_grad()

定義softmax函數

#在結果中保留行和列這兩個維度(keepdims=True)
def softmax(X):
    exp = X.exp()
    partition = exp.sum(axis=1, keepdims=True)
    return exp / partition # 這裏應用了廣播機制。

定義模型

通過reshape函數將每張原始圖片改成長度為num_inputs的向量。

def net(X):
    return softmax(nd.dot(X.reshape((-1, num_inputs)), W) + b)

定義損失函數

通過使用pick函數,我們得到了2個樣本的標簽的被預測概率。

y_hat = nd.array([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
y = nd.array([0, 2])
nd.pick(y_hat, y)
# output
[ 0.1  0.5]
<NDArray 2 @cpu(0)>

交叉熵損失函數翻譯成代碼:

def cross_entropy(y_hat, y):
    return -nd.pick(y_hat.log(), y)

模型的評測-計算分類精度

分類準確率即正確預測數量與總預測數量的比。

def accuracy(y_hat, y):
    return (nd.argmax(y_hat, axis=1) == y).asnumpy().mean()

def evaluate_accuracy(data_iter, net):
    acc = 0
    for X, y in data_iter:
        acc += accuracy(net(X), y)
    return acc / len(data_iter)

因為我們隨機初始化了模型net,所以這個模型的準確率應該接近於1 / num_outputs = 0.1。

evaluate_accuracy(test_iter, net)
# output
0.0947265625

訓練模型

在訓練模型時,叠代周期數num_epochs和學習率lr都是可以調的超參數。改變它們的值可能會得到分類更準確的模型。

def sgd(params, lr, batch_size):
    for param in params:
        param[:] = param - lr * param.grad / batch_size
        
num_epochs = 5
lr = 0.1
loss = cross_entropy

def train_cpu(net, train_iter, test_iter, loss, num_epochs, batch_size, params=None, lr=None, trainer=None):
    for epoch in range(1, num_epochs + 1):
        train_l_sum = 0
        train_acc_sum = 0
        for X, y in train_iter:
            with autograd.record():
                y_hat = net(X)
                l = loss(y_hat, y)
            l.backward()
            if trainer is None:
                sgd(params, lr, batch_size)
            else:
                trainer.step(batch_size)
            train_l_sum += nd.mean(l).asscalar()
            train_acc_sum += accuracy(y_hat, y)
        test_acc = evaluate_accuracy(test_iter, net)
        print("epoch %d, loss %.4f, train acc %.3f, test acc %.3f"% (epoch, train_l_sum / len(train_iter), train_acc_sum / len(train_iter), test_acc))

train_cpu(net, train_iter, test_iter, loss, num_epochs, batch_size, params, lr)
# output
epoch 1, loss 0.7105, train acc 0.842, test acc 0.884
epoch 2, loss 0.4296, train acc 0.887, test acc 0.899
epoch 3, loss 0.3840, train acc 0.896, test acc 0.905
epoch 4, loss 0.3607, train acc 0.901, test acc 0.909
epoch 5, loss 0.3461, train acc 0.905, test acc 0.911

預測

data, label = mnist_test[0:9]
predicted_labels = net(data).argmax(axis=1)

Softmax回歸——使用Gluon

Softmax和交叉熵損失函數

分開定義Softmax運算和交叉熵損失函數可能會造成數值不穩定。因此,Gluon提供了一個包括Softmax運算和交叉熵損失計算的函數。它的數值穩定性更好。

程序實現

# -*- coding: utf-8 -*-
from mxnet import init

#定義數據源
import gb
batch_size = 256
train_iter, test_iter = gb.load_data_fashion_mnist(batch_size)

#定義網絡
from mxnet.gluon import nn
net = nn.Sequential()
net.add(nn.Flatten())
net.add(nn.Dense(10))
net.initialize(init.Normal(sigma=0.01))

#損失函數
from mxnet.gluon import loss as gloss
loss = gloss.SoftmaxCrossEntropyLoss()

#優化算法
from mxnet.gluon import Trainer
trainer = Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.1})

#訓練模型
num_epochs = 5
gb.train_cpu(net, train_iter, test_iter, loss, num_epochs, batch_size, None,
             None, trainer)

基礎函數包 gb.py

from mxnet.gluon import data as gdata
from mxnet import autograd
from mxnet import ndarray as nd

def transform(feature, label):
    return feature.astype('float32') / 255, label.astype('float32')

mnist_train = gdata.vision.MNIST(train=True, transform=transform)
mnist_test = gdata.vision.MNIST(train=False, transform=transform)

def load_data_fashion_mnist(batch_size):
    train_iter = gdata.DataLoader(mnist_train, batch_size, shuffle=True)
    test_iter = gdata.DataLoader(mnist_test, batch_size, shuffle=False)
    return train_iter, test_iter


def accuracy(y_hat, y):
    return (nd.argmax(y_hat, axis=1) == y).asnumpy().mean()

def evaluate_accuracy(data_iter, net):
    acc = 0
    for X, y in data_iter:
        acc += accuracy(net(X), y)
    return acc / len(data_iter)

def sgd(params, lr, batch_size):
    for param in params:
        param[:] = param - lr * param.grad / batch_size

def train_cpu(net, train_iter, test_iter, loss, num_epochs, batch_size, params=None, lr=None, trainer=None):
    for epoch in range(1, num_epochs + 1):
        train_l_sum = 0
        train_acc_sum = 0
        for X, y in train_iter:
            with autograd.record():
                y_hat = net(X)
                l = loss(y_hat, y)
            l.backward()
            if trainer is None:
                sgd(params, lr, batch_size)
            else:
                trainer.step(batch_size)
            train_l_sum += nd.mean(l).asscalar()
            train_acc_sum += accuracy(y_hat, y)
        test_acc = evaluate_accuracy(test_iter, net)
        print("epoch %d, loss %.4f, train acc %.3f, test acc %.3f" % (
            epoch, train_l_sum / len(train_iter), train_acc_sum / len(train_iter), test_acc))

MXNET:分類模型