《動手學深度學習》戴口罩的胡蘿蔔組 - 戶建坤 第二次打卡 (過欠擬合; 梯度消失爆炸;RNN進階; 機器翻譯;注意力機制與Seq2seq; Transformer; CNN基礎)

過欠擬合以及解決方案

知識重點

1.K交叉驗證沒明白有什麼用?
2.單層神經元個數, 相當於多項式擬合的次數, 但是好像神經網絡都是1次方, 那到底是相當於層數加深? 還是單層參數變大? 還是目前的網絡都不體現 x 的多次方???
3.難道需要和代碼中構造的 3dim features 一樣, 預處理特徵或者特徵工程的時候用別的工具提取次方信息? 也就是神經網絡特徵提取忽略了很多數學意義特徵, 需要有 kaggle 裏面好的特徵工程的方法輔助.
4.opt 賦予 w 得到 opt_w 代替 w 參與計算. 但是之前直接 params list 傳入 opt, 那種做法是怎樣的? torch.optim.SGD(params=[net.weight], lr=lr, weight_decay=wd) 平時會用到嗎? 還是 loss 多加上一部分, 看別人的代碼好了.

def fit_and_plot_pytorch(wd):
    # 對權重參數衰減。權重名稱一般是以weight結尾
    net = nn.Linear(num_inputs, 1)
    nn.init.normal_(net.weight, mean=0, std=1)
    nn.init.normal_(net.bias, mean=0, std=1)
    optimizer_w = torch.optim.SGD(params=[net.weight], lr=lr, weight_decay=wd) # 對權重參數衰減
    optimizer_b = torch.optim.SGD(params=[net.bias], lr=lr)  # 不對偏差參數衰減
    
    train_ls, test_ls = [], []
    for _ in range(num_epochs):
        for X, y in train_iter:
            l = loss(net(X), y).mean()
            optimizer_w.zero_grad()
            optimizer_b.zero_grad()
            
            l.backward()
            
            # 對兩個optimizer實例分別調用step函數,從而分別更新權重和偏差
            optimizer_w.step()
            optimizer_b.step()
        train_ls.append(loss(net(train_features), train_labels).mean().item())
        test_ls.append(loss(net(test_features), test_labels).mean().item())
    d2l.semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'loss',
                 range(1, num_epochs + 1), test_ls, ['train', 'test'])
    print('L2 norm of w:', net.weight.data.norm().item())

5.標準的 opt 使用方法

optimizer = torch.optim.SGD(net.parameters(), lr=0.5)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, optimizer)

課後題

1

發生欠擬合的時候在訓練集上訓練誤差不能達到一個比較低的水平,所以過擬合和欠擬合不可能同時發生。其實過擬合發生時, 至少把 val loss 降一下, 再吐槽 train loss 不夠低, 所以目前不認爲是欠擬合. 也就是val loss 和 train loss 相差比較大, 先考慮減小複雜度, 且 train loss 儘量別影響, 再加上權重 l2 正則化, 其實這個可以放在減小複雜度之前來代替, 以及 dropout. 比如我的可以減少參數大小, 增大 l2 的比值, dropout 變大些等. 用 Rayhane 最初的代碼和我的代碼平衡的一版本.

梯度消失和爆炸

知識重點

1.Xavier隨機初始化, 模型參數初始化後,每層輸出的方差不該受該層輸入個數影響,且每層梯度的方差也不該受該層輸出個數影響。保證了二階矩一樣, 不過不知道在數學上有什麼優勢, 類似於 batch nomalization 把?
2.PyTorch中nn.Module的模塊參數都採取了較爲合理的初始化策略(不同類型的layer具體採樣的哪一種初始化方法的可參考源代碼) 所以在線性迴歸的簡潔實現中,我們使用torch.nn.init.normal_()使模型net的權重參數採用正態分佈的隨機初始化方式. 這種平時並不常見. 同時也注意用訓練模型的初始化方式, 以及李宏毅老師當時的 PPT.
3.考慮環境因素, 那三個偏差搞不清楚, 感覺具體情況再分析吧, 反正就是沒挖掘出的因素.
4.pd.frame 和 csv 的結合使用. 以及數據預處理模板.

test_data = pd.read_csv("/home/kesci/input/houseprices2807/house-prices-advanced-regression-techniques/test.csv")
train_data = pd.read_csv("/home/kesci/input/houseprices2807/house-prices-advanced-regression-techniques/train.csv")
train_data.iloc[0:4, [0, 1, 2, 3, -3, -2, -1]]
all_features = pd.concat((train_data.iloc[:, 1:-1], test_data.iloc[:, 1:]))


numeric_features = all_features.dtypes[all_features.dtypes != 'object'].index
all_features[numeric_features] = all_features[numeric_features].apply(
    lambda x: (x - x.mean()) / (x.std()))
# 標準化後,每個數值特徵的均值變爲0,所以可以直接用0來替換缺失值
all_features[numeric_features] = all_features[numeric_features].fillna(0)

# dummy_na=True將缺失值也當作合法的特徵值併爲其創建指示特徵
all_features = pd.get_dummies(all_features, dummy_na=True)
all_features.shape

5.模型, K驗證代碼模板. K驗證屬於更靠譜的 validation.

loss = torch.nn.MSELoss()

def get_net(feature_num):
    net = nn.Linear(feature_num, 1)
    for param in net.parameters():
        nn.init.normal_(param, mean=0, std=0.01)
    return net
    
def log_rmse(net, features, labels):
    with torch.no_grad():
        # 將小於1的值設成1,使得取對數時數值更穩定
        clipped_preds = torch.max(net(features), torch.tensor(1.0))
        rmse = torch.sqrt(2 * loss(clipped_preds.log(), labels.log()).mean())
    return rmse.item()
def get_k_fold_data(k, i, X, y):
    # 返回第i折交叉驗證時所需要的訓練和驗證數據
    assert k > 1
    fold_size = X.shape[0] // k
    X_train, y_train = None, None
    for j in range(k):
        idx = slice(j * fold_size, (j + 1) * fold_size)
        X_part, y_part = X[idx, :], y[idx]
        if j == i:
            X_valid, y_valid = X_part, y_part
        elif X_train is None:
            X_train, y_train = X_part, y_part
        else:
            X_train = torch.cat((X_train, X_part), dim=0)
            y_train = torch.cat((y_train, y_part), dim=0)
    return X_train, y_train, X_valid, y_valid
    
def k_fold(k, X_train, y_train, num_epochs,
           learning_rate, weight_decay, batch_size):
    train_l_sum, valid_l_sum = 0, 0
    for i in range(k):
        data = get_k_fold_data(k, i, X_train, y_train)
        net = get_net(X_train.shape[1])
        train_ls, valid_ls = train(net, *data, num_epochs, learning_rate,
                                   weight_decay, batch_size)
        train_l_sum += train_ls[-1]
        valid_l_sum += valid_ls[-1]
        if i == 0:
            d2l.semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'rmse',
                         range(1, num_epochs + 1), valid_ls,
                         ['train', 'valid'])
        print('fold %d, train rmse %f, valid rmse %f' % (i, train_ls[-1], valid_ls[-1]))
    return train_l_sum / k, valid_l_sum / k

課後題

RNN進階

知識重點

1.GRU, LSTM, 多層, 雙向, 而且 LSTM 最複雜, 並且有很多 0, 1 含義的 gate, 在我的代碼中其實可以看看 gate 來判斷上下文關係, 或者限定 gate 來阻止大於 5步以後的傳遞.
2.nn 中就有這些層.

gru_layer = nn.GRU(input_size=vocab_size, hidden_size=num_hiddens)
model = d2l.RNNModel(gru_layer, vocab_size).to(device)

課後題

門的名字記不住, 而且需要找實驗證明設計的門的確是這樣, 也就是我覺得 gate 的計算有些太簡單了, 能不能加一些如 HMM 的思想進去.

機器翻譯

知識重點

1.核心在於 seq 到 seq 且不定長, 則基本模型 encoder 以後拿出 hidden 作爲 decoder 的初始, 然後 開始自迴歸預測下一幀.
2.masked_CELoss 就是簡單地先算 loss, 然後根據長度把後面的 mask 掉.

class MaskedSoftmaxCELoss(nn.CrossEntropyLoss):
    # pred shape: (batch_size, seq_len, vocab_size)
    # label shape: (batch_size, seq_len)
    # valid_length shape: (batch_size, )
    def forward(self, pred, label, valid_length):
        # the sample weights shape should be (batch_size, seq_len)
        weights = torch.ones_like(label)
        weights = SequenceMask(weights, valid_length).float()
        self.reduction='none'
        output=super(MaskedSoftmaxCELoss, self).forward(pred.transpose(1,2), label)
        return (output*weights).mean(dim=1)

3.平時每個都選最大的爲結果, 對於 seq 整體而言是局部最優的, 除了暴力枚舉, 維特比, dp, 還有一個限定個數版本的剪枝: Beam Search, 每次只留兩個最好的, 則每次從 a 找兩個, b 找兩個, 然後再取兩個.

課後題

比較熟這一塊. 但是好奇這樣 encoder-decoder 模型下的能力有多大? 只靠一個 hidden vector 這麼壓縮嗎? 而且對於 decoder 還只初始化了一次, 看看當時的正確率. 是不是已經夠好了, 證明我們其實現在濫用複雜的網絡.

注意力機制與 seqs 模型

知識重點

1.之前 encoder-decoder 只通過 hidden 狀態連接, 一是句子太長長時間建模就消失了, 二是 target seqs 可能只和一部分輸入有關. 所以用 query 拿到 attention, 然後加起來得到 context vec, 其實也可以不加而是小 RNN.
2.attention 是由 key 和 query, 而 value 是最後的和 context vec 直接相關的, 而點積 α(q,k)=⟨q,k⟩/d−−√. 目的是算出來的值更平滑, 能量跟維度無關. 它不增加額外參數, 然後 query 和 value 是怎麼得到的, 不歸 attention 管. 還有 dropout 是怎麼回事??

attention_weights = self.dropout(masked_softmax(scores, valid_length))

點積可以矩陣並行計算.
3.softmaxMask就和理解的一樣.
4.torch.bmm 就是把第一維當做 batch 去 for 它, 剩下的兩維當做 mat.
5.α(k,q)=vTtanh(Wkk+Wqq) bahan 的attention, 相加的那個, 其實叫 MLP attention, 會先把 key 和 value 都投影到 hidden_unit 維度, 然後 add, 然後經過 MLP_tanh 變爲 scalar. 有額外的算 attention 的參數, 3 個, 但是其實和 dot 差不多. 它竟然也有 dropout.
6.attention 關鍵一, 初始化 decoder attention 和傳統方式一樣, 而不是像現在的 Tacotron.

def init_state(self, enc_outputs, enc_valid_len, *args):
        outputs, hidden_state = enc_outputs
#         print("first:",outputs.size(),hidden_state[0].size(),hidden_state[1].size())
        # Transpose outputs to (batch_size, seq_len, hidden_size)
        return (outputs.permute(1,0,-1), hidden_state, enc_valid_len)
        #outputs.swapaxes(0, 1)

7.attention 關鍵二, 上一幀用來自迴歸和傳統一樣, 輸入 decoder rnn 中, 不過這一次拼接上 context vec, 而這個 context vec 是由上一步的 decoder rnn hidden 作爲 query 得到的. 然後拿到這個步的 decoder rnn hidden 結果後, 只經過一個 dense 層, 無激活函數, 不 concat 上一幀的結果了. 而 Tacotron 可以要拼接. 很奇怪.
8.基於 attention 的 decoder 方式, 把 attention 和 decoder 合起來可以叫 Seq2SeqAttentionDecoder. 而不用只叫 decoder 容易混淆, 不舒服.

課後題

解碼器RNN仍由編碼器最後一個時間步的隱藏狀態初始化。
因爲信息很豐富, 畢竟傳統的只用這個就夠了.
同時 Rayhane 的去除前後靜默段確實方法很重要, 我需要好好處理下.
key 矩陣一樣, query 一樣, 算出來的 attention 就一樣, 不過前提是 mask 也得一樣.

Transformer

知識重點

1.所有 RNN, Attention 結果, forward 函數都是走完時間步的.
2.Multi-head Attention 是多個頭拼接, 而且運算速度不一定 * N 倍. 同時, self Attention 的 alignment 有什麼特點呢? 單頭的一定是對角線吧? 而多頭的雖然不必要每個都是對角線, 但是應該又一個是很像對角線, 並且剩下的不超過這個對角線, 是前綴和的感覺?
3.Attention 爲什麼每次都只能是 hidden 查詢, 如果和 RNN 結合起來還得 for 循環, 且每次時間步 1 個的查詢. 能不能用 output 等. 對應的 Transformer 能不能 TeacherForce
4.最大的疑惑, 如果一次 decoderBlock 會向後移動一個時間步, 那麼多個堆疊起來, 類似於 RNN 的堆疊, 把 1 個時間步拆解爲 1/d, 那麼對於 ASR 來說, 最後的一個模塊, 是 RNN 的堆疊, 或者 Transformer 的堆疊, 則 1…M, 把單位 unit 的跨度假設均分爲 M 分, 那麼原序列 1 個就變爲了 1…M 個, 利用 RNN 的時間偏移能力來自動劃分更小的單元.
5.PositionalEncoding 爲什麼這樣設計? 不明白. 而且爲什麼這樣設計以後 alignment 還是會不好?

假設輸入序列的嵌入表示 X∈Rl×d , 序列長度爲 l 嵌入向量維度爲 d ,則其位置編碼爲 P∈Rl×d ,輸出的向量就是二者相加 X+P 。
位置編碼是一個二維的矩陣,i對應着序列中的順序,j對應其embedding vector內部的維度索引。我們可以通過以下等式計算位置編碼:
Pi,2j=sin(i/100002j/d)
Pi,2j+1=cos(i/100002j/d)
第4維和第5維有相同的頻率但偏置不同。第6維和第7維具有更低的頻率;因此positional encoding對於不同維度具有可區分性。
也就是維度內部是連續的 sin, 維度與維度之間不同的頻率, 而位置的不同體現在了在曲線上 X 不同對應的 Y 的值.

class PositionalEncoding(nn.Module):
    def __init__(self, embedding_size, dropout, max_len=1000):
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(dropout)
        self.P = np.zeros((1, max_len, embedding_size))
        X = np.arange(0, max_len).reshape(-1, 1) / np.power(
            10000, np.arange(0, embedding_size, 2)/embedding_size)
        self.P[:, :, 0::2] = np.sin(X)
        self.P[:, :, 1::2] = np.cos(X)
        self.P = torch.FloatTensor(self.P)
    
    def forward(self, X):
        if X.is_cuda and not self.P.is_cuda:
            self.P = self.P.cuda()
        X = X + self.P[:, :X.shape[1], :]
        return self.dropout(X)

CNN 基礎

知識重點

1.二維互相關運算, 卷積層得名於卷積運算,但卷積層中用到的並非卷積運算而是互相關運算。我們將核數組上下翻轉、左右翻轉,再與輸入數組做互相關運算,這一過程就是卷積運算。由於卷積層的核數組是可學習的,所以使用互相關運算與使用卷積運算並無本質區別。
2.二維卷積層輸出的二維數組可以看作是輸入在空間維度(寬和高)上某一級的表徵,也叫特徵圖(feature map)。邊緣檢測就是原來顏色加加減減局部特徵新形成的特徵圖.

課後題

1.原圖片是3個通道, 輸入通道數是3,輸出通道數是10,所以參數數量是10 \times 3 \times 3 \times 3 + 10 = 28010×3×3×3+10=280
2.conv2d = nn.Conv2d(in_channels=3, out_channels=4, kernel_size=3, padding=2) 輸入一張形狀爲3 \times 100 \times 1003×100×100的圖像,輸出的形狀爲:輸出通道數是4,上下兩側總共填充4行,卷積核高度是3,所以輸出的高度是104 - 3 + 1=102104−3+1=102,寬度同理可得。pytorch 的 padding 是上下均.

LeNet

知識重點

1.LeNet 就是間隔卷積層, pooling 層迭代, 然後 Flatten, 再 MLP.
2.nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5, padding=2), #b12828 =>b62828. nn.Conv2d 默認是 channel, h, w 嗎? 挺不習慣的.
3.net = torch.nn.Sequential( ) 比較常用, 而且可以 net.to(device)

def try_gpu():
    """If GPU is available, return torch.device as cuda:0; else return torch.device as cpu."""
    if torch.cuda.is_available():
        device = torch.device('cuda:0')
    else:
        device = torch.device('cpu')
    return device

device = try_gpu()
device

CNN 進階

知識重點

1.AlexNet, 將sigmoid激活函數改成了更加簡單的ReLU激活函數, 用Dropout來控制全連接層的模型複雜度, 引入數據增強,如翻轉、裁剪和顏色變化,從而進一步擴大數據集來緩解過擬合。
2.VGG:通過重複使⽤簡單的基礎塊來構建深度模型。
Block:數個相同的填充爲1、窗口形狀爲 3×3 的卷積層,接上一個步幅爲2、窗口形狀爲 2×2 的最大池化層。
卷積層保持輸入的高和寬不變,而池化層則對其減半。
3.⽹絡中的⽹絡(NiN)LeNet、AlexNet和VGG:先以由卷積層構成的模塊充分抽取 空間特徵,再以由全連接層構成的模塊來輸出分類結果。
NiN:串聯多個由卷積層和“全連接”層構成的小⽹絡來構建⼀個深層⽹絡。
⽤了輸出通道數等於標籤類別數的NiN塊,然後使⽤全局平均池化層對每個通道中所有元素求平均並直接⽤於分類。明確把通道的含義使用上, 不僅是同一個通道用同一個核, 最後全局平均池化層更是把輸出通道每個枚舉獨立出來, 直接和 one-hot 標籤對比.
5.GoogLeNet, 由Inception基礎塊組成。
Inception塊相當於⼀個有4條線路的⼦⽹絡。它通過不同窗口形狀的卷積層和最⼤池化層來並⾏抽取信息,並使⽤1×1卷積層減少通道數從而降低模型複雜度。
可以⾃定義的超參數是每個層的輸出通道數,我們以此來控制模型複雜度。
CNN 的結構比全連接多一個複雜度, 可以調整通道, 並保持位置關係不變, 合併關係不變, 很方便. Inception 的多超參數去抽取不同大小視野的思路很好.

課後題

1.通過重複使⽤簡單的基礎塊來構建深度模型。可以先經過 1*1 CNN 覈減少通道, 非線性映射而且, 減少數據複雜度. 一般前幾層都是減少數據維度複雜度, 後面是逐步抽取高層特徵, 這樣就得 feature map 也來越小, 因爲越來越高嘛, 並且通道數越來越大, 而且要用 pooling 層.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章