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)

 


posted @ 2022-03-19 10:58  yi术家  阅读(1029)  评论(0编辑  收藏  举报