2022李宏毅作業hw1—新冠陽性人員數量預測。


 事前  :

  kaggle地址:ML2021Spring-hw1 | Kaggle

   我的git地址: https://github.com/xiaolilaoli/lihongyi2022homework/tree/main/hw1_covidpred

        當然作為新手,我也是參考的其他大神的。參考的過多,我就不一一放地址了,在這里謝過各位大佬。如果和我一樣的新手,調試代碼看張量流動絕對是一個好用的方法。

        作業介紹: 說的是啊 這個美國,好像是有40個州, 這四十個州呢 ,統計了連續三天的新冠陽性人數,和每天的一些社會特征,比如帶口罩情況, 居家辦公情況等等。現在有一群人比較壞,把第三天的數據遮住了,我們就要用前兩天的情況以及第三天的特征,來預測第三天的陽性人數。但幸好的是,我們還是有一些數據可以作為參考的,就是我們的訓練集。

 

  一: 數據讀取。

(第一步引用的包:)

 

import numpy as np
from torch.utils.data import Dataset,DataLoader
import csv
import torch
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2

        先從kaggle上把數據下下來看看。點那個data就找到下載的地方了。下載好數據第一步先觀察train的數據是什么樣子的。如下圖,可以看到有很多列,每一列都是一類特征,每一行都是一個樣本。黃紅藍是第1,2,3天的測試陽性數據。藍色的就是我們要預測的值啦。

        我們再細看數據: 可以看到第一行是沒有用的,他只是標簽的名稱。然后第一列也是沒有用處的,因為他只是標注樣本是第幾個樣本。等會處理數據時都要處理掉。 然后我們可以注意到前40列的數據和后面五十多列是不一樣的,一般是一列全1 其他列全0 ,表示的是1所在的那個州,地點標識而已。

 

   看清楚數據的結構,下面我們開始讀入數據。csv數據和其他數據的讀法差不多。比如你可以選擇下面的文件式讀法。

 

with open(r'covid.train.csv', 'r') as f:
    train_data = f.readlines()

    train_data = [line.split('\n') for line in train_data][1:]  #分行之后不要第一行
    train_data = [each[0].split(',') for each in train_data]    #對於每一行 去掉后面的空格
    print(len(train_data[0]))
    train_data = np.array(train_data)         #轉換成numpy的矩陣

    train_x = train_data[:,1:-1]     # x是數據,y是標簽 。第一個冒號表示所有行,第二個冒號表示
    train_y = train_data[:,-1]      #列。所以x就是第2列到倒數第二列。y就是倒數第一列。

也可以選擇csv的專門讀取excel表格的函數

        

with open(path,'r') as f:
    csv_data = list(csv.reader(f))
    column = csv_data[0]         #0行是標題
    csv_data = np.array(csv_data[1:])[:,1:].astype(float)   #連環操作 先取行 轉numpy 
#再取列 轉float

   然后這里要介紹一個取最相關列的操作。 上面的數據我們知道有95列,可是,這90多列,每一列都與結果是相關的嗎? 恐怕不一定,肯定有些特征卵用沒有。所以我們這里可以找到那些相關的列,用這些特征來預測結果。找特征有很多方法,大家可以百度特征選擇,有很多介紹。這里用的是SelectKBest 函數。順便定義了一個挑特征的函數。column是第一行的特征名稱,我傳入是為了打印看看是哪些特征重要,要不然他挑了半天我也不知道啊 。k是挑多少個特征。

        


def get_feature_importance(feature_data, label_data, k =4,column = None):
    """
    此處省略 feature_data, label_data 的生成代碼。
    如果是 CSV 文件,可通過 read_csv() 函數獲得特征和標簽。
    """
    model = SelectKBest(chi2, k=k)#選擇k個最佳特征
    X_new = model.fit_transform(feature_data, label_data)
    #feature_data是特征數據,label_data是標簽數據,該函數可以選擇出k個特征
    print('x_new', X_new)
    scores = model.scores_
    # 按重要性排序,選出最重要的 k 個
    indices = np.argsort(scores)[::-1] #找到重要K個的下標
    if column:
        k_best_features = [column[i+1] for i in indices[0:k].tolist()]
        print('k best features are: ',k_best_features)
    return X_new, indices[0:k]

找好特征后。我們還需要進行訓練集和驗證集的划分。 我們知道,kaggle下下來只有訓練集和測試集,所以我們需要從訓練集里分出來一個驗證集來作為模型評價。 方法可以是直接截一段,也可以是逢幾個挑一個,也可以是隨機的。我這里是逢5挑1 

            if mode == 'train':
                indices = [i for i in range(len(csv_data)) if i % 5 != 0]
                self.y = torch.LongTensor(csv_data[indices,-1])
            elif mode == 'val':
                indices = [i for i in range(len(csv_data)) if i % 5 == 0]
                # data = torch.tensor(csv_data[indices,col_indices])
                self.y = torch.LongTensor(csv_data[indices,-1])
            else:
                indices = [i for i in range(len(csv_data))]
                #這是測試數據 不需要標簽 也沒有標簽

     取完數據后,一般還要有一個歸一化的步驟,防止各個特征的數量級相差過於大。這里用的是Z-score標准化方法。減均值除以標准差

self.data = (self.data - self.data.mean(dim=0,keepdim=True))
 /self.data.std(dim=0,keepdim=True)     #這里將數據歸一化。

   綜上所述,我們可以寫出我們的dataset函數了。基本上大部分神經網絡都是需要讀數據這部分的,過程就是把數據從本地文件,讀入dataset中去。dataset中一般有三個函數,第一個是初始化__init__:  一般負責把數據從文件取出來。第二個獲取數據__getitem__, 負責讀第幾個數據。第三個獲取長度__len__: 負責返回數據集的長度。

        一個完整的從csv到可以用的dataset的 代碼如下圖所示。 這一部分被我放在model——utils的data模塊里。

完整代碼:

import numpy as np
from torch.utils.data import Dataset,DataLoader
import csv
import torch
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2

def get_feature_importance(feature_data, label_data, k =4,column = None):
    """
    此處省略 feature_data, label_data 的生成代碼。
    如果是 CSV 文件,可通過 read_csv() 函數獲得特征和標簽。
    """
    model = SelectKBest(chi2, k=k)#選擇k個最佳特征
    X_new = model.fit_transform(feature_data, label_data)
    #feature_data是特征數據,label_data是標簽數據,該函數可以選擇出k個特征
    print('x_new', X_new)
    scores = model.scores_
    # 按重要性排序,選出最重要的 k 個
    indices = np.argsort(scores)[::-1] #找到重要K個的下標
    if column:
        k_best_features = [column[i+1] for i in indices[0:k].tolist()]
        print('k best features are: ',k_best_features)
    return X_new, indices[0:k]


class covidDataset(Dataset):
    def __init__(self, path, mode, feature_dim):
        with open(path,'r') as f:
            csv_data = list(csv.reader(f))
            column = csv_data[0]
            train_x = np.array(csv_data)[1:][:,1:-1]
            train_y = np.array(csv_data)[1:][:,-1]
            _,col_indices = get_feature_importance(train_x,train_y,feature_dim,column)
            col_indices = col_indices.tolist()   #得到重要列的下標
            csv_data = np.array(csv_data[1:])[:,1:].astype(float)
            if mode == 'train':       #如果讀的是訓練數據 就逢5取4  indices是數據下標
                indices = [i for i in range(len(csv_data)) if i % 5 != 0]
                self.y = torch.LongTensor(csv_data[indices,-1])
            elif mode == 'val':  #如果讀的是驗證數據 就逢5取1  indices是數據下標
                indices = [i for i in range(len(csv_data)) if i % 5 == 0]
                # data = torch.tensor(csv_data[indices,col_indices])
                self.y = torch.LongTensor(csv_data[indices,-1])
            else:      #如果讀的是測試數據 就全取了
                indices = [i for i in range(len(csv_data))]
            data = torch.tensor(csv_data[indices,:]) #取行
            self.data = data[:,col_indices]   #取列
            self.mode = mode
            self.data = (self.data - self.data.mean(dim=0,keepdim=True)) /self.data.std(dim=0,keepdim=True)     #這里將數據歸一化。
            assert feature_dim == self.data.shape[1]


            print('Finished reading the {} set of COVID19 Dataset ({} samples found, each dim = {})'
                  .format(mode, len(self.data), feature_dim))

    def __getitem__(self, item):
        if self.mode == 'test':
            return self.data[item].float()
        else :
            return self.data[item].float(), self.y[item]
    def __len__(self):
        return len(self.data)

 

二 模型設計。

        數據都讀完了,接下來肯定是模型了 。當然這里是一個簡單的回歸模型我用兩個全連接實現的,中間加了一個relu。inDim是傳入的參數 ,就是上面我們挑選的重要特征的數量啦。這部分比較簡單,一般模型都是包括這兩個部分 __init__和forward  也就是初始化和前向傳播。初始化中會定義前向傳播里需要的模型模塊。前向傳播里就是輸入到輸出的流動了 。x是輸入的張量,最后輸出模型計算結果。 模型也非常簡單。

 

    注意網絡一般都是按batch大小計算的。我舉個例子。 假如我挑了4個特征,那么模型輸入長度就是4,輸出長度就是1(回歸值) 。假如我16個數據1批次, 輸入大小就是(16,4) 輸出就是(16,1) 這都是自動的 不用我們擔心。這一部分被我放在model_utils的model模塊里。

完整代碼:

import torch.nn as nn

class myNet(nn.Module):
    def __init__(self,inDim):
        super(myNet,self).__init__()
        self.fc1 = nn.Linear(inDim, 64)
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(64,1)

    def forward(self, x):
        x = self.fc1(x)
        x = self.relu(x)
        x = self.fc2(x)
        if len(x.size()) > 1:
            return x.squeeze(1)     #如果批量大小不為1 這里才需要展平。 
        else:
            return x

 

三   訓練步驟。

        訓練函數推薦大家自己定義一個的,這樣以后面對大部分問題都可以通用。 

這個是訓練的過程 都是很常規的步驟。

   for i in range(epoch):
        start_time = time.time()
        model.train()   #開啟訓練
        train_loss = 0.0
        val_loss = 0.0
        for data in trainloader:
            optimizer.zero_grad()
            x , target = data[0].to(device), data[1].to(torch.float32).to(device)  
            #從loader里取一批數據
            pred = model(x)  #經過模型預測
            bat_loss = loss(pred, target, model)  #計算loss
            bat_loss.backward()     #梯度回傳
            optimizer.step()      #計算
            train_loss += bat_loss.detach().cpu().item()    #記錄loss值 注意要從gpu上取下來
#再從張量里取出來

        plt_train_loss . append(train_loss/trainset.__len__())  #記錄

每一個epoch里還有驗證步驟。參照訓練可以看到每一步的作用。多了一個保存模型的步驟。保存loss最低時的那個模型。

 

        model.eval()
        with torch.no_grad():
            for data in valloader:
                val_x , val_target = data[0].to(device), data[1].to(device)
                val_pred = model(val_x)
                val_bat_loss = loss(val_pred, val_target, model)
                val_loss += val_bat_loss
                val_rel.append(val_pred)
        if val_loss < min_val_loss:
            min_val_loss = val_loss
            torch.save(model, save_)

        plt_val_loss . append(val_loss/valloader.__len__())

 

還有繪圖: 畫出loss的變化情況。

    plt.plot(plt_train_loss)
    plt.plot(plt_val_loss)
    plt.title('loss')
    plt.legend(['train', 'val'])
    plt.show()

完整的訓練代碼:這一部分被我放在model_utils的train模塊里。


import torch
import time
import matplotlib.pyplot as plt

def train_val(model, trainloader, valloader,optimizer, loss, epoch, device, save_):

    # trainloader = DataLoader(trainset,batch_size=batch,shuffle=True)
    # valloader = DataLoader(valset,batch_size=batch,shuffle=True)
    model = model.to(device)
    plt_train_loss = []
    plt_val_loss = []
    val_rel = []
    min_val_loss = 100000

    for i in range(epoch):
        start_time = time.time()
        model.train()
        train_loss = 0.0
        val_loss = 0.0
        for data in trainloader:
            optimizer.zero_grad()
            x , target = data[0].to(device), data[1].to(torch.float32).to(device)
            pred = model(x)
            bat_loss = loss(pred, target, model)
            bat_loss.backward()
            optimizer.step()
            train_loss += bat_loss.detach().cpu().item()

        plt_train_loss . append(train_loss/trainloader.__len__())

        model.eval()
        with torch.no_grad():     #驗證時 不計算梯度
            for data in valloader:
                val_x , val_target = data[0].to(device), data[1].to(device)
                val_pred = model(val_x)
                val_bat_loss = loss(val_pred, val_target, model)
                val_loss += val_bat_loss
                val_rel.append(val_pred)
        if val_loss < min_val_loss:
            torch.save(model, save_)
            min_val_loss = val_loss
        plt_val_loss . append(val_loss/valloader.__len__())

        print('[%03d/%03d] %2.2f sec(s) TrainLoss : %3.6f | valLoss: %3.6f' % \
              (i, epoch, time.time()-start_time, plt_train_loss[-1], plt_val_loss[-1])
              )

    plt.plot(plt_train_loss)
    plt.plot(plt_val_loss)
    plt.title('loss')
    plt.legend(['train', 'val'])
    plt.show()

四:測試和保存步驟 。

        測試和驗證時很相似的。 少的是預測值和真值的比較,因為沒有真值 ,多的是預測值得保存。按照kaggle要求保存在csv里 。這一部分被我放在model_utils的evaluate模塊里。

   完整代碼:

import numpy as np
import torch
from torch.utils.data import  DataLoader

import csv

def evaluate(model_path, testset, rel_path ,device):
    model = torch.load(model_path).to(device)
    testloader = DataLoader(testset,batch_size=1,shuffle=False)  #放入loader 其實可能沒必要 loader作用就是把數據形成批次而已
    val_rel = []
    model.eval()
    with torch.no_grad():
        for data in testloader:
            x  = data.to(device)
            pred = model(x)
            val_rel.append(pred.item())
    print(val_rel)
    with open(rel_path, 'w') as f:
        csv_writer = csv.writer(f)        #百度的csv寫法
        csv_writer.writerow(['id','tested_positive'])
        for i in range(len(testset)):
            csv_writer.writerow([str(i),str(val_rel[i])])

 

五 : 主函數。

        萬事俱備,只欠東風。就像人的四肢腦袋都齊了,就差個body把他們連起來了,起這個作用的 就是main函數。

        調包第一步 除了系統包 還有自己寫的 

        

from model_utils.model import myNet
from model_utils.data import covidDataset
from model_utils.train import train_val
from model_utils.evaluate import evaluate
from torch import optim
import torch.nn as nn
import torch
from torch.utils.data import Dataset,DataLoader

路徑和設備 以及一些超參。 在這里 我嘗試將一些超參放入字典中。

device = 'cuda' if torch.cuda.is_available() else 'cpu'  #設備一般gpu 沒有就cpu 
train_path = 'covid.train.csv'     #訓練數據路徑
test_path = 'covid.test.csv'        #測試數據路徑


feature_dim = 6       #重要的特征數
trainset = covidDataset(train_path,'train',feature_dim=feature_dim)  
valset = covidDataset(train_path,'val',feature_dim=feature_dim)
testset = covidDataset(test_path,'test',feature_dim=feature_dim)
      #對照數據部分 讀取了三個數據set


config = {
    'n_epochs': 2000,                # maximum number of epochs
    'batch_size': 270,               # mini-batch size for dataloader
    'optimizer': 'SGD',              # optimization algorithm (optimizer in torch.optim)
    'optim_hparas': {                # hyper-parameters for the optimizer (depends on which optimizer you are using)
        'lr': 0.0001,                 # learning rate of SGD
        'momentum': 0.9              # momentum for SGD
    },
    'save_path': 'model_save/model.pth',  # your model will be saved here
}

#一些超參數  比如epoch  batchsize lr 等等。

定義loss  這里采用了mseloss 然后還加上了正則化

def getLoss(pred, target, model):
    loss = nn.MSELoss(reduction='mean')
    ''' Calculate loss '''
    regularization_loss = 0
    for param in model.parameters():
        # 使用L2正則項
        regularization_loss += torch.sum(param ** 2)
    return loss(pred, target) + 0.00075 * regularization_loss

loss =  getLoss

定義model和優化器 以及數據傳入loader 前面說過這是為了批量處理

model = myNet(feature_dim).to(device)
optimizer = optim.SGD(model.parameters(), lr=0.001,momentum=0.9)


trainloader = DataLoader(trainset,batch_size=config['batch_size'],shuffle=True)
valloader = DataLoader(valset,batch_size=config['batch_size'],shuffle=True)

訓練和測試


train_val(model, trainloader,valloader,optimizer, loss, config['n_epochs'],device,save_=config['save_path'])
evaluate(config['save_path'], testset, 'pred.csv',device)

 

完整代碼:

from model_utils.model import myNet
from model_utils.data import covidDataset
from model_utils.train import train_val
from model_utils.evaluate import evaluate
from torch import optim
import torch.nn as nn
import torch
from torch.utils.data import Dataset,DataLoader

device = 'cuda' if torch.cuda.is_available() else 'cpu'
train_path = 'covid.train.csv'
test_path = 'covid.test.csv'


feature_dim = 6
trainset = covidDataset(train_path,'train',feature_dim=feature_dim)
valset = covidDataset(train_path,'val',feature_dim=feature_dim)
testset = covidDataset(test_path,'test',feature_dim=feature_dim)



config = {
    'n_epochs': 2000,                # maximum number of epochs
    'batch_size': 270,               # mini-batch size for dataloader
    'optimizer': 'SGD',              # optimization algorithm (optimizer in torch.optim)
    'optim_hparas': {                # hyper-parameters for the optimizer (depends on which optimizer you are using)
        'lr': 0.0001,                 # learning rate of SGD
        'momentum': 0.9              # momentum for SGD
    },
    'early_stop': 200,               # early stopping epochs (the number epochs since your model's last improvement)
    'save_path': 'model_save/model.pth',  # your model will be saved here
}

def getLoss(pred, target, model):
    loss = nn.MSELoss(reduction='mean')
    ''' Calculate loss '''
    regularization_loss = 0
    for param in model.parameters():
        # TODO: you may implement L1/L2 regularization here
        # 使用L2正則項
        # regularization_loss += torch.sum(abs(param))
        regularization_loss += torch.sum(param ** 2)
    return loss(pred, target) + 0.00075 * regularization_loss

loss =  getLoss

model = myNet(feature_dim).to(device)
optimizer = optim.SGD(model.parameters(), lr=0.001,momentum=0.9)


trainloader = DataLoader(trainset,batch_size=config['batch_size'],shuffle=True)
valloader = DataLoader(valset,batch_size=config['batch_size'],shuffle=True)

train_val(model, trainloader,valloader,optimizer, loss, config['n_epochs'],device,save_=config['save_path'])
evaluate(config['save_path'], testset, 'pred.csv',device)

 

事后:

                運行主函數 我們將得到 pred.csv。這就是我們得預測結果啦。打開kaggle網址項目所在頁, 注冊,點擊late submission 提交你的pred.csv文件吧。 這也是我第一次用kaggle。 好像我的得分也很低。大家如果想得一個比較高得分,可以多調調超參和模型。fighting!!!

         

 

   李宏毅老師前年課程的第一個作業也是回歸,不過不是新冠。當時我啥都不會寫,把網上得copy下來,一步一步調試才慢慢懂一點點。 這次第二次做回歸,只能說比第一次熟練了很多,雖然還是不能全部一個人寫下來。 寫這個文章,與大家共勉。

 



免責聲明!

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



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