在medical dataset中的pretrain資料夾中挑幾個樣本出來
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']
<eos>
text_id.append(tokenizer.special_tokens['<eos>'])
text_id)串在一起成為很長的一個陣列(doc_ids)doc_ids轉成uint16陣列後寫入.bin檔arr = np.array(doc_ids,dtype=np.uint16)
data_process.pydef 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()) 
流程如下
.bin檔內預處理好的uint16陣列doc_ids
data=多個.bin檔內的doc_ids串在一起len(data) % max_length = 0
max_length這邊為512data切割成多個訓練樣本
data = data.reshape(-1,max_length)
假設
data.shape = [1024],reshape過後data.shape=[2,512],表示有2筆訓練樣本
__getitem__
X = data['index'][0:511]
Y = data['index'][1:512]
這邊也解釋了為什麼模型輸入的token數與預測token數=max_seq_len-1,主要是方便,因為一筆sample的長度為max_seq_len,X,Y分別的長度就是max_seq_len-1。
dataset.pyclass 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)
pretrain的訓練程式碼在
pretrain.py中
流程如下
印出 decode過後的 X,Y 內容
這邊的X,Y內容故意刪減過,方便理解,真實情況下所有的X,Y長度都會是max_seq_len-1
X: ",凹向左心室。室间隔可分为肌部和膜部两部"Y: "凹向左心室。室间隔可分为肌部和膜部两部份"Y
Y = X[1:] + "份"(需要預測的下一個字)印出 (X,Y,logits) 的 shape 資訊
logits.shape = torch.Size([32, 511, 64793])
從印出的資訊可以反推出以下資訊
從logits.shape我們可以解析model運作的原理 (先忽略batch)
預測出的結果是logits`
logits.shape = torch.Size([511, 64793])
model 需要預測 511個token
字典中每一個token出現在該位置的機率
logits=511x64793個機率值
logits 取 argmax可以得到511個token_index
pretrain.pydef train_epoch(epoch):
    ...
    for step, (X, Y) in enumerate(train_loader):
        logits = model(X, Y)
        loss = raw_model.last_loss
        scaler.scale(loss).backward()
pretrain 階段 Loss 計算寫在
model.py裡面
如果簡化一下程式碼,pretrain階段的Loss計算很容易理解:
self.last_loss = F.cross_entropy(logits, Y, ignore_index=-1)
logits與Y計算cross_entropy
忽略Y=-1位置的loss (token=-1表示該位置沒有任何意義,純粹是填充)model.pydef 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)
    ...
在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()))
        ...
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訓練結果很差的原因,不過這邊出現亂碼的確需要解決一下。
你的 Tokenizer 是用哪個?既然要從頭訓練的話,要不要考慮 Tokenizer 也重新訓練?比較不容易因為 BPE Tokenizer 會 Fallback To Bytes 所造成的亂碼問題。
恩~謝謝你的建議,我應該會用使用的dataset自己重新訓練一個tokenizer來解決亂碼的問題~
我後來研究了一下,這邊碰到的亂碼問題是在medical dataset裡面就存在的,是數據本身的問題;我目前使用的tokenizer跟baby llama2 chinese這個repo的作者一樣使用ChatGLM的tokenizer
from chatglm_tokenizer.tokenization_chatglm import ChatGLMTokenizer
tokenizer=ChatGLMTokenizer(vocab_file='./chatglm_tokenizer/tokenizer.model')