hw-2 李宏毅2022年作業2 phoneme識別 單strong-hmm詳細解釋。


 目錄

系列文章

前言 :

項目: 

       一:  數據 :

          二: 模型 

三 : 訓練和評估 

四:main函數和訓練過程 

 五 后處理 。


 

系列文章

2022李宏毅作業hw1—新冠陽性人員數量預測。_亮子李的博客-CSDN博客

目錄

系列文章 


前言 :

     作業二 真的很難 。而且會出現訓練集沒辦法過擬合的情況 也就是訓練集准確度沒辦法到百分之百  數據太多了 。  向實驗室申請了一台服務器來跑這個作業,最后在kaggle上 pub達到了strong pri沒有 哭了 試了好久都不太行。  

    但還是試着寫寫思路吧。git地址 和kaggle網址 

https://github.com/xiaolilaoli/lihongyi2022homework/tree/main/2_phoneme

ML2021Spring-hw2 | Kaggle

項目: 

        我的習慣 一般會把一個小項目做成下面的幾個模塊 這樣非常的清晰

       第一個就是數據模塊 , 這部分的作用是讀入數據。 接口輸入是數據地址 返回的是各種loader

       第二個是模型模塊 。沒啥好說的 ,就是創造模型。接口輸入是特征數和分類數(分類任務), 出口是模型。

        第三個是訓練模塊 。 這一部分要傳入訓練集 驗證集 和超參數。步驟就是 梯度歸0 模型前向過程得到預測結果  預測和標簽比得到loss loss回傳。模型更新。還有一些記錄。  訓練過后有一個驗證過程 , 讓模型在驗證集上跑 ,最后得到驗證集准確率。

        第四個是驗證模塊。一般負責從模型和測試數據出發,得到想要驗證的數據 。

        第五個是main函數 。 整合各個模塊 進行數據模型的傳遞。

          我覺得模塊化是非常有必要的 ,我一直想做一個萬能模塊。特別是數據部分,后來發現,想多了,面對每一個項目 總是免不了微調。大家可以逐漸形成自己的模塊。

 

       一:  數據 :

 

 看到 數據一段編碼好了的 聲音片段 。  每一段是39長度的向量。 然后會給出前后五段的 數據 也就是我們有11*39 = 429 維度的一個特征 根據這個特征去做分類 。 有多少類呢 ?有39類 。數據被放在一個.npy的numpy文件中 。跟上次一樣, 先創建一個讀數據的dataset

        x = np.load(traindataPath)
        y = np.load(trainlabelPath)
        y = y.astype(np.int)

把x,y讀進來 把y轉為int型 

​  可以看到x是1168435個數據 每個數據都有429維,對應一個標簽。

class phonemeDataset(Dataset):
    def __init__(self, x, y=None):
        self.x = torch.tensor(x)
        self.y = y
        if y is not None:
            self.y = torch.LongTensor(y)

    def __getitem__(self, index):
        if self.y is not None:
            return self.x[index].float(), self.y[index]
        else:
            return self.x[index].float()

    def __len__(self):
        return len(self.x)

 dataset 是非常基礎的 init負責把數據讀進來 getitem 負責在訪問數據集時返回數據  我們返回的是float類型的特征數據 和longtensor的標簽  len負責返回長度 。。 用這個文件做數據很簡單。直接放完整代碼 。 這部分被我放在model.utils 中的data里 

import numpy as np
from torch.utils.data import Dataset,DataLoader
import torch
from sklearn.model_selection import train_test_split



class phonemeDataset(Dataset):
    def __init__(self, x, y=None):
        self.x = torch.tensor(x)
        self.y = y
        if y is not None:
            self.y = torch.LongTensor(y)

    def __getitem__(self, index):
        if self.y is not None:
            return self.x[index].float(), self.y[index]
        else:
            return self.x[index].float()

    def __len__(self):
        return len(self.x)

def getDataset(path,mode):
    if mode == 'train' or mode == 'trainAndVal':
        traindataPath = path + '/' + 'train_11.npy'
        trainlabelPath = path + '/' + 'train_label_11.npy'
        x = np.load(traindataPath)
        y = np.load(trainlabelPath)
        # x = x[:, 3*39:8*39]
        y = y.astype(np.int)
        if mode == 'trainAndVal':
            trainAndValset = phonemeDataset(x, y)
            return trainAndValset
        else:
            train_x,val_x, train_y, val_y = train_test_split(x, y, test_size=0.05,shuffle=True,random_state=0)
            trainset = phonemeDataset(train_x, train_y)
            valset = phonemeDataset(val_x, val_y)
            return trainset, valset

    elif mode == 'test':
        testdataPath = path + '/' + 'test_11.npy'
        x = np.load(testdataPath)
        # x = x[:, 3*39:8*39]
        testset = phonemeDataset(x)
        return testset

def getDataLoader(path, mode, batchSize):
    assert mode in ['train', 'test', 'trainAndVal']
    if mode == 'train':
        trainset, valset = getDataset(path, mode)
        trainloader = DataLoader(trainset,batch_size=batchSize, shuffle=True)
        valloader = DataLoader(valset,batch_size=batchSize, shuffle=True)
        return trainloader,valloader
    elif mode == 'trainAndVal':
        trainAndValset = getDataset(path, mode)
        trainAndValloader = DataLoader(trainAndValset,batch_size=batchSize, shuffle=True)
        return trainAndValloader

    elif mode == 'test':
        testset = getDataset(path, mode)
        testLoader = DataLoader(testset, batch_size=batchSize, shuffle=False)
        return testLoader

          二: 模型 

很基礎的一個模型 就是fc+relu+dropout 我不知道怎么樣去改出來一個好模型 只是感覺 都差不多。

這部分被我放在model.utils 中的model里 

class myNet(nn.Module):
    def __init__(self, inDim, outDim):
        super(myNet,self).__init__()
        self.fc1 = nn.Linear(inDim, 1024)
        self.relu1 = nn.ReLU()
        self.drop1 = nn.Dropout(0.5)
        self.fc2 = nn.Linear(1024, 1024)
        self.relu2 = nn.ReLU()
        self.drop2 = nn.Dropout(0.5)
        self.fc3 = nn.Linear(1024, 512)
        self.relu3 = nn.ReLU()
        self.fc4 = nn.Linear(512, outDim)

    def forward(self, x):
        x = self.fc1(x)
        x = self.relu1(x)
        x = self.drop1(x)
        x = self.fc2(x)
        x = self.relu2(x)
        x = self.drop2(x)
        x = self.fc3(x)
        x = self.relu3(x)
        x = self.fc4(x)
        if len(x.size()) > 1:
            return x.squeeze(1)
        else:
            return x

 

三 : 訓練和評估 

                這部分和之前幾乎一樣 。我就不放代碼了 可以去git下 

 

 
        

四:main函數和訓練過程 

        超參和模型設置  以及創造loader 

def seed_everything(seed=1):
    '''
    設置整個開發環境的seed
    :param seed:
    :param device:
    :return:
    '''
    random.seed(seed)
    os.environ['PYTHONHASHSEED'] = str(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    # some cudnn methods can be random even after fixing the seed
    # unless you tell it to be deterministic
    torch.backends.cudnn.deterministic = True






#################################################################

batch_size = 512
learning_rate = 1e-4
seed_everything(1)

epoch = 1000
# w = 0.00001
device = 'cuda:1' if torch.cuda.is_available() else 'cpu'
##################################################################

dataPath = 'timit_11'
savePath = 'model_save/My'
trainloader, valloader = getDataLoader(dataPath, 'train', batchSize=batch_size)
test_loader = getDataLoader(dataPath, 'test', batchSize=batch_size)
optimizer = optim.SGD(model.parameters() , lr=learning_rate, weight_decay=0.0001,momentum=0.9)
scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, eta_min=1e-9, T_0=20)
criterion = nn.CrossEntropyLoss()

創造優化器和loss 然后訓練

train_val(model,trainloader,valloader, optimizer=optimizer ,scheduler=scheduler, loss= criterion, epoch=epoch, device=device, save_=savePath)

    1 訓練過程中 會發現訓練集的准確率比測試集還高。 這是因為我們有drop過程。

     2 驗證准確度會與drop率有關 大家不要迷信准確率。 最后我上面這個模型驗證准確在百分之88.5左右就上不去了 。我是訓練了 1000個epoch。

      3 換了好幾個優化器 換了好幾個lr  最后結果總是上不去, 然后上傳到kaggle 上 得到的分數是 0.741

 五 后處理 。

                 很早就得到了這個結果 卻差別人一大截。 然后在網上找到了一個大佬做的視頻。 【ML2021李宏毅機器學習】作業2 TIMIT phoneme classification 思路講解_嗶哩嗶哩_bilibili

然后跟着大佬的思路 (抄他的代碼),把結果提了兩個點。  然后我來盡量講一下這個思路 。 實在是太難了 。這個思路就是HMM, hmm是什么 大家可以參考這里。記得跟着這個鏈接里的算一次。就算沒看懂,也要把他是怎么算出結果的。用手把步驟寫出來、下面我盡量帶大家理解我的想法。好吧 我也不確定我的想法對。 

HMM與分詞、詞性標注、命名實體識別-碼農場

其實hmm感覺上是什么呢 ? 就感覺是天下大勢和小家之勢的相互關系。 天下有大勢,當你實力強時,你就可以不完全被大勢挾裹,但是如果你實力微弱,就會被卷入其中。

后處理的代碼如下。這串代碼非常的難看懂。一定要仔細調試並與上面的例子結合起來看。

 1 加載訓練標簽。

alllength = 451552
#transition matrix
data_root = 'timit_11/train_label_11.npy'
train_label = np.load(data_root)
print('size of train data:{}'.format(train_label.shape))
trans_table = np.zeros((39,39))
train_label = train_label.astype('int')

2  統計轉移矩陣。 trains_table[i,j]位置的元素  表示在所有標簽里,從i到j的個數。統計后,在每行進行歸一化, 就得到從當現在的標簽是i  他下一個是其他標簽各個的概率。

for i in range(len(train_label)-1):
    trans_table[train_label[i], train_label[i+1]] += 1
trans_table_norm = trans_table/ np.sum(trans_table,axis=1,keepdims=True)

3:得到發射矩陣。 這就是小家小勢了 。也就是模型預測的置信度多高。如果你模型預測一個結果的置信度是0.99 那么相信天下大勢也很難影響你。test_ln_softmax有451552行 每行都是每一類的預測概率。

m = nn.Softmax(dim=1)
test_ln_softmax = m(torch.tensor(raw_output))
test_ln_softmax = np.array((test_ln_softmax))

4:將他們log化 這里應該是為了乘起來快一點。 大家都知道 log的加就相等於數字相乘。反正我們只是比較相對大小。所以這里log后可以用加法代替乘法。

trans_table_norm += 1e-17
trans_table_norm = np.log(trans_table_norm)
test_ln_softmax = test_ln_softmax + 1e-17
test_ln_softmax = np.log(test_ln_softmax)

5: 定義路徑和初始狀態。 

tracking = np.zeros((alllength, 39))
last_state = test_ln_softmax[0]

6: 計算過程  開始計算了 。 

 

prob = last_state.reshape(39,1) + trans_table_norm + test_ln_softmax[i]

這一句相當於上面例子里的這一段。前一個是y0的概率然后乘以從y0轉到yi的概率 再乘以現在這個表現為yi的概率。 我們就得到了在前一個表現為y0的情況下,這個表現為yi的概率。 我們會得到39個。我們來細看 這一句到底是怎么算的。

        laststate  就是前面一天 表現為yi的概率 。 長度是39 代表了每一類。但是這里有 reshape(39,1)  就表示變成了39行。 transtabnle是一個39乘以39的矩陣。這兩個矩陣相加,就要對latestate進行行擴充。  下面的0 就表示 預測值為0的概率。 

 

 

 

再看 下面兩個的相加  我們知道 其實就是相乘  tran的每個位置i,j都是i到j的概率。那么 相加之后的矩陣意思就是 如果上一天是yi  今天表現為yj的概率。

 test_ln_softmax[i]   是一個1*39長的向量  他每個位置意思是不管天下大勢 我表現為 y?的概率 。由於他要和39*39的矩陣相加 所以他也要擴充。  

 

 

所以這三個矩陣相加 i,j位置的值 就表示 綜合了天下大勢和小家局勢。 后 前一天是i今天是j的概率。

current_state = np.max(prob, axis=0)

取列的最大值。 那么固定的是j的值 如果j=0 就是意思找  昨天預測值是多少時 今天能得到0的概率最高。 此時那個概率的值時多少。 但找齊每一列后  current_state(1,39) 中每一個值的意思就變成了  這天得到預測值j的概率是多大。(我們把最大的那個,選擇相信他。)

        

tracking[i] = np.argmax(prob, axis=0)

列最大值所在行是哪一行。 這個就是位置了。 第j個位置的數字表明 昨天取預測值track【j】時, 今天得到預測值j的概率最大。 也就是統計一個路徑。 

    last_state = current_state

更新狀態。也就是把今天概率看成上一天的概率,看下一天。

pred_ls = [np.argmax(raw_output[-1])]

for i in range(0,alllength-1):
    back = tracking[alllength-i-1][int(pred_ls[-1])]
    pred_ls.append(int(back))

track 是一個 長451551 寬39的矩陣。 有45萬行 表示45萬天 然后有39 列 第j表示  昨天取預測值track【j】時, 今天得到預測值j的概率最大。

我們選擇最后一天的那個數字  帶入。 這個數字 就是最后一天的發射值。比如這個值是38(正常大家得到的值應該是25)  我們就要去找  38位的值  發現這一位上還是38  那么意思就是 昨天取38時 今天取38的概率最大。 所以預測值里就要加入38 但這里我們是倒序加入,最后要反過來。 

到這里 發現38位上寫的 7  說明上天是7 今天取38概率最大。然后找到上天的7 繼續這個步驟 。

 

 

也就是說 按照track這條路去走  是最有可能的 。 所以就取這條路提交。 得到最后的結果。hmm后會比正常提交提高兩個百分點左右。 

 

而hmm那個晴天陰天的例子 用第一天的轉移用 上面的 代碼大概就是 下面這樣 

import numpy as np
a = np.array([0.06,0.24])
b = np.array([[0.7,0.3],[0.4,0.6]])
c = np.array([0.4,0.3])
print(a.reshape(2,1)*b*c)

 



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM