??本章內(nèi)容屬于數(shù)據(jù)處理階段,將分別介紹任務(wù)加載器task
和預(yù)處理器processor
。
[1] 數(shù)據(jù)集
??在深入探討數(shù)據(jù)處理的具體步驟之前,讓我們先了解一下我們將要使用的數(shù)據(jù)集的形式。
??本項(xiàng)目采用的是七絕數(shù)據(jù)集,總計(jì)83072條古詩,其形式如下:
偈頌一百二十三首 其二○:布袋頭開天地大,翠巖眉毛在不在。一聲鴻鴈海門秋,行人已在青山外。
偈頌一百二十三首 其二八:釋迦老子弄精魂,死了依前似不曾。一樹落花春過后,遠(yuǎn)山無限碧層層。
偈頌一百二十三首 其三○:一夜江風(fēng)攪玉塵,孤峰不白轉(zhuǎn)精神。從空放下從空看,徹骨寒來有幾人。
...
??實(shí)際上我們只需要古詩的正文部分:
布袋頭開天地大,翠巖眉毛在不在。一聲鴻鴈海門秋,行人已在青山外。
釋迦老子弄精魂,死了依前似不曾。一樹落花春過后,遠(yuǎn)山無限碧層層。
一夜江風(fēng)攪玉塵,孤峰不白轉(zhuǎn)精神。從空放下從空看,徹骨寒來有幾人。
...
??除了七絕數(shù)據(jù)集之外,我們的項(xiàng)目還包括其他類型的古詩數(shù)據(jù)集,如七律、五絕和五律。雖然在本項(xiàng)目的實(shí)戰(zhàn)應(yīng)用中我們并未使用這些數(shù)據(jù)集,但它們對于對不同古詩形式感興趣的學(xué)習(xí)者來說是極好的實(shí)踐資源。如果你有興趣探索更多古詩風(fēng)格,不妨嘗試一下這些數(shù)據(jù)集。
??需要注意的一點(diǎn)是,這些數(shù)據(jù)集都是以繁體字呈現(xiàn)的。因此,在處理這些數(shù)據(jù)之前,需要將繁體字轉(zhuǎn)換為簡體字。為此,我們可以利用opencc
庫,這是一個(gè)專門用于中文繁簡轉(zhuǎn)換的工具。使用opencc
,你可以輕松地完成繁簡之間的轉(zhuǎn)換,確保數(shù)據(jù)適合模型訓(xùn)練和文本生成。
??更多關(guān)于opencc
庫的信息和使用文檔,可以參考其在GitHub上的官方頁面:https://github.com/BYVoid/OpenCC。
[2] 任務(wù)加載器task
??在我們詳細(xì)了解了數(shù)據(jù)集的格式之后,接下來我們將探討項(xiàng)目中非常關(guān)鍵的組件之一:任務(wù)加載器task
。
??那么,什么是任務(wù)加載器task
呢?簡單來說,這是我自定義的一個(gè)數(shù)據(jù)加載類Task
,它的主要職責(zé)是從各種格式的數(shù)據(jù)文件(如txt、csv、json)中加載數(shù)據(jù)??紤]到不同數(shù)據(jù)集的格式可能有所不同,Task
類還負(fù)責(zé)提取文件中的指定部分?jǐn)?shù)據(jù)(例如,僅從csv文件的某些列中提取數(shù)據(jù)),并進(jìn)行后續(xù)的文本清洗工作。
??你可能會(huì)問,為什么我們要在項(xiàng)目中專門編寫任務(wù)加載器task
呢?其主要原因是為了增強(qiáng)項(xiàng)目代碼的可擴(kuò)展性,使得我們能夠在不同任務(wù)和不同數(shù)據(jù)集之間靈活切換。這種設(shè)計(jì)理念在實(shí)際應(yīng)用中極為重要。
當(dāng)然,當(dāng)讀者親自查看實(shí)戰(zhàn)項(xiàng)目的代碼后,就會(huì)直觀感受到規(guī)范和高效代碼的優(yōu)勢。
??下面,讓我們來看一下實(shí)戰(zhàn)項(xiàng)目中針對七絕古詩數(shù)據(jù)集定義的任務(wù)加載器QiJueTask
的偽代碼:
class QiJueTask(DataTask):
"""Processor for the Example data set."""
def get_train_examples(self, data_path):
return self._create_examples(data_path, "train")
def get_dev_examples(self, data_path):
return []
def get_test_examples(self, data_path):
return []
def get_single_examples(self, text):
# 定制id
guid = "%s-%s" % ('test', 0)
example = InputExample(guid=guid, text=text)
return [example]
def get_labels(self):
return []
def check_text(self, text):
"""檢查預(yù)處理后文本"""
if any(char in text for char in ['_', '(', '(', '《', '[', '□', 'C', ' ̄', 'w', 'p', '{', '}']):
# logging.info(f'文本【{text}】中包含特殊字符,請修改預(yù)處理邏輯')
return False
if len(text) != 32:
logging.info(f'文本【{text}】長度不為32,請修改預(yù)處理邏輯')
return False
return True
def preprocess_text(self, text):
"""文本預(yù)處理"""
text = text.replace(' ', '')
return text
def _create_examples(self, path: str, set_type: str) -> List[InputExample]:
logging.info(f"加載{set_type}數(shù)據(jù)集中...")
examples = []
with open(path, "r", encoding='utf-8', ) as f:
for i, line in tqdm(enumerate(f.readlines())):
try:
title, content = line.strip().split(':')
content = self.preprocess_text(content)
if self.check_text(content):
# 定制id
guid = "%s-%s" % (set_type, i)
example = InputExample(guid=guid, text=content)
examples.append(example)
except ValueError as e:
pass
return examples
??在我們的代碼中,我們定義了一個(gè)名為QiJueTask
的類,這個(gè)類的核心作用是處理不同類型的數(shù)據(jù)集,比如訓(xùn)練集、驗(yàn)證集和測試集,以及對單個(gè)文本樣本的特定處理。這個(gè)類包含總計(jì)8個(gè)方法,下面是對這些方法的簡要介紹:
??· get_xxx_examples(self, data_path)
: 這些方法負(fù)責(zé)加載不同類型的訓(xùn)練數(shù)據(jù)集。它們通過調(diào)用內(nèi)部方法_create_examples
來完成這一任務(wù),并傳遞數(shù)據(jù)路徑和特定的標(biāo)記(如"train"
、"dev"
、"test"
)來區(qū)分訓(xùn)練、驗(yàn)證和測試數(shù)據(jù)。
??· get_single_examples(self, text)
: 這個(gè)方法用于為單個(gè)文本樣本創(chuàng)建InputExample
對象。這在進(jìn)行單個(gè)古詩文本的預(yù)測時(shí)顯得非常重要。
??注意:InputExample類是一個(gè)用于存放文本數(shù)據(jù)的容器,我將在后面詳細(xì)介紹。
??· get_labels(self)
: 這個(gè)方法返回一個(gè)空列表,因?yàn)樵谖覀兊娜蝿?wù)中,標(biāo)簽是從輸入文本中直接得到,而不是預(yù)先定義的。
??· check_text(self, text)
: 這個(gè)方法用于檢查經(jīng)過預(yù)處理的文本。雖然我們對數(shù)據(jù)進(jìn)行了清洗,但有時(shí)仍可能存在一些遺漏,因此這個(gè)方法提供了一個(gè)機(jī)會(huì)來進(jìn)行額外的過濾和檢查。
??· preprocess_text(self, text)
: 這是文本預(yù)處理的方法,當(dāng)前的主要操作是去除文本中的空格。
??· _create_examples(self, path: str, set_type: str)
: 這是一個(gè)內(nèi)部方法,用于從數(shù)據(jù)文件中讀取數(shù)據(jù),并將其轉(zhuǎn)換為InputExample
對象的列表。它接受數(shù)據(jù)路徑和數(shù)據(jù)集的類型(訓(xùn)練、驗(yàn)證或測試)作為參數(shù)。
??總的來說,任務(wù)加載器task
的主要作用就是從數(shù)據(jù)集文件中讀取文本、清洗文本、處理文本,然后將處理后的文本存放到List[InputExample]
中。下面,我們將詳細(xì)探討InputExample
類的結(jié)構(gòu)和作用。
class InputExample(object):
"""原始樣本類"""
def __init__(self, guid, text, word_label=None, index_label=None, encode_label=None):
"""
Create a new InputExample.
:param guid: a unique textual identifier
:param text: the sequence of text
:param word_label: 單詞形式的標(biāo)簽 如['sport','tech']
:param index_label: 索引形式的標(biāo)簽 從總標(biāo)簽集里面取出word_label對應(yīng)的索引 如[2,0]
:param encode_label: 獨(dú)熱編碼形式的標(biāo)簽 根據(jù)總標(biāo)簽集 把index_label轉(zhuǎn)化成獨(dú)熱標(biāo)簽 如[0,1,1,0]
"""
self.guid = guid
self.text = text
self.word_label = word_label
self.index_label = index_label
self.encode_label = encode_label
??為了確保我們的代碼具有高度的拓展性,InputExample
類被設(shè)計(jì)得相當(dāng)靈活,其屬性豐富多樣。這種設(shè)計(jì)不僅適用于我們當(dāng)前的古詩生成任務(wù),還可以方便地應(yīng)用于其他類型的文本任務(wù),比如文本分類任務(wù),或是適配transformers庫中的模型(例如Bert、GPT)。
??那么,我們的任務(wù)加載器task
是如何實(shí)現(xiàn)在不同任務(wù)和不同數(shù)據(jù)集之間靈活切換的能力呢?
??讓我們通過一個(gè)例子來說明這一點(diǎn)。假設(shè)目前我們手頭上有四個(gè)不同格式的數(shù)據(jù)集。為了處理這些不同的數(shù)據(jù)集,我們可以為每個(gè)數(shù)據(jù)集定義一個(gè)特定的任務(wù)加載器task
。通過這種方式,當(dāng)需要切換數(shù)據(jù)集時(shí),我們只需簡單地修改數(shù)據(jù)集的名稱,就可以迅速切換到不同的數(shù)據(jù)集,如下所示:
TASKS = {
"qijue": QiJueTask,
"qilv": QiLvTask,
"wujue": WuJueTask,
"wulv": WuLvTask,
}
??之后想加載不同數(shù)據(jù)集的訓(xùn)練集,你只需要使用如下代碼:
task = TASK['your_dataset_name']
train_data = task.get_train_examples('your_dataset_path')
[3] 預(yù)處理器processor
??讓我們來探討預(yù)處理器processor
的作用。processor
的主要職責(zé)是基于數(shù)據(jù)集構(gòu)建詞典,并向外提供分詞器tokenizer
的功能。
??對于那些有使用transformers
庫經(jīng)驗(yàn)的人來說,他們會(huì)知道,在使用預(yù)訓(xùn)練模型處理文本數(shù)據(jù)之前,需要通過transformers.PreTrainedTokenizer
將文本轉(zhuǎn)換為向量形式。
??考慮到本項(xiàng)目代碼可能需要對transformers
庫中的預(yù)訓(xùn)練模型進(jìn)行拓展使用,我們決定將分詞器集成到預(yù)處理器processor
中。這樣做的好處是,如果我們選擇使用transformers
庫的預(yù)訓(xùn)練模型,就無需手動(dòng)構(gòu)建詞典和分詞器;但如果不使用這些預(yù)訓(xùn)練模型,我們則需要自己從數(shù)據(jù)集中讀取數(shù)據(jù)并構(gòu)建詞典,以便將文本轉(zhuǎn)化為向量。進(jìn)一步地,我們可能還需要對嵌入層進(jìn)行預(yù)訓(xùn)練的詞向量初始化。
‘嵌入’、‘word2vec’是NLP領(lǐng)域非?;A(chǔ)的概念。以下兩篇文章介紹的十分詳細(xì):
1、【文本分類】深入理解embedding層的模型、結(jié)構(gòu)與文本表示:介紹了嵌入的相關(guān)理論基礎(chǔ);
2、一文了解Word2vec 闡述訓(xùn)練流程:介紹了嵌入的訓(xùn)練方式。
??現(xiàn)在,我們將詳細(xì)講解預(yù)處理器processor
中詞典的構(gòu)建方式:
def _build_vocab(cls, data: List[InputExample]):
"""構(gòu)建詞表"""
if os.path.exists(cls.vocab_path):
logging.info('加載詞表中...')
with open(cls.vocab_path, 'rb') as fh:
words = pickle.load(fh)
logging.info(f'詞表大小={len(words)}')
return words
logging.info('構(gòu)建詞表中...')
texts = []
for example in tqdm(data):
texts.append(cls.start_token + example.text + cls.end_token)
all_words = [word for text in texts for word in text]
counter = collections.Counter(all_words)
words = sorted(counter.keys(), key=lambda x: counter[x], reverse=True)
# 空格作為生詞字符
words.append(cls.unk_token)
with open(cls.vocab_path, 'wb') as fh:
pickle.dump(words, fh)
logging.info(f'詞表大小={len(words)} 內(nèi)容={words}')
return words
??在我們的項(xiàng)目中,預(yù)處理器processor
在構(gòu)建詞匯表時(shí)會(huì)遵循一系列明確的步驟。首先,它會(huì)檢查指定的詞匯表文件路徑是否存在。如果該路徑上已有詞匯表文件,預(yù)處理器將直接從中加載這個(gè)詞匯表。這對于加速處理流程非常有幫助,尤其是在處理大型數(shù)據(jù)集時(shí)。
??如果詞匯表文件不存在,預(yù)處理器則會(huì)開始從頭構(gòu)建一個(gè)新的詞匯表。這個(gè)過程涉及處理傳入的InputExample
對象列表,從中提取所有文本內(nèi)容。在構(gòu)建詞匯表的過程中,預(yù)處理器會(huì)在每個(gè)文本樣本的前后分別添加開始和結(jié)束標(biāo)記,這有助于模型理解文本的邊界。接著,它會(huì)統(tǒng)計(jì)并記錄每個(gè)唯一詞匯的出現(xiàn)頻率,并根據(jù)這些頻率以降序的方式對詞匯進(jìn)行排列。
??完成這些步驟后,最終得到的詞典就是一個(gè)包含所有不同詞匯的字符串列表。這個(gè)詞匯表是后續(xù)文本處理和模型訓(xùn)練的基礎(chǔ),它確保了每個(gè)文本樣本都能以一致和標(biāo)準(zhǔn)的方式被模型理解和處理。如下:
??其次就是預(yù)處理器processor
向外提供分詞器:
def tokenizer(cls, text, add_end=True):
if add_end:
# 加上前綴字符
text = cls.start_token + text + cls.end_token
if len(text) < cls.max_seq_len:
text += cls.unk_token * (cls.max_seq_len - len(text))
elif len(text) > cls.max_seq_len:
text = text[:cls.max_seq_len - 1] + cls.end_token
else:
text = cls.start_token + text
if len(text) < cls.max_seq_len:
text += cls.unk_token * (cls.max_seq_len - len(text))
elif len(text) > cls.max_seq_len:
text = text[:cls.max_seq_len]
# 轉(zhuǎn)id
ids = [cls.vocab.index(word) for word in text]
return ids
??在我們的預(yù)處理器processor
中,有一個(gè)特定的函數(shù)負(fù)責(zé)將文本字符串轉(zhuǎn)換為模型能夠理解的格式。這個(gè)函數(shù)接收兩個(gè)參數(shù):一個(gè)文本字符串和一個(gè)布爾值add_end
。add_end
參數(shù)用于指示是否在文本的末尾添加一個(gè)結(jié)束標(biāo)記。
??在處理文本時(shí),函數(shù)首先會(huì)在文本的開頭添加一個(gè)開始標(biāo)記。如果add_end
被設(shè)為True
,那么在文本的末尾也會(huì)添加一個(gè)結(jié)束標(biāo)記。這一步是為了讓模型能夠更清楚地識別文本的開始和結(jié)束。隨后,根據(jù)設(shè)定的最大序列長度max_seq_len
,文本可能會(huì)被截?cái)嗷蛘咛畛湮粗址麡?biāo)記,以確保其長度與max_seq_len
一致。最后,文本中的每個(gè)字符都會(huì)被轉(zhuǎn)換成對應(yīng)的索引值,這些索引值代表了字符在詞匯表vocab
列表中的位置。
??設(shè)置add_end
參數(shù)的目的在于,當(dāng)進(jìn)行文本生成時(shí),我們可以選擇不在文本后面添加結(jié)束字符。這是因?yàn)槿绻谋灸┪蔡砑恿私Y(jié)束字符,模型在預(yù)測下一個(gè)輸出時(shí)有較大可能會(huì)生成結(jié)束字符,這可能不利于生成連續(xù)和流暢的文本內(nèi)容。
[4] 提取word2vec
??在我們自定義的模型中,嵌入層(embedding layer)起著至關(guān)重要的作用。這個(gè)層的參數(shù)可以通過兩種方式初始化:一種是隨機(jī)初始化,另一種則是使用已有的官方預(yù)訓(xùn)練詞向量,這后者通常被稱作微調(diào)(Fine-tuning)。
self.embedding = nn.Embedding.from_pretrained(embedding_pretrained, freeze=False)
??舉個(gè)例子,假設(shè)我們的輸入中有一個(gè)字‘天’。如果我們選擇隨機(jī)初始化(比如全為0),那么在嵌入層中,‘天’對應(yīng)的表征向量就會(huì)是一個(gè)全零向量[0,0,...,0]
。這種情況下,項(xiàng)目初期所有詞的表征向量都將是相同的,這顯然不利于模型的有效學(xué)習(xí)。
??為了解決這個(gè)問題,有研究者基于大規(guī)模語料庫進(jìn)行無監(jiān)督訓(xùn)練,以獲取各種字、詞的表征向量。這些表征向量最終形成了一組稱為word2vec的向量庫。
??然而,在中文處理中,盡管常用字可能只有幾千或幾萬個(gè),但通常我們會(huì)在詞級別而非字級別進(jìn)行分詞,這就意味著常用的詞組合非常龐大。因此,第三方提供的word2vec通常包含幾十萬個(gè)不同的字和詞向量,其大小通常達(dá)到1GB甚至更大。
??在本項(xiàng)目中,由于我們只使用了大約7千個(gè)詞,所以我們需要從這些龐大的word2vec庫中提取出僅與我們詞典中存在的字詞相對應(yīng)的向量。下面是實(shí)現(xiàn)這一目的的代碼示例:
# ## 加載預(yù)訓(xùn)練詞向量
def get_embed(file):
print('加載詞向量中...')
model = KeyedVectors.load_word2vec_format(file, binary=False)
print('加載加載文本構(gòu)建的詞匯表中...')
vocabulary = pickle.load(open('./dataset/qijue/qijue_vocab.pkl', 'rb'))
# 構(gòu)建詞匯-向量字典
ebed = []
for word in vocabulary:
if word in model.key_to_index.keys():
ebed.append(model[word])
else:
ebed.append(np.asarray([0 for i in range(0, 300)], dtype='float32'))
# 儲(chǔ)存詞匯-向量字典
np.savez(r'./dataset/qijue/sg.qijue.300d.npz',embeddings=np.asarray(ebed, dtype='float32'))
??第三方提供的word2vec的下載地址可以參考:https://github.com/Embedding/Chinese-Word-Vectors/blob/master/README_zh.md。文章來源:http://www.zghlxwxcb.cn/news/detail-788406.html
[5] 進(jìn)行下一篇實(shí)戰(zhàn)
??【古詩生成AI實(shí)戰(zhàn)】之四——模型包裝器與模型的訓(xùn)練文章來源地址http://www.zghlxwxcb.cn/news/detail-788406.html
到了這里,關(guān)于【古詩生成AI實(shí)戰(zhàn)】之三——任務(wù)加載器與預(yù)處理器的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!