机器学习(一)

一、机器学习基础

1、定义

机器具备有学习的能力。具体来讲就是让机器具备找一个函数的能力。

2、类别

(1)回归(Regression):函数的输出是一个数值,一个标量(scalar);

(2)分类(Classification):让机器做选择题,函数的输出是从设定好的选项里面选择一个当作输出;

(3)结构化学习(Structured Learning):机器产生一个有结构的物体,比如画一张画,写一篇文章。

3、机器学习的训练的三个步骤

(1)写出一个带有未知参数的function;

  • 模型(model):带有位置参数的函数;
  • 特征(feature):函数里面已知的;
  • 权重(weight);
  • 偏置(bias);

(2)定义损失函数lost;(L越大,代表现在这组参数越不好,L越小,代表这组参数越好)

估测值与标签(label)之间的差距计算方法:

  1. 平均绝对误差(MAE):
  2. 均方误差(MSE):
  3. 交叉熵(cross entropy);

误差表面(error surface):尝试了不同的参数,计算它的损失,画出来的等高线图;

(3)最优化(optimization),找到一组最好的(w, b)让损失最小。

常用方法:梯度下降(gredient descent)

  1. 选择一个初始点
  2. 计算**;**
  3. 根据该点的切线斜率判断下一步往哪里走,斜率为正,w向左移,相反右移;
  4. 移动:
    步伐大小取决于:
    · 斜率。斜率大步伐大,斜率小步伐小;
    · 学习率。(超参数:自己设定的)
  5. 重复操作直到失去耐心或

4、线性模型

(1)定义:把输入的特征乘上一个权重再加上一个偏置就得到预测的结果。

(2)模型偏差(model bias):来自于模型的限制。

(3)Hard Sigmoid函数: 当 x 轴的值小于某一个阈值的时候,大于另外一个阈值的时候,中间有一个斜坡。故其图象是先水平,再斜坡,再水平。

(4)分段线性曲线(piecewise linear curve):常数+一组Hard Sigmoid函数的和。

(5)Sigmoid函数:

可以用Sigmoid函数逼近Hard Sigmoid函数,然后把不同的Sigmoid函数叠起来就可以去逼近各种不同的分段线性函数,分段线性函数可以拿来近似各种不同的连续的函数。

如果的值趋近于无穷大的时候,这一项就会消失,当非常大的时候,这一条就会收敛在高度为c的地方。如果负的非常大的时候,分母的地方就会非常大,y的值就会趋近于 0。

(6)不只用一个特征,可以用多个特征代入不同的c,b,w,组合出各种不同的函数,从而得到更有灵活性(flexibility)的函数。

  • 写一个带未知数的function;

用矩阵跟向量相乘的方法表示:

线性代数常用的表示方式:

通过Sigmoid函数计算得到:

即这个比较灵活的函数为:

  • 定义损失;

W 的每一行或者每一列拼成一个长向量,把b ,b"拼"上去,形成一个长向量**** 。损失函数为L()。

  • 优化;

(7)批量(batch):本来有N笔数据,现在B笔数据一组,一组叫做批量。

(8)回合(epoch):把所有批量都看过一次,称为一个回合。

(9)模型变形:Hrad Sigmoid看作两个修正线性单元(ReLU)的加总。ReLU公式如下:

Sigmoid或ReLU被称为激活函数。想要使用ReLU,就把Sigmoid的地方换成

5、深度学习引入

(1)神经元===> 神经网络(neural network);

(2)隐藏层(hidden layer)===>深度学习(deep learning);

二、实践方法论

对训练结果不满意,分析路径:

(1)检查训练集上的loss,如果训练集的loss太大,则说明在训练资料上没有学好,进入第(2)步;如果训练集的loss太小,进入第(3)步,检查测试集上的loss。

(2)训练集上没有学好,有两种可能:通过比较不同模型来判断模型现在到底够不够大。

  • 模型偏差(model bias):模型太过简单,需要重新设计一个模型;
  • 优化做得不好:收敛在局部极限值与鞍点会导致优化失败。
  1. 临界点:梯度为零的点,包括局部极小值(local minimum)、鞍点(saddle point)、局部极大值(local maximum)。

  2. 判断临界值种类的方法:海森矩阵H ;(同时指出了参数可以更新的方向)Hessian 矩阵(海森矩阵)_hessian矩阵-CSDN博客

  3. 批量(batch)大小对梯度下降法的影响:在有并行计算的情况下,小的批量跟大的批量运算的时间并没有太大的差距。除非大的批量非常大,才会显示出差距。但是一个回合需要的时间,小的批量比较长,大的批量反而是比较快的,所以从一个回合需要的时间来看大的批量是较有优势的。而小的批量更新的方向比较有噪声的,大的批量更新的方向比较稳定。但是有噪声的更新方向反而在优化的时候有优势,而且在测试的时候也会有优势。所以大的批量跟小的批量各有优缺点,批量大小是需要去调整的超参数。

  4. 动量(Momentum)法是可以对抗鞍点或局部最小值的方法。可能走到更好的局部最小值。

  5. 自适应学习率:为每一个参数不同的学习率。如果在某一个方向上,梯度的值很小,非常平坦,希望学习率调大一点;如果在某一个方向上非常陡峭,坡度很大,希望学习率可以设得小一点。
    AdaGrad、RMSProp、Adam:最强总结!神经网络中常用的九种优化技术(二)AdaGrad、RMSProp、Adam、学习率衰减、Early Stopping(非常详细)大模型入门到精通!-CSDN博客

  6. 学习率调度:通过动态调整学习率来实现更好的训练效果;
    学习率衰减:随着参数的不断更新,让越来越小;
    预热(Warm Up):让学习率先变大后变小。

  7. 批量归一化(BN):

(3)如果测试集上得loss也小,那么训练是好的,如果测试集上的loss太大,有两种情况:

一是出现了过拟合(overfitting)现象,解决方法:

  1. 增加训练集,可以做数据增强(data augmentation);
  2. 给模型增加一些限制,比如给模型比较少的参数、用较少的特征、早停、正则化、丢弃法等。但是不能给太多限制,否者可能出现模型偏差问题。

交叉验证:合理选择模型的方法

二是训练资料与测试资料不匹配的问题。

三、练习

1、Regression

(1)Dataset:https://www.kaggle.com/competitions/ml2023spring-hw1

(2)解释

  • 随机种子:确保实验的可重复性,计算机的"随机"其实是数学公式生成的伪随机数。种子的作用是固定公式的起点
    为什么要设置"随机种子"_训练模型时需要设置随机种吗-CSDN博客
  • Dtaset:可以通过继承Pytorch提供的Dataset类来构造更适合的数据集类;
    def__init__(self, features, targets = None):读取数据并进行预处理;
    def__getitem__(self, idx):每次提取一笔数据;
    def__len__(self):返回数据的长度;
  • DataLoader:一个迭代器,可以多线程地读取数据,并可将几笔数据组成batch;
    参数列表(待加载数据集,batch大小,是否打乱数据,是否锁页);
    shuffle(是否打乱数据):Traning时设为True,Testing时设为False;
    pin_memory(是否锁页):设置为True时,将数据张量固定在GPU内存中,使数据加载至GPU速度更快;
  • 神经网络模型:通过集成nn.Model类来构造自己的神经网络模型;
    self.layers = nn.Sequential():一个序列容器,将每一layer按照被传入构造器的顺序添加到nn.Sequential()容器中;
    def forward(self, x):前向传播,可理解成神经网络处理输入的过程;
  • 训练过程:对每个batch数据:清零梯度(optimizer.zero_grad()),前向传播计算预测值,计算损失,反向传播(loss.backward()),更新参数(optimizer.step())。
  • 深度学习中有哪些超参数,都有什么作用_实验超参数配置的作用-CSDN博客

(3)代码:

python 复制代码
# 数据操作
import math
import numpy as np

# 读取、写入数据
import pandas as pd
import os
import csv

# 进度条
from tqdm import tqdm

# Pytorch
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader, random_split

# 绘制图像
from torch.utils.tensorboard import SummaryWriter

'''
    设置随机种子
    在机器学习和深度学习实验中,由于存在随机性(如权重初始化、数据打乱等),每次运行的结果可能会有所不同。
    通过设置固定的随机种子,可以使得每次运行的结果保持一致,便于调试和验证。
'''
def same_seed(seed):
    # 将 cuDNN(CUDA 深度神经网络库)设置为确定性模式,以确保在 GPU 上运行时结果可重复。
    torch.backends.cudnn.deterministic = True
    # 禁用 cuDNN 的自动调优功能,因为该功能会根据硬件选择最优的卷积算法,但这可能导致结果不一致。
    torch.backends.cudnn.benchmark = False

    np.random.seed(seed)
    torch.manual_seed(seed)
    if torch.cuda.is_available():
        torch.cuda.manual_seed(seed)


'''
    划分数据集:本实验从训练集中划分验证集
    data_set:要划分的数据集
    valid_ratio:划分比例
    seed:保证从训练集中随机的划分出验证集
'''
def train_valid_split(data_set, valid_ratio, seed):
    # 计算验证集的大小
    valid_data_size = int(len(data_set) * valid_ratio)
    # 计算训练集的大小
    train_data_size = len(data_set) - valid_data_size
    ''' 
        random_split 函数随机划分数据集:
            按照计算出的训练集和验证集大小进行划分
            使用指定的随机种子确保结果可重现
            generator=torch.Generator().manual_seed(seed) 确保划分过程的随机性是可控的 
    '''
    train_data, valid_data = random_split(data_set, [train_data_size, valid_data_size], generator=torch.Generator().manual_seed(seed))
    return np.array(train_data), np.array(valid_data)


''' 
    选择特征
    最后一列数据是label,x是feature,y是label
    默认选择了全部的特征来做训练,后续可选择合适的特征来优化模型提高预测率
'''
def select_feat(train_data, valid_data, test_data, select_all = True):
    # 分别提取训练数据和验证数据的最后一列作为目标值(即标签)
    y_train = train_data[:, -1]
    y_valid = valid_data[:, -1]

    # 分别提取训练数据、验证数据(除了最后一列)和测试数据的所有特征列
    raw_x_train = train_data[:, :-1]
    raw_x_valid = valid_data[:, :-1]
    raw_x_test = test_data

    if select_all:
        feat_idx = list(range(raw_x_train.shape[1]))
    else:
        feat_idx = [0, 1, 2, 3]

    return raw_x_train[:, feat_idx], raw_x_valid[:, feat_idx], raw_x_test[:, feat_idx], y_train, y_valid


# 数据集
class COVID19Dataset(Dataset):
    '''
        数据初始化方法
        festures:输入特征数据
        targets:目标值(可选,默认为None)
    '''
    def __init__(self, features, targets = None):
        if targets is None:
            self.targets = targets
        else:
            self.targets = torch.FloatTensor(targets)
        self.features = torch.FloatTensor(features)

    '''
        数据获取方法:根据索引返回数据项
        如果没有目标值(如测试集),只返回特征
        如果有目标值(如训练集、验证集),返回 (特征, 目标值) 对
    '''
    def __getitem__(self, index):
        if self.targets is None:
            return self.features[index]
        else:
            return self.features[index], self.targets[index]

    '''
        返回数据集中样本的总数
    '''
    def __len__(self):
        return len(self.features)


# 神经网络,继承自PyTorch的 nn.Module 基类
class My_Model(nn.Module):
    '''
        初始化方法
        input_dim:输入特征的维度
        super(My_Model, self).__init__() 调用父类初始化
        nn.Linear:线性变换层
        nn.ReLU() - ReLU激活函数
    '''
    def __init__(self, input_dim):
        super(My_Model, self).__init__()
        self.layers = nn.Sequential(
            nn.Linear(input_dim, 16),
            nn.ReLU(),
            nn.Linear(16, 8),
            nn.ReLU(),
            nn.Linear(8, 1),
        )

    '''
        通过 self.layers 顺序执行所有定义的层
        使用 x.squeeze(1) 移除张量的第二个维度(索引为1),防止输出出现不必要的维度
    '''
    def forward(self, x):
        x = self.layers(x)
        x = x.squeeze(1)
        return x


# 参数设置
device = 'cuda' if torch.cuda.is_available() else 'cpu'
config = {
    'seed': 5201314,
    'select_all': True,
    'valid_ratio': 0.2,
    'n_epochs': 3000,
    'batch_size': 256,
    'learning_rate': 1e-5,
    'early_stop': 400,
    'save_path': './models/model.ckpt',
}


# 训练过程
def trainer(train_loader, valid_loader, model, config, device):
    # 损失函数和优化器
    criterion = nn.MSELoss(reduction='mean')
    optimizer = torch.optim.SGD(model.parameters(), lr=config['learning_rate'], momentum=0.7)
    writer = SummaryWriter()

    if not os.path.isdir('./models'):
        os.mkdir('./models')

    n_epochs = config['n_epochs']
    best_loss = math.inf
    step = 0
    early_stop_count = 0

    for epoch in range(n_epochs):
        # 设置模型为训练模式
        model.train()
        loss_record = []

        # 使用tqdm进度条显示训练进度
        train_pbar = tqdm(train_loader, position=0, leave=True)

        '''
            对每个batch数据:
            清零梯度(optimizer.zero_grad())
            前向传播计算预测值
            计算损失
            反向传播(loss.backward())
            更新参数(optimizer.step())
        '''
        for x, y in train_pbar:
            optimizer.zero_grad()
            x, y = x.to(device), y.to(device)
            pred = model(x)
            loss = criterion(pred, y)
            loss.backward()
            optimizer.step()
            step += 1
            loss_record.append(loss.detach().item())

            # 显示训练过程
            train_pbar.set_description(f'Epoch[{epoch + 1}/{n_epochs}]')
            train_pbar.set_postfix({'loss': loss.detach().item()})

        mean_train_loss = sum(loss_record) / len(loss_record)
        writer.add_scalar('loss/train', mean_train_loss, step)

        # 设置模型为评估模式
        model.eval()
        loss_record = []
        # 在验证集上计算平均损失,使用torch.no_grad()避免计算梯度以节省内存
        for x, y in valid_loader:
            x, y = x.to(device), y.to(device)
            with torch.no_grad():
                pred = model(x)
                loss = criterion(pred, y)

            loss_record.append(loss.item())

        mean_valid_loss = sum(loss_record) / len(loss_record)
        print(f'Epoch[{epoch + 1}/{n_epochs}]: Train loss : {mean_train_loss: .4f}, Valid loss : {mean_valid_loss: .4f}')
        writer.add_scalar('loss/valid', mean_valid_loss, step)

        # 如果当前验证损失低于历史最佳损失,保存模型
        if mean_valid_loss < best_loss:
            best_loss = mean_valid_loss
            torch.save(model.state_dict(), config['save_path'])
            print('Saving model with loss {: .3f}...'.format(best_loss))
            early_stop_count = 0
        else:
            early_stop_count += 1

        # 连续early_stop轮验证损失未改善则提前停止训练
        if early_stop_count >= config['early_stop']:
            print('\nEarly stopping')
            return



# 设置随机种子
same_seed(config['seed'])
# 读取数据
train_data = pd.read_csv('./covid_train.csv').values
test_data = pd.read_csv('./covid_test.csv').values
# 划分数据集
train_data, valid_data = train_valid_split(train_data, config['valid_ratio'], config['seed'])
print(f"""train_data size: {train_data.shape}, valid_data size: {valid_data.shape}, test_data size: {test_data.shape})""")
# 选择特征
x_train, x_valid, x_test, y_train, y_valid = select_feat(train_data, valid_data, test_data, config['select_all'])
print(f'The number of features: {x_train.shape[1]}')
# 构造数据集
train_dataset = COVID19Dataset(x_train, y_train)
valid_dataset = COVID19Dataset(x_valid, y_valid)
test_dataset = COVID19Dataset(test_data)
# 准备Dataloader
train_loader = DataLoader(train_dataset, batch_size=config['batch_size'], shuffle=True, pin_memory=True)
valid_loader = DataLoader(valid_dataset, batch_size=config['batch_size'], shuffle=True, pin_memory=True)
test_loader = DataLoader(test_dataset, batch_size=config['batch_size'], shuffle=False, pin_memory=True)

# 开始训练
model = My_Model(input_dim=x_train.shape[1]).to(device)
trainer(train_loader, valid_loader, model, config, device)

# 预测
def predict(test_loader, model, device):
    model.eval()
    preds = []
    for x in tqdm(test_loader):
        x = x.to(device)
        with torch.no_grad():
            pred = model(x)
            preds.append(pred.detach().cpu())
    preds = torch.cat(preds, dim=0).numpy()
    return preds


def save_pred(preds, file):
    with open(file, 'w') as fp:
        writer = csv.writer(fp)
        writer.writerow(['id', 'tested_positive'])
        for i, p in enumerate(preds):
            writer.writerow([i, p])


# 预测并保存结果
model = My_Model(input_dim=x_train.shape[1]).to(device)
model.load_state_dict(torch.load(config['save_path']))
preds = predict(test_loader, model, device)
save_pred(preds, 'pred.csv')

2、Classification

(1)Dataset:https://www.kaggle.com/competitions/ml2023spring-hw2

(2)解释

  • 模块化思维:
    BasicBlock:一层Linear层 + ReLu激活函数 = 一个BasicBlock;可以视作封装了一层hidden_layer;
    Classfier:由许多个BasicBlock组成,构成最终要用的神经网络模型。

  • 损失函数nn.CrossEntropyLoss()通常用于多类别分类任务,特别是当类别之间不平衡或样本数目不均衡时;
    交叉熵(Cross-Entropy):一种用于比较两个概率分布之间的度量,对比了模型的预测结果和数据的真实标签,随着预测越来越准确,交叉熵的值越来越小,如果预测完全正确,交叉熵的值就为0。因此,训练分类模型时,可以使用交叉熵作为损失函数。

    一文搞懂熵(Entropy),交叉熵(Cross-Entropy) - 知乎

  • 优化器AdamW(),集成一阶、二阶动量和weight decay正则化的优化器,区别于随机梯度下降法,它能够在训练过程中自适应的调整学习率;

(3)代码:

python 复制代码
import csv
import os

import numpy as np
import torch
import random
from torch.utils.data import Dataset, DataLoader
import torch.nn as nn
from tqdm import tqdm
import gc # 回收内存,节约资源


# 设置随机种子
def same_seeds(seed):
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    if torch.cuda.is_available():
        torch.cuda.manual_seed(seed)
        torch.cuda.manual_seed_all(seed)
    torch.backends.cudnn.benchmark = False
    torch.backends.cudnn.deterministic = True


# 读取pt文件(加载特征)
def load_feat(path):
    return torch.load(path)


'''
    拼接frame
    假设我们有以下输入:输入张量 x 形状为 (4, 3),表示4个帧,每帧3个特征,concat_n = 3
    初始输入 x (4个帧,每帧3个特征)
        x = torch.tensor([
            [1, 2, 3],  # 帧0
            [4, 5, 6],  # 帧1
            [7, 8, 9],  # 帧2
            [10, 11, 12] # 帧3
        ])
    1、x.repeat(1, concat_n)
    x = [
        [1, 2, 3, 1, 2, 3, 1, 2, 3],    # 帧0重复
        [4, 5, 6, 4, 5, 6, 4, 5, 6],    # 帧1重复
        [7, 8, 9, 7, 8, 9, 7, 8, 9],    # 帧2重复
        [10, 11, 12, 10, 11, 12, 10, 11, 12] # 帧3重复
    ]
    2、x.view(seq_len, concat_n, feature_dim),重新排列为 (4, 3, 3) 的形状:
    x = [
        # 帧0: [[1,2,3], [1,2,3], [1,2,3]]
        # 帧1: [[4,5,6], [4,5,6], [4,5,6]]
        # 帧2: [[7,8,9], [7,8,9], [7,8,9]]
        # 帧3: [[10,11,12], [10,11,12], [10,11,12]]
    ]
    3、x.permute(1, 0, 2),转置为 (3, 4, 3) 的形状,第一维表示拼接组:
    x = [
        # 组0: [[1,2,3], [4,5,6], [7,8,9], [10,11,12]] - 原始帧
        # 组1: [[1,2,3], [4,5,6], [7,8,9], [10,11,12]] - 重复帧
        # 组2: [[1,2,3], [4,5,6], [7,8,9], [10,11,12]] - 重复帧
    ]
    4、应用 shift 函数
        mid = concat_n // 2 = 1 (中间组索引)
        对于 r_idx = 1:
            x[mid+1, :] = shift(x[mid+1, :], 1) (向右移动1位)
            x[mid-1, :] = shift(x[mid-1, :], -1) (向左移动1位)
        shift(x[mid+1, :], 1) 的执行过程:
            x[2, :] = [[1,2,3], [4,5,6], [7,8,9], [10,11,12]]
            n = 1 > 0,所以:
            left = x[1:] = [[4,5,6], [7,8,9], [10,11,12]]
            right = x[-1].repeat(1, 1) = [[10,11,12]]
            结果:[[4,5,6], [7,8,9], [10,11,12], [10,11,12]]
        shift(x[mid-1, :], -1) 的执行过程:
            x[0, :] = [[1,2,3], [4,5,6], [7,8,9], [10,11,12]]
            n = -1 < 0,所以:
            left = x[0].repeat(1, 1) = [[1,2,3]]
            right = x[:-1] = [[1,2,3], [4,5,6], [7,8,9]]
            结果:[[1,2,3], [1,2,3], [4,5,6], [7,8,9]]
        最终x = [
            # 组0: [[1,2,3], [1,2,3], [4,5,6], [7,8,9]] - 向左移动后的帧
            # 组1: [[1,2,3], [4,5,6], [7,8,9], [10,11,12]] - 原始帧
            # 组2: [[4,5,6], [7,8,9], [10,11,12], [10,11,12]] - 向右移动后的帧
        ]
    5、x.permute(1, 0, 2).view(seq_len, concat_n * feature_dim),重新排列并展平特征维度,得到最终结果:
    [
        [1,2,3, 1,2,3, 4,5,6],      # 帧0: [前1帧, 当前帧, 后1帧]
        [1,2,3, 4,5,6, 7,8,9],      # 帧1: [前1帧, 当前帧, 后1帧]
        [4,5,6, 7,8,9, 10,11,12],   # 帧2: [前1帧, 当前帧, 后1帧]
        [7,8,9, 10,11,12, 10,11,12] # 帧3: [前1帧, 当前帧, 后1帧]
    ]
'''

# shift 函数用于对输入张量 x 进行移位操作
def shift(x, n):
    if n < 0:
        left = x[0].repeat(-n, 1)
        right = x[:n]
    elif n > 0:
        left = x[n:]
        right = x[-1].repeat(n, 1)
    else:
        return x
    return torch.cat((left, right), dim=0)



'''
    concat_feat 函数用于将特征帧进行拼接以增加上下文信息:
    这个函数的主要目的是为每个时间帧添加前后帧的上下文信息, 以帮助模型更好地理解语音信号的时序特征。
'''
def concat_feat(x, concat_n):
    assert concat_n % 2 == 1
    if concat_n < 2:
        return x

    seq_len = x.size(0)     # frame的个数
    feature_dim = x.size(1)     # 每一个frame的维度
    x = x.repeat(1, concat_n)
    # 重新排列张量形状为 (concat_n, seq_len, feature_dim),即[组号, 帧序号, 帧内号]
    x = x.view(seq_len, concat_n, feature_dim).permute(1, 0, 2)

    mid = concat_n // 2

    # 对于中间帧周围的每一帧: 使用 shift 函数对称地移动帧以创建上下文
    for r_idx in range(1, mid+1):
        x[mid+r_idx, :] = shift(x[mid+r_idx], r_idx)
        x[mid-r_idx, :] = shift(x[mid-r_idx], -r_idx)
    # 最后将张量重新排列并展平特征维度, 得到拼接后的特征
    return x.permute(1, 0, 2).view(seq_len, concat_n * feature_dim)


# 预处理数据
def preprocess_data(split, feat_dir, phone_path, concat_nframes, train_ratio=0.8):
    class_num = 41  # 41个分类
    mode = 'train' if (split == 'train' or split == 'val') else 'test'

    # 将每个文件名及其对应的标签序列存储在 label_dick 字典中
    label_dick = {}
    if mode != 'test':
        phone_file = open(os.path.join(phone_path, f'{mode}_labels.txt')).readlines()
        for line in phone_file:
            line  = line.strip('\n').split(' ')
            # temp = []
            # for p in line[1:]:
            #     temp.append(int(p))
            label_dick[line[0]] = [int(p) for p in line[1:]]

        # 划分数据集
        if split == 'train' or split == 'val':
            usage_list = open(os.path.join(phone_path, 'train_split.txt')).readlines()
            random.shuffle(usage_list)
            percent = int(len(usage_list) * train_ratio)
            if split == 'train':
                usage_list = usage_list[:percent]
            elif split == 'val':
                usage_list = usage_list[percent:]
    elif split == 'test':
            usage_list = open(os.path.join(phone_path, 'test_split.txt')).readlines()
    else:
        raise ValueError('Invalid \'split\' argument for dataset: PhoneDataset!')

    usage_list = [line.strip('\n') for line in usage_list]
    print('[Dataset] - # phone classes: {}, # samples: {:d}'.format(class_num, len(usage_list)))

    # 创建预分配的张量 x 和 y 用于存储特征和标签
    max_len = 10000000
    x = torch.empty(max_len, 39 * concat_nframes)
    if mode != 'test':
        y = torch.empty(max_len, dtype=torch.long)
    '''
        遍历文件列表,将特征和标签存储到预分配的张量中
        使用 torch.empty 预分配内存以提高效率
        通过 concat_feat 函数增加时序上下文信息
        使用 tqdm 显示处理进度
        最终通过切片操作 x[:idx, :] 和 y[:idx] 去除预分配的多余空间
    '''
    idx = 0
    for i, fname in tqdm(enumerate(usage_list)):
        feat = load_feat(os.path.join(feat_dir, mode, f'{fname}.pt'))
        cur_len = len(feat)
        feat = concat_feat(feat, concat_nframes)
        if mode != 'test':
            label = torch.LongTensor(label_dick[fname])
        x[idx: idx + cur_len] = feat
        if mode != 'test':
            y[idx: idx + cur_len] = label

        idx += cur_len

    x = x[:idx, :]
    if mode != 'test':
        y = y[:idx]
    print(f'[INFO]{split} set')
    print(x.shape)
    if mode != 'test':
        print(y.shape)
        return x, y
    else:
        return x


# Dataset
class LibriDataset(Dataset):
    def __init__(self, x, y=None):
        self.feature = x
        if y is not None:
            self.label = torch.LongTensor(y)
        else:
            self.label = None

    def __getitem__(self, item):
        if self.label is not None:
            return self.feature[item], self.label[item]
        else:
            return self.feature[item]

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


# Model
class BasicBlock(nn.Module):
    def __init__(self, input_dim, output_dim):
        super(BasicBlock, self).__init__()
        self.block = nn.Sequential(
            nn.Linear(input_dim, output_dim),
            nn.ReLU(),
        )

    def forward(self, x):
        x = self.block(x)
        return x


class Classifier(nn.Module):
    def __init__(self, input_dim, output_dim=41, hidden_layers=1, hidden_dim=256):
        super(Classifier, self).__init__()
        self.fc = nn.Sequential(
            BasicBlock(input_dim, hidden_dim),      # 输入层
            *[BasicBlock(hidden_dim, hidden_dim) for _ in range(hidden_layers)],    #hidden_layers 个 BasicBlock 作为隐藏层
            nn.Linear(hidden_dim, output_dim)   # 一个线性层将隐藏层映射到输出维度
        )

    def forward(self, x):
        x = self.fc(x)
        return x

# 配置
device = 'cuda' if torch.cuda.is_available() else 'cpu'
concat_nframes = 3
train_ratio = 0.8
config = {
    'seed': 0,
    'batch_size': 512,
    'n_epoch': 5,
    'learning_rate': 1e-4,
    'model_path': './model.ckpt'
}
input_dim = 39 * concat_nframes
hidden_layers = 3
hidden_dim = 256

# 训练
def trainer(train_set, valid_set, train_loader, val_loader, config, model, device):
    # 损失函数和优化器
    criterion = nn.CrossEntropyLoss()
    optimizer = torch.optim.AdamW(model.parameters(), lr=config['learning_rate'])

    best_acc = 0.0
    n_epochs = config['n_epoch']

    for epoch in range(n_epochs):
        train_acc = 0.0
        train_loss = 0.0
        model.train()
        for i, batch in enumerate(tqdm(train_loader)):
            features, labels = batch
            features = features.to(device)
            labels = labels.to(device)

            optimizer.zero_grad()
            outputs = model(features)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()

            _, train_pred = torch.max(outputs, dim = 1)
            train_acc += (train_pred.detach() == labels.detach()).sum().item()
            train_loss += loss.item()

            #
            if (len(valid_set)) > 0:
                val_acc = 0.0
                val_loss = 0.0
                model.eval()
                with torch.no_grad():
                    for i, batch in enumerate(tqdm(val_loader)):
                        features, labels = batch
                        features = features.to(device)
                        labels = labels.to(device)
                        outputs = model(features)
                        loss = criterion(outputs, labels)

                        _, val_pred = torch.max(outputs, dim=1)
                        val_acc += (val_pred.cpu() == labels.cpu()).sum().item()
                        val_loss += loss.item()

                    print(
                        '[Epoch {:03d}/{:03d}] train_loss: {:.4f}, train_acc: {:.3f}, val_loss: {:.4f}, val_acc: {:.3f}'.format(
                            epoch + 1, n_epochs,
                            train_loss / len(train_loader), train_acc / len(train_set),
                            val_loss / len(val_loader), val_acc / len(valid_set)))

                    # 保存最优模型
                    if val_acc > best_acc:
                        best_acc = val_acc
                        torch.save(model.state_dict(), config['model_path'])
                        print('saving model with acc {:.3f}'.format(best_acc))
            else:
                print('[Epoch {:03d}/{:03d}] train_loss: {:.4f}, train_acc: {:.3f}'.format(
                    epoch + 1, n_epochs,
                    train_loss / len(train_loader), train_acc / len(train_set)))

    if len(valid_set) == 0:
        torch.save(model.state_dict(), config['model_path'])
        print('saving model with acc {:.3f}'.format(best_acc))

    del train_loader, val_loader
    gc.collect()
    return

# same_seeds(config['seed'])
# train_x, train_y = preprocess_data(split='train', feat_dir='./dataset/feature', phone_path='./dataset', concat_nframes=concat_nframes, train_ratio=train_ratio)
# val_x, val_y = preprocess_data(split='val', feat_dir='./dataset/feature', phone_path='./dataset', concat_nframes=concat_nframes, train_ratio=train_ratio)
#
# train_set = LibriDataset(train_x, train_y)
# val_set = LibriDataset(val_x, val_y)
#
# del train_x, train_y, val_x, val_y
# gc.collect()
#
# train_loader = DataLoader(train_set, batch_size=config['batch_size'], shuffle=True, pin_memory=True)
# val_loader = DataLoader(val_set, batch_size=config['batch_size'], shuffle=True, pin_memory=True)
#
# print('==> Building model and loading data ...')
# model = Classifier(input_dim=input_dim, hidden_layers=hidden_layers, hidden_dim=hidden_dim).to(device)
# trainer(train_set, val_set, train_loader, val_loader, config, model, device)


# Predict
def predict(model, test_loader):
    # test_acc = 0.0
    # test_length = 0
    pred = np.array( [], dtype=np.int32)

    model.eval()
    with torch.no_grad():
        for i, batch in enumerate(tqdm(test_loader)):
            features = batch
            features = features.to(device)

            outputs = model(features)
            _, test_pred = torch.max(outputs, dim=1)
            pred = np.concatenate( (pred, test_pred.cpu().numpy()), axis=0 )

        return  pred

# def save_pred(preds, file):
#     with open(file, 'w') as fp:
#         fp.write('Id Class\n')
#         for i, p in enumerate(preds):
#             fp.write('{} {}\n'.format(i, p))

def save_pred(preds, file):
    with open(file, 'w') as fp:
        writer = csv.writer(fp)
        writer.writerow(['Id', 'Class'])
        for i, p in enumerate(preds):
            writer.writerow([i, p])


test_x = preprocess_data(split='test', feat_dir='./dataset/feature', phone_path='./dataset', concat_nframes=concat_nframes)
test_set = LibriDataset(test_x, None)
test_loader = DataLoader(test_set, batch_size=config['batch_size'], shuffle=False, drop_last=False, pin_memory=True)
model = Classifier(input_dim=input_dim, hidden_layers=hidden_layers, hidden_dim=hidden_dim).to(device)
model.load_state_dict(torch.load(config['model_path']))

preds = predict(model, test_loader)
save_pred(preds, 'prediction.csv')
相关推荐
Bdygsl1 小时前
数字图像处理总结 Day 1
人工智能·算法·计算机视觉
张彦峰ZYF1 小时前
Coze文章仿写:智能体 + 工作流实现内容自动生成与插图输出
人工智能·ai·coze dify
Jerry.张蒙1 小时前
SAP传输请求流程:从开发到生产的安全流转
大数据·网络·人工智能·学习·职场和发展·区块链·运维开发
Lethehong1 小时前
openGauss在教育领域的AI实践:基于Java JDBC的学生成绩预测系统
java·开发语言·人工智能·sql·rag
山科智能信息处理实验室1 小时前
PCDreamer:基于多视角扩散先验的点云补全
人工智能
LDG_AGI1 小时前
【推荐系统】深度学习训练框架(六):PyTorch DDP(DistributedDataParallel)数据并行分布式深度学习原理
人工智能·pytorch·分布式·python·深度学习·算法·spark
极智视界1 小时前
目标检测数据集 - 自动驾驶场景驾驶员注意力不集中检测数据集下载
人工智能·目标检测·自动驾驶
亚马逊云开发者1 小时前
Serverless is all you need: 在亚马逊云科技上一键部署大模型API聚合管理平台OneHub
人工智能
人工智能训练1 小时前
Docker中Dify镜像由Windows系统迁移到Linux系统的方法
linux·运维·服务器·人工智能·windows·docker·dify