在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 dataset
pretrain階段的資料前處理完整程式碼:
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.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())
流程如下
.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.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)
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.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()
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.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)
...
在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')