iT邦幫忙

2023 iThome 鐵人賽

DAY 11
0

Pretrain 階段內容詳解

1. 資料前處理

訓練資料原始字串內容

在medical dataset中的pretrain資料夾中挑幾個樣本出來

  • json檔中每個line都是一個json物件
  • 每一個line都只有一個key: text
  • line['text']中就是一個訓練樣本的原始字串
{"text": "外阴上皮样肉瘤的并发症?易并发感染。伴发区域淋巴结转移性肿大,后期常转移至肺。"}
{"text": "转移因子口服液的副作用(不良反应)?尚未见有关不良反应报道"}

 從內容可以看出pretrain階段的訓練資料就是一段一段的文本內容,這邊故意挑短的方便閱讀。

資料前處理程式碼

pretrain資料前處理流程:
1.讀取一個.json檔,
2.把其中每一個樣本的text轉成對應的tokens,
3.在tokens結尾加上一個<eos>的token,
4.之後全部串起來成為一個很長的陣列,
5.最後把陣列型態轉換成uin16(因為字典長度64793 < 2^16),
6.寫入到一個.bin檔。

以下是medical datasetpretrain階段的資料前處理完整程式碼:

  • line['text']字串轉成tokens
    • text = line['text']
    • ‵text_id=tokenizer.encode(text,add_special_tokens=False)‵
  • 在結尾加上一個special_token<eos>
    • text_id.append(tokenizer.special_tokens['<eos>'])
  • 限制每個樣本的token總數 > 5,否則丟棄
  • 將所有樣本的tokens(text_id)串在一起成為很長的一個陣列(doc_ids)
  • doc_ids轉成uint16陣列後寫入.bin
    arr = np.array(doc_ids,dtype=np.uint16)

data_process.py

def process_medical(data_path,name):
    f=open(data_path,'r')
    doc_ids=[]
    while True:
        line=f.readline()
        if not line:
            break
        line=json.loads(line)
        text=line['text']
        text_id=tokenizer.encode(text,add_special_tokens=False)
        text_id.append(tokenizer.special_tokens['<eos>'])
        if len(text_id)>5:
            doc_ids+=text_id
    arr = np.array(doc_ids,dtype=np.uint16)
    with open('./data/medical_{}.bin'.format(name),'wb') as f:
        f.write(arr.tobytes()) 

2. 讀取 datasets

流程如下

  1. 讀取多個.bin檔內預處理好的uint16陣列doc_ids
  2. data=多個.bin檔內的doc_ids串在一起
  3. 刪除陣列尾部資料,確保 len(data) % max_length = 0
    • max_length這邊為512
  4. 將超長的陣列data切割成多個訓練樣本
    • data = data.reshape(-1,max_length)

    假設 data.shape = [1024],reshape過後data.shape=[2,512]表示有2筆訓練樣本

  5. __getitem__
    • max_length = max_seq_len = 512
    • X = data['index'][0:511]
    • Y = data['index'][1:512]
    • `return X,Y

這邊也解釋了為什麼模型輸入的token數與預測token數=max_seq_len-1,主要是方便,因為一筆sample的長度為max_seq_len,X,Y分別的長度就是max_seq_len-1。

dataset.py

class PretrainDataset(Dataset):
    def __init__(self,data_path_lst,max_length=256,memmap=False):
            data_lst=[]
            for data_path in data_path_lst:
                with open(data_path,'rb') as f:
                    data=np.fromfile(f,dtype=np.uint16)
                    data_lst.append(data)
            data = np.concatenate(data_lst)
            data = data[:max_length*int(len(data)/max_length)]
            self.data = data.reshape(-1,max_length)       
    def __len__(self):
        return self.data.shape[0]
    def __getitem__(self, index: int):
        #
        sample = self.data[index]
        X=np.array(sample[:-1]).astype(np.int64)
        Y=np.array(sample[1:]).astype(np.int64)
        
        return torch.from_numpy(X),torch.from_numpy(Y)

3. 訓練

pretrain的訓練程式碼在 pretrain.py

流程如下

  • 印出 decode過後的 X,Y 內容

    這邊的X,Y內容故意刪減過,方便理解,真實情況下所有的X,Y長度都會是max_seq_len-1

    • 印出X: ",凹向左心室。室间隔可分为肌部和膜部两部"
    • 印出Y: "凹向左心室。室间隔可分为肌部和膜部两部份"
    • model的預測的目標就是Y
      • Y = X[1:] + ""(需要預測的下一個字)
  • 印出 (X,Y,logits) 的 shape 資訊

    • X.shape = torch.Size([32, 511])
    • Y.shape = torch.Size([32, 511])
    • logits.shape = torch.Size([32, 511, 64793])

    從印出的資訊可以反推出以下資訊

    • batch_size = 32
    • max_seq_len = 511 + 1 = 512
    • tokenizer的字典長度=64793

    logits.shape我們可以解析model運作的原理 (先忽略batch)

    • ‵model預測出的結果是logits`
      • logits.shape = torch.Size([511, 64793])
      • 我們知道 model 需要預測 511個token
      • 怎麼預測呢?每一個位置都要預測出字典中每一個token出現在該位置的機率
      • 因此logits=511x64793個機率值
      • logitsargmax可以得到511個token_index

pretrain.py

def train_epoch(epoch):
    ...
    for step, (X, Y) in enumerate(train_loader):
        logits = model(X, Y)
        loss = raw_model.last_loss
        scaler.scale(loss).backward()

4. Loss 計算

pretrain 階段 Loss 計算寫在 model.py 裡面

如果簡化一下程式碼,pretrain階段的Loss計算很容易理解:

  • self.last_loss = F.cross_entropy(logits, Y, ignore_index=-1)
  • 看得出來就是用logitsY計算cross_entropy
  • 然後要忽略Y=-1位置的loss (token=-1表示該位置沒有任何意義,純粹是填充)

model.py

def forward(self, tokens: torch.Tensor, targets: Optional[torch.Tensor] = None) -> torch.Tensor:
    ...
    self.last_loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1), ignore_index=-1)
    ...

pretrain 階段 Debug

pretain.py中印出X,Y發現竟然有一些是亂碼,可能是在前處理的地方出了問題。

from chatglm_tokenizer.tokenization_chatglm import ChatGLMTokenizer
tokenizer=ChatGLMTokenizer(vocab_file='./chatglm_tokenizer/tokenizer.model')
...
def train_epoch(epoch):
    ...
    for step, (X, Y) in enumerate(train_loader):
        print(tokenizer.decode(X[0].cpu().numpy().tolist()))
        ...
  • 印出的 decode(X) 結果竟然有亂碼
    '••·咖H叩IoS曲mI P叩刊叩S阰T叩血匈叩9平onP叫科l叩In I.现1彻叩中I,nq,叫Ir.“内的叩叩$呻仰和叩引·卧Tn"\'i111几叮m,nlil1mn T『扣?知hm1叩InIoSIIh1111吓1mP叩引1位印加1叩$,叩叩吓佃·I S阰还T d沁护加meSRP伈盯ne"\'的Qq S卧Run庄叩盯和oPr砬IFull SRX:Experim的t(实验设计)\nSM2212489:Leaf BGM-infes也d24hr rep4; Hordeum vulgare; RNA-Seq fTDJJMTIIJAl[Oumina HiSeq2500)run:38M spots.9.5G bases.4.3Gb downloadsStudy:Ba rl ey tran亡nses to specialist and generalist spider n飞te herbivore s PRJNA326683SRPD7702O A|1expenment s·AII runsshow Abstract SRP:Study(研究课题)\nSam;以骂芦一名巴詈广二二·归-n s\nOrganism:~S RS:Sample(样本)\nStrategy RNA-Seq I相应测序平台、测序数据类型及测序方式(PAIRED或SINGLE)Source.·TRAN SCRI PTOMICLayoul PAIRED Construdionprolocol:Leaf tissue was grnund to fine powder in liquid nitrogen. Total RNA was prepared using theDirectZol RNA extraction kit(Zymo Research). Quality of the RNA extractions was assessed using a Bioanalyzer(Aligent). The libraries were prepar ed at the University of Utah High Throughput Genomicsfaci lity using the lllumina TruSeq Stranded mRNA Library Preparation Kit with poly(A)selectionExr===:SM22124891测序样本(SRS)对应的GEO样本ID号(GSM)Runs:'
    

嘗試找出model預測結果很差的原因,雖然不一定是造成sft訓練結果很差的原因,不過這邊出現亂碼的確需要解決一下。


上一篇
Day 10 - Baby LLama2 Chinese (4)
下一篇
Day 12 - Baby LLama2 Chinese (6) 重新整理Pretrain的資料前處理
系列文
用單張顯卡探索大型語言模型的奧秘30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Penut Chen
iT邦研究生 5 級 ‧ 2023-09-12 15:24:37

你的 Tokenizer 是用哪個?既然要從頭訓練的話,要不要考慮 Tokenizer 也重新訓練?比較不容易因為 BPE Tokenizer 會 Fallback To Bytes 所造成的亂碼問題。

jjchen1 iT邦新手 5 級 ‧ 2023-09-14 12:03:52 檢舉

恩~謝謝你的建議,我應該會用使用的dataset自己重新訓練一個tokenizer來解決亂碼的問題~

jjchen1 iT邦新手 5 級 ‧ 2023-09-14 13:30:36 檢舉

我後來研究了一下,這邊碰到的亂碼問題是在medical dataset裡面就存在的,是數據本身的問題;我目前使用的tokenizer跟baby llama2 chinese這個repo的作者一樣使用ChatGLM的tokenizer

from chatglm_tokenizer.tokenization_chatglm import ChatGLMTokenizer
tokenizer=ChatGLMTokenizer(vocab_file='./chatglm_tokenizer/tokenizer.model')

我要留言

立即登入留言