1. 程式人生 > 其它 >動手學深度學習 | Softmax迴歸+損失函式+圖片分類資料集 | 07

動手學深度學習 | Softmax迴歸+損失函式+圖片分類資料集 | 07

目錄

Softmax迴歸

首先簡單理解softmax:就是將一個迴歸值轉換成一個概率(也就是把一個實數,定在[0,1.]中)

Softmax迴歸名字叫做迴歸,但其實是一個分類問題。(基本是個老師都會重複講這句話)

分類和迴歸的差別就在,迴歸只有一個輸出,而分類是有多個輸出。一般是有幾個類別多少個輸出。

並且分類輸出的i是預測為第i類的置信度。

exp()這要要進行指數,是因為指數的好處就是不管什麼值,都可以變成非負(概率不能是負數)。

softmax實際上可以理解成也是一個全連線層,得到對應類別的置信度(概率)是需要和之前的輸出進行線性組合的。

最後分類問題的損失函式使用交叉熵損失函式(具體推到如果看不懂的話,建議先跳過)

損失函式

loss用來衡量真實值和預測值的差別,是機器學習中一個非常重要的概念。

這裡給大家簡單介紹3個常用的損失函式。

均方損失,也叫做L2 loss​。

這裡除2是為了方便求導的時候可以和平方的2進行抵消。

上面有3條線,可以視覺化這個損失函式的特性。

  • 藍色:當y=0,變換預測值y',它的函式。可以看到它是一個二次函式
  • 綠色:它的似然函式,我們並沒有要將似然函式,但它確實是統計中一個非常重要的概念,可以看到它的似然函式就是一個高斯分佈了。
  • 橙色:梯度,我們知道它的梯度就是一個一次函式。

我們更新梯度的時候是根據負梯度方向來更新我們的引數,所以它的導數就決定如何來更新我們的引數,如上圖紅色箭頭。大小要看梯度的值。但是有個不好的地方,就是其實預測值和真實值差的比較遠的時候,其實有時候並不想更新那麼大。

L1 loss,梯度就是一個常數,在-1,1。好處就是不管真實值和預測值相差多大,都可以對引數進行一個穩定的更新,這會帶來很多穩定性上的好處。

絕對值函式在0點處是不可導的,所以在0點處會有一個比較劇烈的 變化,也就是當真實值預測值靠的比較近,也就是優化到末期,這裡就會變得不那麼穩定了。

Huber’s Robuts Loss就將L1 loss 和 L2 loss進行了結合。

一般看損失函式,都是看其梯度的函式形狀,來分析當預測值和真實值相差比較遠,還有相差比較近的時候,分別是什麼情況。

圖片分類資料集

操作總結

def load_data_fashion_mnist(batch_size,resize=None):
	# 通過ToTensor例項將影象資料從PIL型別變換成32位浮點數格式 
    trans = [transforms.ToTensor()]     
    if resize:         
    	trans.insert(0, transforms.Resize(resize)) # 插入指定的位置     
    trans = transforms.Compose(trans) # 將多個transform操作進行組合
    # 資料資料集
    mnist_train = torchvision.datasets.FashionMNIST(root="../data", train=True, transform=trans, download=True)
    mnist_test = torchvision.datasets.FashionMNIST(root="../data", train=False, transform=trans, download=True)
    # 返回兩個DataLoader
    return (data.DataLoader(mnist_train, batch_size, shuffle=True,
                            num_workers=get_dataloader_workers()),
            data.DataLoader(mnist_test, batch_size, shuffle=False,
                            num_workers=get_dataloader_workers()))

Softmax迴歸從零開始實現

Softmax也要從零開始實現,因為這是一個非常重要的模型,也是後面所有深度學習模型的一個基礎。

操作總結

batch_size = 256
train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size) # 載入batch_size的資料

num_inputs = 784
num_outputs = 10

# 這裡初始化權重和引數
W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True) # 對每一個輸出都要有一個偏移

# 定義了softmax函式
def softmax(X):
    X_exp = torch.exp(X)
    partition = X_exp.sum(1, keepdim=True) # 按照水平方向(行)來進行求和
    return X_exp / partition # 這裡應用了廣播機制

# 實現了softmax迴歸模型
def net(X):
    return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)

# 交叉熵損失函式
def cross_entropy(y_hat, y):
    return - torch.log(y_hat[range(len(y_hat)), y])


# 累加器
class Accumulator:  
    """在`n`個變數上累加,這是一個累加器"""
    def __init__(self, n):
        self.data = [0.0] * n

    def add(self, *args):
        self.data = [a + float(b) for a, b in zip(self.data, args)] # 這是一個累加的過程

    def reset(self):
        self.data = [0.0] * len(self.data)

    def __getitem__(self, idx): # 按照[] 取值
        return self.data[idx]


# 計算真實值
def accuracy(y_hat, y):  
    """計算預測正確的數量,也就是判斷n行中可以預測對幾個"""
    # 如果存在多行,就只儲存每一行的最大值下標
    if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
        y_hat = y_hat.argmax(axis=1) 
    cmp = y_hat.type(y.dtype) == y 
    # 返回預測正確的數目
    return float(cmp.type(y.dtype).sum()) # 布林型別轉換int在求和


# 評估模型在資料集上的準確性
def evaluate_accuracy(net, data_iter):  
    """計算在指定資料集上模型的精度,就是看模型在資料迭代器上的精度
    net:模型
    data_iter:資料迭代器
    """
    if isinstance(net, torch.nn.Module): # 如果是使用了torch.nn.Module的模型
        net.eval() # 將模型設定為評估模式
    metric = Accumulator(2) # 正確預測數、預測總數
    for X, y in data_iter:
        metric.add(accuracy(net(X), y), y.numel()) # 不斷地加入累加器中
    return metric[0] / metric[1]


# softmax迴歸訓練
def train_epoch_ch3(net, train_iter, loss, updater):  
    """訓練模型一個迭代週期(定義見第3章)。"""
    if isinstance(net, torch.nn.Module):
        net.train() # 開啟訓練模式,也就是要訓練梯度
    
    # [loss,correct_num,total]
    metric = Accumulator(3)
    for X, y in train_iter:
        y_hat = net(X) # softmax迴歸函式
        l = loss(y_hat, y) # 得到loss
        if isinstance(updater, torch.optim.Optimizer): 
            updater.zero_grad()
            l.backward()
            updater.step()
            metric.add(float(l) * len(y), accuracy(y_hat, y),
                       y.size().numel())
        else:
            l.sum().backward()
            updater(X.shape[0]) # 根據批量大小,反向update一下
            metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
    return metric[0] / metric[2], metric[1] / metric[2]


# 訓練函式
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):  
    """訓練模型(定義見第3章)。"""
    # 畫圖物件
    animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],
                        legend=['train loss', 'train acc', 'test acc'])
    for epoch in range(num_epochs):
        train_metrics = train_epoch_ch3(net, train_iter, loss, updater)
        test_acc = evaluate_accuracy(net, test_iter)
        animator.add(epoch + 1, train_metrics + (test_acc,))
    train_loss, train_acc = train_metrics
    assert train_loss < 0.5, train_loss
    assert train_acc <= 1 and train_acc > 0.7, train_acc
    assert test_acc <= 1 and test_acc > 0.7, test_acc


# 使用SGD來優化模型的loss
lr = 0.1

def updater(batch_size):
    return d2l.sgd([W, b], lr, batch_size)
    
# 訓練10個週期
num_epochs = 10
# 這裡還封裝了一個動態的畫圖的功能,非常的酷炫(請面向github程式設計)
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)


Softmax迴歸簡潔實現

batch_size = 256
# 還是將資料加入資料迭代器中
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

# 將模型進行組合
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))
# 完成線性模型的初始化
def init_weights(m):
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, std=0.01)

net.apply(init_weights);

# 交叉熵損失函式
loss = nn.CrossEntropyLoss()

# SGD
trainer = torch.optim.SGD(net.parameters(), lr=0.1)

# 訓練10個epoch
num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

QA

  1. softmax迴歸和logistic迴歸分析是一樣的嗎?如果不一樣的話,那些地方不同?

可以認為logistic迴歸是softmax迴歸的一個特例,因為前者的作用就是進行二分類任務,但是二分類任務只要預測一個類別就可以了,另一個類別的概率就是1-other。

softmax是用於多分類的。我們後面基本不會用到二分類,所以課程直接跳過了logistic迴歸,直接將softmax迴歸。

  1. 為什麼用交叉熵,不用....等其他基於資訊量的度量?

沒有特別為什麼,一個是交叉熵比較好算。

還有就是其實我們真正關注的就是兩個分佈的距離,能夠得到這個距離就可以了。

  1. 交叉熵損失函式為什麼我們只關心正確類,不關心不正確的類呢?

其實不是我們不關心不正確的類,而是因為one-hot編碼就是把不正確類別的概率變成了0,導致我們計算的時候可以忽略掉不正確的類。

  1. 能對MSE的最大似然估計提一下嗎?

沒有講似然函式,是因為這一塊是統計中的概念。我們儘量不涉及太多的統計,是因為統計是可以用來解釋我們模型的一個工具,反過來講,後面的深度學習和統計沒有太多的關係。所以我們主要講的是線性代數,因為使用的所有結構是線性代數。

可以大概講一下,最小化損失就等價於最大化似然函式。似然函式就是有個模型,給定資料的情況下,我的模型(也就是權重)出現的概率有多大?我們要最大化似然函式,也即是找到一個w,是的x出現的概率是最大的,這個也是最合理的解釋。

下面這張最大似然函式和損失函式的關係圖,也就很好理解了。

我們會稍微講一點統計的東西,但是不會深入太多。

  1. batch_size的大小會訓練時間的影響是什麼?

如果CPU的話,基本是看不出區別的。

batch_size大一點的話,在GPU是可以提高訓練的並行度,這樣可以節約訓練的時間。

當然,不管batch_size怎麼取,訓練的總體計算量是沒有發生變化的。

  1. 為什麼不在accuracy函式中除以len(y)做完呢?

因為最後一個batch可能是不能讀滿的,所以最好的做法就是Accumulator進行累加,最後在進行相除。

  1. 在多次迭代之後如果測試進度出現上升後再下降過擬合了嗎?可以提前終止嗎?

很有可能是過擬合了,其實可以再等等,但如果測試精度是一直下降的話,那很有可能是過擬合。

後面會講一些策略來儘量避免,比如來微調學習率,當然也可以加入各種各樣的正則項。

  1. cnn網路學習到的到底是什麼資訊?是紋理還是輪廓還是所有內容的綜合,說不清的那種?

其實我們也不好說cnn學到了什麼,目前,至少幾年前,大家認為cnn是學到的紋理,輪廓cnn其實不那麼在意。

  1. 如果是自己的圖片資料集,需要怎麼做才能用於訓練,怎麼根據本地圖片訓練集和測試機建立迭代器?

這個需要查閱框架的文件,基本都是在本地建立名字為類別的資料夾,然後在對應類別中放入圖片,然後告訴框架上層目錄即可。