GPT2-Chinese 介紹
GPT-2 (Generative Pre-trained Transformer 2) 是由 OpenAI 開發(fā)的一種基于 Transformer 模型的自然語言處理(NLP)模型,旨在生成自然流暢的文本。它是一種無監(jiān)督學(xué)習(xí)模型,其設(shè)計目標是能夠理解人類語言的復(fù)雜性并模擬出自然的語言生成。
GPT-2 是目前最先進的自然語言處理模型之一,因為它具有大量的訓(xùn)練數(shù)據(jù)和強大的算法,可以生成自然流暢、準確的文本,其中文版本為GPT2-Chinese:使用wiki中文通用語料訓(xùn)練。
與其他基于神經(jīng)網(wǎng)絡(luò)的語言模型相比,GPT-2 具有許多獨特的優(yōu)點。首先,它采用了自監(jiān)督學(xué)習(xí)的方式進行訓(xùn)練,使其能夠處理多種語言和任務(wù)。其次,GPT-2 可以生成各種類型的文本,例如新聞、故事、對話和代碼等。最后,GPT-2 模型使用了大量的預(yù)訓(xùn)練參數(shù),使其具有強大的表現(xiàn)力和泛化能力。
GPT2-Chinese 版本是 GPT-2 模型的中文版本,也是基于 Transformer 模型構(gòu)建的,具有相同的架構(gòu)和訓(xùn)練技術(shù)。GPT2-Chinese 已經(jīng)在多項中文 NLP 任務(wù)上取得了顯著的成果,并被廣泛應(yīng)用于中文文本生成、問答、文本摘要和翻譯等領(lǐng)域。
GPT-2 參數(shù)數(shù)量?1.5億到1.75億模型大小0.5GB到1.5GB
模型 |
每個參數(shù)占用的字節(jié)大小 |
模型大小 |
模型大小 |
層數(shù) |
頭數(shù) |
GPT-1 |
4 個字節(jié)的 FP32 精度浮點數(shù) |
117M |
446MB |
12 |
12 |
GPT-2 |
2 個字節(jié)的 FP16 |
1.5億到1.75億 |
0.5GB到1.5GB |
48 |
16 |
GPT-3 |
2 個字節(jié)的 FP16 |
1.75萬億(17500億) |
350GB |
175 |
96個頭 |
下載代碼
https://github.com/Morizeyao/GPT2-Chinese
?
在根目錄(目錄\GPT2-Chinese\)下建立文件夾data和model
\GPT2-Chinese\data
\GPT2-Chinese\model
把要訓(xùn)練的小說復(fù)制到train.json這里面
train.json(也即->?\GPT2-Chinese\data\train.json),需要注意的是,train.json編碼格式嚴格為UTF-8,并且不帶BOM頭<-去頭咱用的sublime。
?
vocab
- vocab.txt:詞匯表。默認的大小為13317,若需要使用自定義字典,需要將confog.json文件中的vocab_size字段設(shè)為相應(yīng)的大小。也就是vocab.txt文件有多少行,多少個分詞.
- 把?vocab.txt 字典文件里的 [SEP]?行號減1,設(shè)置為?Config.json 配置文件? "bos_token_id": 1,和? "eos_token_id": 1,的值.減1是因為vocab是從0下標開始,而行下標是從1開始.我這里設(shè)置為1是因為我的?[SEP]?在vocab的第二行.
特殊 token?符號
[UNK]:表示未知標記(即,詞匯表中沒有的詞);
[SEP]:表示句子分隔符;換行
[PAD]:表示填充標記,用于填充序列的長度;也就是無效符號。
pad_token_id默認為tokenizer.eos_token_id,這是特殊token [EOS]的位置。它被用來指示模型當(dāng)前生成的句子已經(jīng)結(jié)束,因此當(dāng)我們想要生成一個開放式文本時,我們可以將pad_token_id設(shè)置為eos_token_id,以確保生成文本不會被提前結(jié)束。
[CLS]:表示分類標記,用于BERT模型的分類任務(wù);文章之間添加CLS表示文章結(jié)束
[MASK]:表示掩碼標記,用于BERT模型的掩碼語言建模任務(wù)。文章開頭添加MASK表示文章開始
?vocab 詞匯表
在自然語言處理任務(wù)中,將文本轉(zhuǎn)換成數(shù)字是非常重要的預(yù)處理步驟之一。這個過程叫做文本編碼。傳統(tǒng)的文本編碼方法,如one-hot編碼或詞袋模型,通常會忽略單詞之間的語義和上下文關(guān)系,因此不太適用于語義相似性計算、文本分類、問答系統(tǒng)等需要更深層次理解文本含義的任務(wù)。
通過詞匯表 把一個字 、一個短語 、一個短句 轉(zhuǎn)換成一個數(shù)字 ,形成了 數(shù)字 映射 成 文字,文字映射成 數(shù)字的過程。字典 詞典?
?詞匯表的大小會對訓(xùn)練模型的大小和復(fù)雜度產(chǎn)生影響。
在自然語言處理中,詞匯表是所有可能的單詞集合。如果詞匯表很大,那么訓(xùn)練模型需要處理更多的單詞和更多的單詞組合,因此會增加模型的復(fù)雜度和大小。
此外,詞匯表的大小還會影響模型的訓(xùn)練時間和資源消耗。一個包含更多單詞的詞匯表需要更多的內(nèi)存和計算資源來存儲和處理。
因此,為了訓(xùn)練一個高效且準確的自然語言處理模型,需要平衡詞匯表大小和模型大小之間的關(guān)系,并考慮可用的計算資源和訓(xùn)練時間。
?安裝依賴
transformers>=2.1.1 torch numpy tqdm sklearn keras tb-nightly future thulac
?如果transformers>=4.2.1? 要修改的改以下代碼
transformers 報錯 got_ver is None,重裝numpy,
pip uninstall numpy
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple numpy
#model_config = transformers.modeling_gpt2.GPT2Config.from_json_file(args.model_config)
model_config = transformers.models.gpt2.GPT2Config.from_json_file(args.model_config)
if not args.pretrained_model:
#model = transformers.modeling_gpt2.GPT2LMHeadModel(config=model_config)
model = transformers.models.gpt2.GPT2LMHeadModel(config=model_config)
else:
#model = transformers.modeling_gpt2.GPT2LMHeadModel.from_pretrained(args.pretrained_model)
model = transformers.models.gpt2.GPT2LMHeadModel(args.pretrained_model)
提示模塊'transformers'沒有屬性'WarmupLinearSchedule'的異常
這是因為在新版本中WarmupLinearSchedule方法已經(jīng)沒有了,可以換為get_linear_schedule_with_warmup方法
# scheduler = transformers.WarmupLinearSchedule(optimizer, warmup_steps=warmup_steps,
# t_total=total_steps)
scheduler = transformers.get_linear_schedule_with_warmup(optimizer, num_warmup_steps =warmup_steps,
num_training_steps =total_steps)
?Config.json 配置文件
{
"_name_or_path": "model/",
"activation_function": "gelu_new",
"architectures": [
"GPT2LMHeadModel"
],
"attn_pdrop": 0.1,
"bos_token_id": 1,
"embd_pdrop": 0.1,
"eos_token_id": 1,
"initializer_range": 0.02,
"layer_norm_epsilon": 1e-05,
"model_type": "gpt2",
"n_ctx": 512,
"n_embd": 1024,
"n_head": 16,
"n_inner": null,
"n_layer": 12,
"n_positions": 512,
"reorder_and_upcast_attn": false,
"resid_pdrop": 0.1,
"scale_attn_by_inverse_layer_idx": false,
"scale_attn_weights": true,
"summary_activation": null,
"summary_first_dropout": 0.1,
"summary_proj_to_labels": true,
"summary_type": "cls_index",
"summary_use_proj": true,
"torch_dtype": "float16",
"transformers_version": "4.26.1",
"use_cache": true,
"vocab_size": 47689
}
??Config.json 配置文件參數(shù)解釋
"activation_function": "gelu_new": 激活函數(shù),這里使用改進版的GELU函數(shù)
"attn_pdrop": 0.1: 注意力機制中的dropout概率,每個注意力機制的權(quán)重有10%的概率被置為0
"bos_token_id": 50256: 開始標記(Begin-Of-Sequence)的token ID,這里是50256
"embd_pdrop": 0.1: 輸入嵌入層中的dropout概率,每個輸入token的嵌入向量有10%的概率被置為0
"eos_token_id": 50256: 結(jié)束標記(End-Of-Sequence)的token ID,這里是50256
"initializer_range": 0.02: 初始化權(quán)重矩陣的范圍,權(quán)重值在正負0.02之間均勻分布
"layer_norm_epsilon": 1e-05: Layer Normalization的epsilon值,防止分母為0
"model_type": "gpt2": 模型類型,這里是GPT-2
"n_ctx": 512: 輸入的文本序列的最大長度,這里是512
"n_embd": 768: 輸入嵌入層的維度,這里是768
"n_head": 12: Transformer中的多頭注意力機制中頭的數(shù)量,這里是12
"n_inner": null: Transformer中全連接層的隱層層數(shù),這里是null表示使用默認值(4)
"n_layer": 10: Transformer中的層數(shù),這里是10
"n_positions": 512: 輸入的位置編碼的維度,這里是512
"reorder_and_upcast_attn": false: 是否需要重新排序并升級注意力機制的權(quán)重矩陣,這里是false
"resid_pdrop": 0.1: 殘差連接的dropout概率,每個殘差連接的輸出向量有10%的概率被置為0
"scale_attn_by_inverse_layer_idx": false: 是否根據(jù)層數(shù)來縮放注意力機制的權(quán)重矩陣,這里是false
"scale_attn_weights": true: 是否需要縮放注意力機制的權(quán)重矩陣,這里是true
"summary_activation": null: 摘要(Summary)層的激活函數(shù),這里是null表示使用默認值(softmax)
"summary_first_dropout": 0.1: 摘要(Summary)層中第一個dropout的概率,這里是10%
"summary_proj_to_labels": true: 摘要(Summary)層是否需要將摘要結(jié)果投影到標簽空間,這里是true
"summary_type": "cls_index": 摘要(Summary)的類型,這里是CLS池化層
"summary_use_proj": true: 摘要(Summary)層是否需要使用投影層,這里是true
"transformers_version": "4.22.1": 使用
訓(xùn)練材料處理流程
0.根據(jù)小說制作詞匯表文件
import os
import jieba
from collections import Counter
def build_vocab(text_file, vocab_file, vocab_size, ignore_single=False):
# 讀取文本文件并進行分詞
'''
text_file:要處理的文本文件路徑。
vocab_file:生成的詞匯表文件路徑。
vocab_size:詞匯表大小,即最多包含多少個單詞。
ignore_single 的布爾型參數(shù),默認為 False。如果設(shè)置為 True,則不會將單個字添加到詞匯表中
'''
# 讀取文本文件并進行分詞
with open(text_file, 'r', encoding='utf-8') as f:
text = f.read()
words = jieba.lcut(text)
# 統(tǒng)計詞頻
counter = Counter(words)
if ignore_single:
counter = {word: freq for word, freq in counter.items() if len(word) > 1}
sorted_words = sorted(counter.items(), key=lambda x: x[1], reverse=True)
# 保存詞匯表文件
with open(vocab_file, 'w', encoding='utf-8') as f:
for i, (word, freq) in enumerate(sorted_words):
if i >= vocab_size:
break
f.write(word + '\n')
dir_path = os.path.dirname(os.path.abspath(__file__)) # 本腳本所在的目錄路徑,
novel_file_path = os.path.join(dir_path, "西游記.txt")
vocab_path = os.path.join(dir_path, "vocab.txt")
build_vocab(novel_file_path, vocab_path, 48000)
?
1.讀取小說文件,平分成100份,100份后多余的部分舍棄.每份保存到一個文件
import os
from tqdm import tqdm
if __name__ == '__main__':
dir_path = os.path.dirname(os.path.abspath(__file__)) # 本腳本所在的目錄路徑
novel_file_path = os.path.join(dir_path, "西游記.txt")
split_novel_path = os.path.join(dir_path, "split_novel")
# 創(chuàng)建保存 tokenized 文件的目錄
if not os.path.exists(split_novel_path):
os.mkdir(split_novel_path)
with open(novel_file_path, 'r', encoding='utf8') as f:
print('reading lines')
single = f.read()
len_single = len(single)
num_pieces = 100
for i in tqdm(range(num_pieces)):
# 從 single 中截取一段長度為 len_single // num_pieces
# 并進行分詞
sub_text = single[len_single // num_pieces * i: len_single // num_pieces * (i + 1)]
seg_file_path = os.path.join(split_novel_path, f"split_novel_{i}.txt")
with open(seg_file_path, 'w', encoding='utf8') as f:
f.write(sub_text)
2.每份字符串用cut函數(shù)分詞,單詞之間用空格連接區(qū)分 ,然后把每份寫到 word_segmentation文件夾內(nèi),每份名稱word_segmentation_0.txt-word_segmentation_99.txt
'''
from vocab import Vocab
# 創(chuàng)建一個詞匯表對象
vocab = Vocab('vocab.txt')
# 對輸入文本進行分詞
text = '我愛自然語言處理'
tokens = vocab.cut(text)
print(tokens) # ['我', '愛', '[UNK]', '[UNK]']
# 將單詞列表編碼成單詞索引列表
token_ids = vocab.encode_tokens(tokens)
print(token_ids) # [143, 54, 0, 0]
# 將單詞索引列表解碼成單詞列表
decoded_tokens = vocab.decode_tokens(token_ids)
print(decoded_tokens) # ['我', '愛', '[UNK]', '[UNK]']
'''
class Vocab:
def __init__(self, vocab_file):
"""
從給定的詞匯表文件中構(gòu)建一個詞匯表對象,并將每個單詞與其對應(yīng)的索引建立映射關(guān)系。
Args:
vocab_file (str): 詞匯表文件路徑。
"""
self.token2id = {} # 詞匯表中每個單詞與其索引之間的映射(字典)
self.id2token = {} # 詞匯表中每個索引與其對應(yīng)的單詞之間的映射(字典)
# 讀取詞匯表文件,將每個單詞映射到其索引
with open(vocab_file, 'r', encoding='utf-8') as f:
for i, line in enumerate(f):
token = line.strip() # 移除行尾的換行符并得到單詞
self.token2id[token] = i # 將單詞映射到其索引
self.id2token[i] = token # 將索引映射到其對應(yīng)的單詞
self.num_tokens = len(self.token2id) # 詞匯表中單詞的數(shù)量
self.unknown_token = '[UNK]' # 特殊的未知標記
self.pad_token = '[PAD]' # 用于填充序列的特殊標記(在這里僅用于編碼)
self.pad_token_id = self.token2id.get(self.pad_token, -1) # 填充標記的索引
def cut(self, input_str):
"""
中文語句分詞的算法 用python代碼 cut函數(shù) 參數(shù) 詞匯表文件 和 語句str
詞匯表文件 每行一個詞語,
1.詞匯表字典的鍵為詞匯,值為該詞匯在詞匯表中的行號-1,也即該詞匯在詞匯表中的索引位置。
3.輸入的中文語句,從左到右依次遍歷每一個字符,以當(dāng)前字符為起點嘗試匹配一個詞匯。具體匹配方式如下:
a. 從當(dāng)前字符開始,依次向后匹配,直到找到一個最長的詞匯。如果該詞匯存在于詞典中,就將其作為一個分詞結(jié)果,并將指針移動到該詞匯的后面一個字符。如果該詞匯不存在于詞典中,則將當(dāng)前字符作為一個單獨的未知標記,同樣將其作為一個分詞結(jié)果,并將指針移動到下一個字符。
b. 如果從當(dāng)前字符開始,沒有找到任何詞匯,則將當(dāng)前字符作為一個單獨的未知標記,同樣將其作為一個分詞結(jié)果,并將指針移動到下一個字符。
重復(fù)上述過程,直到遍歷完整個輸入的中文語句,得到所有的分詞結(jié)果列表。
"""
result = []
i = 0
while i < len(input_str):
longest_word = input_str[i]
for j in range(i + 1, len(input_str) + 1):
if input_str[i:j] in self.token2id:
longest_word = input_str[i:j]
result.append(longest_word)
i += len(longest_word)
return result
def encode_tokens_strToint(self, tokens):
"""
將給定的單詞列表編碼成對應(yīng)的單詞索引列表。
如果一個單詞在詞匯表中沒有出現(xiàn),則將其替換為特殊的未知標記。
Args:
tokens (list): 待編碼的單詞列表。
Returns:
token_ids (list): 編碼后的單詞索引列表。
"""
return [self.token2id.get(token, self.token2id[self.unknown_token]) for token in tokens]
def decode_tokens_intTostr(self, token_ids):
"""
將給定的單詞索引列表解碼成對應(yīng)的單詞列表。
如果一個索引在詞匯表中沒有對應(yīng)的單詞,則將其替換為特殊的未知標記。
Args:
token_ids (list): 待解碼的單詞索引列表。
Returns:
tokens (list): 解碼后的單詞列表。
"""
return [self.id2token.get(token_id, self.unknown_token) for token_id in token_ids]
import os
import threading
from multiprocessing import Process
from Vocab import Vocab
def process_file(i: int, content: str, vocab: Vocab, word_segmentation_path: str):
# 進行分詞并保存到文件
seg = vocab.cut(content)
file_path = os.path.join(word_segmentation_path, f"word_segmentation_{i}.txt")
with open(file_path, "w", encoding="utf-8") as f:
f.write(" ".join(seg))
print(f"Thread-{threading.current_thread().ident} finished processing file-{i}")
def many_Process():
dir_path = os.path.dirname(os.path.abspath(__file__)) # 本腳本所在的目錄路徑
novel_file_path = os.path.join(dir_path, "西游記.txt")
tokenized_data_path = os.path.join(dir_path, "tokenized")
vocab_path = os.path.join(dir_path, "vocab.txt")
word_segmentation_path = os.path.join(dir_path, "word_segmentation")
split_novel_path = os.path.join(dir_path, "split_novel")
# 創(chuàng)建保存結(jié)果的文件夾
os.makedirs(word_segmentation_path, exist_ok=True)
# 定義文件名和分割數(shù)
num_splits = 100
for i in range(num_splits):
seg_file_path = os.path.join(split_novel_path, f"split_novel_{i}.txt")
with open(seg_file_path, 'r', encoding='utf8') as f:
content = f.read()
# 創(chuàng)建Vocab實例
vocab = Vocab(vocab_path)
proc1 = Process(target=process_file, args=(i, content, vocab, word_segmentation_path))
proc1.start()
if __name__ == '__main__':
many_Process()
3.把分詞文件轉(zhuǎn)換為int數(shù)字文件
import os
def main():
dir_path = os.path.dirname(os.path.abspath(__file__)) # 本腳本所在的目錄路徑
novel_file_path = os.path.join(dir_path, "西游記.txt")
tokenized_data_path = os.path.join(dir_path, "tokenized")
vocab_path = os.path.join(dir_path, "vocab.txt")
word_segmentation_path = os.path.join(dir_path, "word_segmentation")
# 讀取詞匯表
with open(vocab_path, "r", encoding="utf-8") as f:
vocab = {}
for i, line in enumerate(f):
word = line.strip()
vocab[word] = i
# 讀取分詞文件并轉(zhuǎn)換為token
for i in range(100):
seg_file_path = os.path.join(word_segmentation_path, f"word_segmentation_{i}.txt")
token_file_path = os.path.join(tokenized_data_path, f"tokenized_train_{i}.txt")
with open(seg_file_path, "r", encoding="utf-8") as f:
with open(token_file_path, "w", encoding="utf-8") as fw:
for line in f:
tokens = []
for word in line.strip().split():
if word in vocab:
tokens.append(str(vocab[word]))
if tokens:
fw.write(" ".join(tokens) + "\n")
if __name__ == '__main__':
main()
5.調(diào)用訓(xùn)練腳本 train.py
import transformers
import torch
print(torch.cuda.current_device())
import os
import random
import argparse
import numpy as np
from torch.nn import DataParallel
from tqdm import tqdm
import datetime
def is_tokenizer(tokenized_data_path):
if not os.path.exists(tokenized_data_path):
return False
# 獲取目錄下(包含子目錄)的所有文件數(shù)
file_nums = sum([len(files) for root, dirs, files in os.walk(tokenized_data_path)])
if file_nums > 1:
return True
else:
return False
def main():
# char_to_int.run()
parser = argparse.ArgumentParser()
parser.add_argument('--device', default='0,1,2,3', type=str, required=False, help='設(shè)置使用哪些顯卡')
parser.add_argument('--model_config', default='./config.json', type=str, required=False,help='選擇模型參數(shù)')
parser.add_argument('--tokenizer_path', default='./newvocab.txt', type=str, required=False, help='選擇詞庫')
parser.add_argument('--tokenized_data_path', default='./tokenized/', type=str, required=False,help='tokenized語料存放位置')
parser.add_argument('--raw', action='store_true', help='是否先做tokenize')
parser.add_argument('--epochs', default=50000, type=int, required=False, help='訓(xùn)練循環(huán)')
parser.add_argument('--batch_size', default=1, type=int, required=False, help='訓(xùn)練batch size')
parser.add_argument('--lr', default=1.5e-4, type=float, required=False, help='學(xué)習(xí)率')
parser.add_argument('--warmup_steps', default=2000, type=int, required=False, help='warm up步數(shù)')
parser.add_argument('--log_step', default=1, type=int, required=False, help='多少步匯報一次loss')
parser.add_argument('--stride', default=768, type=int, required=False, help='訓(xùn)練時取訓(xùn)練數(shù)據(jù)的窗口步長')
parser.add_argument('--gradient_accumulation', default=1, type=int, required=False, help='梯度積累')
parser.add_argument('--fp16', action='store_true', help='混合精度')
parser.add_argument('--fp16_opt_level', default='O1', type=str, required=False)
parser.add_argument('--max_grad_norm', default=1.0, type=float, required=False)
parser.add_argument('--num_pieces', default=100, type=int, required=False, help='將訓(xùn)練語料分成多少份')
parser.add_argument('--output_dir', default='model/', type=str, required=False, help='模型路徑')
parser.add_argument('--pretrained_model', default='', type=str, required=False, help='模型訓(xùn)練起點路徑')
parser.add_argument('--segment', action='store_true', help='中文以詞為單位')
args = parser.parse_args()
print('args:\n' + args.__repr__())
is_random_shuffle_data=False # 是否打亂順序進行訓(xùn)練
if args.segment:
from tokenizations import tokenization_bert_word_level as tokenization_bert
else:
from tokenizations import tokenization_bert
os.environ["CUDA_VISIBLE_DEVICES"] = args.device # 此處設(shè)置程序使用哪些顯卡
model_config = transformers.models.gpt2.GPT2Config.from_json_file(args.model_config)
print('config:\n' + model_config.to_json_string())
n_ctx = model_config.n_ctx
full_tokenizer = tokenization_bert.BertTokenizer(vocab_file=args.tokenizer_path)
full_tokenizer.max_len = 999999
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print('using device:', device)
tokenized_data_path = args.tokenized_data_path
raw = is_tokenizer(tokenized_data_path) # 選擇是否從零開始構(gòu)建數(shù)據(jù)集
epochs = args.epochs
batch_size = args.batch_size
lr = args.lr
warmup_steps = args.warmup_steps
log_step = args.log_step
stride = args.stride
gradient_accumulation = args.gradient_accumulation
fp16 = args.fp16 # 不支持半精度的顯卡請勿打開
fp16_opt_level = args.fp16_opt_level
max_grad_norm = args.max_grad_norm
num_pieces = args.num_pieces
output_dir = args.output_dir
if raw == False:
print('building files')
model_dir = args.output_dir
config_file = os.path.join(model_dir, 'config.json')
pytorch_model_file = os.path.join(model_dir, 'pytorch_model.bin')
if os.path.isfile(config_file) and os.path.isfile(pytorch_model_file):
print('模型文件存在,加載已訓(xùn)練過的模型,繼續(xù)訓(xùn)練...')
model = transformers.models.gpt2.GPT2LMHeadModel.from_pretrained(model_dir)
else:
print('模型文件不存在,創(chuàng)建新的模型,開始訓(xùn)練...')
model = transformers.models.gpt2.GPT2LMHeadModel(config=model_config)
model.train()
model.to(device)
print(model)
multi_gpu = False
full_len = 0
print('calculating total steps')
for i in tqdm(range(num_pieces)):
with open(tokenized_data_path + 'tokenized_train_{}.txt'.format(i), 'r') as f:
full_len += len([int(item) for item in f.read().strip().split()])
total_steps = int(full_len / stride * epochs / batch_size / gradient_accumulation)
print('total steps = {}'.format(total_steps))
optimizer = transformers.AdamW(model.parameters(), lr=lr, correct_bias=True)
scheduler = transformers.get_linear_schedule_with_warmup(optimizer, num_warmup_steps=warmup_steps,
num_training_steps=total_steps)
steps_Count = 0
if fp16:
try:
from apex import amp
except ImportError:
raise ImportError("Please install apex from https://www.github.com/nvidia/apex to use fp16 training.")
model, optimizer = amp.initialize(model, optimizer, opt_level=fp16_opt_level)
if torch.cuda.device_count() > 1:
print("Let's use", torch.cuda.device_count(), "GPUs!")
model = DataParallel(model)
multi_gpu = True
print('calculating total steps')
for i in tqdm(range(num_pieces)): # 迭代處理所有的 tokenized_train_{}.txt 文件
with open(tokenized_data_path + 'tokenized_train_{}.txt'.format(i), 'r') as f: # 打開文件
full_len += len([int(item) for item in f.read().strip().split()]) # 統(tǒng)計文件中數(shù)字的數(shù)量
total_steps = int(full_len / stride * epochs / batch_size / gradient_accumulation) # 計算總共需要迭代的步數(shù)
print('total steps = {}'.format(total_steps)) # 打印總共需要迭代的步數(shù)
optimizer = transformers.AdamW(model.parameters(), lr=lr, correct_bias=True) # 定義優(yōu)化器
scheduler = transformers.get_linear_schedule_with_warmup(optimizer, num_warmup_steps=warmup_steps,
num_training_steps=total_steps) # 定義學(xué)習(xí)率調(diào)度器
steps_Count = 0 # 初始化迭代步數(shù)
if fp16: # 判斷是否使用半精度浮點數(shù)
try:
from apex import amp # 嘗試導(dǎo)入 apex 庫
except ImportError:
raise ImportError("Please install apex from https://www.github.com/nvidia/apex to use fp16 training.")
model, optimizer = amp.initialize(model, optimizer, opt_level=fp16_opt_level) # 將模型和優(yōu)化器轉(zhuǎn)換成半精度浮點數(shù)
if torch.cuda.device_count() > 1: # 判斷是否有多個 GPU
print("Let's use", torch.cuda.device_count(), "GPUs!")
model = DataParallel(model) # 多 GPU 并行計算
multi_gpu = True # 標記啟用了多 GPU
print('starting training') # 打印開始訓(xùn)練
running_loss = 0 # 初始化損失值
run_pice = 0 # 初始化處理的文件數(shù)量
elapsed_minutes = 3 # 每過 20 分鐘后 就會休息 3.5分鐘.
rest_minutes = 1.2
start_time = datetime.datetime.now()
for epoch in range(epochs):
print('epoch {}'.format(epoch + 1))
now = datetime.datetime.now()
print('time: {}'.format(now))
x = np.linspace(0, num_pieces - 1, num_pieces, dtype=np.int32) # 生成0~num_pieces-1的等差數(shù)列
if is_random_shuffle_data:
random.shuffle(x) # 打亂數(shù)列順序
piece_num = 0
for i in x: # 遍歷每個文件
with open(tokenized_data_path + 'tokenized_train_{}.txt'.format(i), 'r') as f: # 打開tokenized_train_i.txt文件
line = f.read().strip() # 讀取整個文件的內(nèi)容,并移除字符串首尾的空格符
tokens = line.split() # 按照空格符分割字符串,得到單詞列表
tokens = [int(token) for token in tokens] # 將單詞列表中的元素轉(zhuǎn)化為整型數(shù)值
start_point = 0
samples = [] # 存儲數(shù)據(jù)樣本的列表
while start_point < len(tokens) - n_ctx: # 循環(huán)采樣數(shù)據(jù)樣本,直到文本結(jié)束
samples.append(tokens[start_point: start_point + n_ctx]) # 截取長度為n_ctx的數(shù)據(jù)樣本,并加入列表
start_point += stride # 步長為stride
if start_point < len(tokens): # 如果剩下的單詞數(shù)小于n_ctx
samples.append(tokens[len(tokens) - n_ctx:]) # 將剩下的單詞作為一個數(shù)據(jù)樣本加入列表
if is_random_shuffle_data:
random.shuffle(samples) # 打亂數(shù)據(jù)樣本的順序
for step in range(len(samples) // batch_size): # 將數(shù)據(jù)樣本按batch_size分組,遍歷每個batch
# 準備數(shù)據(jù) # 1. 獲取輸入的 batch
batch = samples[step * batch_size: (step + 1) * batch_size] # 獲取當(dāng)前batch的數(shù)據(jù)樣本
batch_labels = [] # 存儲batch的標簽
batch_inputs = [] # 存儲batch的輸入
for ids in batch: # 遍歷當(dāng)前batch的數(shù)據(jù)樣本
int_ids_for_labels = [int(x) for x in ids] # 將數(shù)據(jù)樣本中的每個單詞轉(zhuǎn)化為整型數(shù)值,得到標簽序列
int_ids_for_inputs = [int(x) for x in ids] # 將數(shù)據(jù)樣本中的每個單詞轉(zhuǎn)化為整型數(shù)值,得到輸入序列
batch_labels.append(int_ids_for_labels) # 將標簽序列加入batch_labels列表
batch_inputs.append(int_ids_for_inputs) # 將輸入序列加入batch_inputs列表
batch_labels = torch.tensor(batch_labels).long().to(device) # 將batch_labels轉(zhuǎn)化為PyTorch的tensor,并移動到GPU上
batch_inputs = torch.tensor(batch_inputs).long().to(device) # 將batch_inputs轉(zhuǎn)化為PyTorch的tensor,并移動到GPU上
# 2. 正向傳播
outputs = model.forward(input_ids=batch_inputs, labels=batch_labels)
loss, logits = outputs[:2]
# 3. 計算損失函數(shù)
if multi_gpu:
loss = loss.mean()
if gradient_accumulation > 1:
loss = loss / gradient_accumulation
# 4. 損失函數(shù)反向傳播
if fp16:
with amp.scale_loss(loss, optimizer) as scaled_loss:
scaled_loss.backward()
torch.nn.utils.clip_grad_norm_(amp.master_params(optimizer), max_grad_norm)
else:
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_grad_norm)
# 5. 更新參數(shù) optimizer step
if (step + 1) % gradient_accumulation == 0:
running_loss += loss.item()
optimizer.step()
optimizer.zero_grad()
scheduler.step()
if (step + 1) % log_step == 0:
steps_Count += 1
print('now time: {}:{}. Step {} / {} of piece {} of epoch {}, loss {}'.format(
datetime.datetime.now().hour,
datetime.datetime.now().minute,
steps_Count,
total_steps,
len(samples) // batch_size,
epoch + 1,
running_loss / log_step))
running_loss = 0
if run_pice % 1000 == 0 and run_pice > 999:
model_to_save = model.module if hasattr(model, 'module') else model
print('保存模型中...')
model_to_save.save_pretrained(output_dir)
run_pice += 1
piece_num += 1
print('saving model for epoch {}'.format(epoch + 1))
if not os.path.exists(output_dir):
os.mkdir(output_dir)
model_to_save = model.module if hasattr(model, 'module') else model
model_to_save.save_pretrained(output_dir)
print('epoch {} finished'.format(epoch + 1))
then = datetime.datetime.now()
print('time: {}'.format(then))
print('time for one epoch: {}'.format(then - now))
print('training finished')
if not os.path.exists(output_dir):
os.mkdir(output_dir)
model_to_save = model.module if hasattr(model, 'module') else model
model_to_save.save_pretrained(output_dir)
if __name__ == '__main__':
torch.cuda.init()
main()
6.生成腳本?generate.py
import torch from transformers import GPT2LMHeadModel from tokenizations import tokenization_bert def top_k_top_p_filtering(logits, top_k=0, top_p=0.0, filter_value=-float('Inf')): if top_k > 0: top_k_values, top_k_indices = torch.topk(logits, top_k, dim=-1) logits = logits.masked_fill(logits < torch.max(top_k_values, dim=-1, keepdim=True).values, filter_value) if top_p > 0.0: sorted_logits, sorted_indices = torch.sort(logits, descending=True) cumulative_probs = torch.cumsum(torch.softmax(sorted_logits, dim=-1), dim=-1) sorted_indices_to_remove = cumulative_probs > top_p if top_k > 0: sorted_indices_to_remove[..., :top_k] = 0 indices_to_remove = sorted_indices[sorted_indices_to_remove] mask = torch.zeros_like(logits, dtype=torch.bool).to(device) for idx in indices_to_remove: mask = torch.logical_or(mask, torch.eq(logits, idx)) logits = logits.masked_fill(mask, filter_value) return logits def is_word(word): for item in list(word): if item not in 'qwertyuiopasdfghjklzxcvbnm': return False return True def _is_chinese_char(char): """Checks whether CP is the codepoint of a CJK character.""" # This defines a "chinese character" as anything in the CJK Unicode block: # https://en.wikipedia.org/wiki/CJK_Unified_Ideographs_(Unicode_block) # # Note that the CJK Unicode block is NOT all Japanese and Korean characters, # despite its name. The modern Korean Hangul alphabet is a different block, # as is Japanese Hiragana and Katakana. Those alphabets are used to write # space-separated words, so they are not treated specially and handled # like the all of the other languages. cp = ord(char) if ((cp >= 0x4E00 and cp <= 0x9FFF) or # (cp >= 0x3400 and cp <= 0x4DBF) or # (cp >= 0x20000 and cp <= 0x2A6DF) or # (cp >= 0x2A700 and cp <= 0x2B73F) or # (cp >= 0x2B740 and cp <= 0x2B81F) or # (cp >= 0x2B820 and cp <= 0x2CEAF) or (cp >= 0xF900 and cp <= 0xFAFF) or # (cp >= 0x2F800 and cp <= 0x2FA1F)): # return True return False def tokenizer_decode(tokenizer,out_text): text = tokenizer.convert_ids_to_tokens(out_text) for i, item in enumerate(text[:-1]): # 確保英文前后有空格 if is_word(item) and is_word(text[i + 1]): text[i] = item + ' ' for i, item in enumerate(text): if '[UNK]' ==item: text[i] = '' if item == '[CLS]' or item == '[SEP]': text[i] = '\n' if item == '[PAD]' or '[MASK]' == item: text[i] = ' ' text = ''.join(text) return text def generate_novel(prompt, temperature=0.7, top_k=0, top_p=0.9, length=2000, repetition_penalty=1.0): print(prompt, end="") # 將輸入的 prompt 轉(zhuǎn)化為 token id input_ids = tokenizer.encode(prompt, return_tensors='pt').to(device) # 生成一個和 input_ids 相同形狀的 tensor,全部為 1 input_mask = torch.ones(input_ids.shape, dtype=torch.long).to(device) # 生成的文本初始化為 prompt generated_text = tokenizer.decode(input_ids[0], skip_special_tokens=True) # 禁用梯度計算 with torch.no_grad(): # 控制生成文本長度小于 length while len(generated_text) < length: # 對 input_ids 進行前向傳播,獲取輸出 logits outputs = model(input_ids=input_ids, attention_mask=input_mask) logits = outputs.logits[:, -1, :] / temperature # 對 logits 進行 top-k 和 top-p 過濾 filtered_logits = top_k_top_p_filtering(logits, top_k=top_k, top_p=top_p) # 對已經(jīng)生成的 token 進行重復(fù)懲罰 for i in range(len(input_ids[0])): if input_ids[0][i] == filtered_logits[0].argmax().item(): filtered_logits[0][i] /= repetition_penalty # 計算下一個 token 的概率分布,并采樣出下一個 token probabilities = torch.softmax(filtered_logits, dim=-1) next_token = torch.multinomial(probabilities, num_samples=1) # 將生成的 token 拼接到 input_ids 中 input_ids = torch.cat((input_ids, next_token), dim=1) # 重新生成一個和 input_ids 相同形狀的 tensor,全部為 1 input_mask = torch.ones(input_ids.shape, dtype=torch.long).to(device) # 將生成的 token 轉(zhuǎn)化為文本,拼接到 generated_text 中 new_text = tokenizer_decode(tokenizer,next_token[0]) print(new_text, end="") generated_text +=new_text # 如果生成的文本長度已經(jīng)超過 length,結(jié)束生成過程 if len(generated_text) >= length: break return generated_text tokenizer = tokenization_bert.BertTokenizer('./vocab.txt') model = GPT2LMHeadModel.from_pretrained( "./model/") device = "cuda" if torch.cuda.is_available() else "cpu" model.to(device) model.eval() prompt = "孫悟空吃完仙桃," generate_novel(prompt)
?文本生成效果文章來源:http://www.zghlxwxcb.cn/news/detail-647080.html
第三回情友。卻說那沙僧急急抬頭觀看, 第二指腰尸往里觀看,闖入斗柄賀喜環(huán)現(xiàn)出鮮紅之下,看見八戒者即著腳手往里觀看,看見, 著實個活捉了鋼刀,半霧,把個燈籠。行者道:“老施主,上禪。那長老看何地方,只見海邊此必是救出師父,潑猢猻打破唐僧,拿住得受用槍!” 慌得就問老大慌了手腳,把釘鈀輪著訣,對對唐僧,就問曰:“若不是甚么披掛來也!開門,又搖身抵語,有莫打破人頭打破了?!?旁有張睜睛看處,釘鈀筑了,把個大睜睛看處,原來那怪見有八萬四千鋼頭鉆將出來厲聲高叫道:“潑猢猻!你從成精之對你這潑物,長的根,卻不認得尊神饒命!你不知是個器只叫你,我也?”八戒道:“你,把門的?”八戒道:“你不知,斷乎是我們斗瓦噴,把他就跳下何處睡看我,我們且是個假的?!?八戒道:“不要怕,你轉(zhuǎn)鈀,皺頭綁得甚,把個干凈,卻將起來,八戒道:“拿得我等我?” 八戒道:“我還未哭得象盤跌了,卻就顧得脫了八戒道:“師父不濟!我們幾口,等不知,就答應(yīng)?!卑私涞溃骸按糇訝?,乃是個假八戒道:“看棍!” 八戒道:“我再不敢看。你使兵,使一條槍槍就去救師,八戒依言,只情打了,八戒道:“正是!正是!” 那長老歡歡喜喜。八戒道:“且顧得脫手。你不知,卻又吩咐,等候他繩來!”須臾跪倒,盡睡去尋看去,八戒對八戒道:“虧他綁在那里去了。”好行者道:“正是,只說:“都到此盡皆是假人頭下海。但只說開了!我們走過法兒,再不敢歸寢,斷然傾勢,卻又從澗枯眼就把他個卻又從木兒,把勢盡皆看處,卻又使盡盡盡盡鈀鐵棒把勢盡盡拔下起,又不見了一下,就變做個一股之內(nèi)卻又只見了:“小的們!果然是甚的們!” 三藏認得他?行者道:“他!” 行者道:“也不怕,看得明白,見駕祥云,到后門邊,見了,就趕到山門前枕了手,徑至山門,見了一把摸觀看。三藏忽抬頭看時,慌得只見那僧官叫聲“仙童,你下馬來至林中,慌得孫大圣開上來接起了,右手淚落的笑道:“拿住了。你怎么就低頭看時,那里認得?” 忙忙答禮道:“我不是甚么?” 八戒道:“師父請你從何來?”八戒道:“你從何來?”八戒道:“你多目魔何在?” 八戒道:“你之命,八戒道:“我把你受用!你把我們定,把行李埋,就低頭而來。八戒道:“我有一樁這個猴子!不打緊,怎么今日卻要回,你!你認他多破了我使尸綁了我們救了,把你將下去,八戒笑道:“你!那呆子不去了,卻怎么轉(zhuǎn)要法,卻怎么不認得誰?” 八戒道:“我想?你受了!你要蒸死了!你得翻倒一鈀逼,卻怎么去,放聲大哭?!?八戒道:“我。你了,把勢!令牌俱念了一下,莫誤了,使鈀鉆入里面走時,又是開之筑倒在空中干鼓撒了!” 八戒道:“八戒道:“兄弟們起個人言,咬著牙,那木叉勒掯兵盡棍好取笑。!打,卻將下去戲他賭物景象!”那推云先鋒道:“你便就弄本事,呆子!你聽我內(nèi)中樹林皎,上前來趕我綁在空中影,倒飛下來,又依舊返隔架遮攔,直在身上,伏尸,倒丹爐念了。畢竟不知休齁,且駕祥云。你看得溫苗。卻說無事,功完大覺圣,且聽下回分解。休教威風(fēng)無二推倒兇心,修身檄安營破 第六十回黑河潮攀雨車囊施威性本性盡念無心,夜忘解懷車鉆兒,休教神狂。休教兇,炬照氣滾了性參差。至今終剛強原檄,文章來源地址http://www.zghlxwxcb.cn/news/detail-647080.html
到了這里,關(guān)于GPT2-Chinese 文本生成,訓(xùn)練AI寫小說,AI寫小說2的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!