ScratchLLMStepByStep:訓練自己的Tokenizer

1. 引言

分詞器是每個大語言模型必不可少的組件,但每個大語言模型的分詞器幾乎都不相同。如果要訓練自己的分詞器,可以使用huggingface的tokenizers框架,tokenizers包含以下主要組件:

  1. Tokenizer: 分詞器的核心組件,定義了分詞的整個流程,包括標準化、預分詞、模型分詞、后處理等
  2. Normalizers:可選,負責將文本標準化,包括unicode歸一化、大寫轉小寫、去重音等操作
  3. Pre-tokenizers:負責將文本分割成更小的片段(如單詞等),為模型分詞做準備。常見的預分詞器有按空格分詞(Whitespace)、正則表達式分詞(Regex)等
  4. Models:是實際的分詞算法,負責將文本片段轉換為子詞,常見的有BPE、WordPiece、Unigram等。
  5. Post-Processors:負責對分詞結果進行后處理,如添加特殊標記(CLS、SEP)。
  6. Decoders:負責將分詞結果轉換回原始文本,常見的解碼器有 ByteLevel、WordPiece 等。
  7. Trainers:用于訓練分詞模型,不同的模型對應不同的訓練器,如 BpeTrainer、WordPieceTrainer、UnigramTrainer 等。

在開始之前,先導入對應的包。

import json
import re
import os
from tokenizers import (decoders,models,normalizers,pre_tokenizers,processors,trainers,Tokenizer,
)

2. 加載語料庫

我們準備好的語料庫是一個jsonl文件,大概有736MB,每一行是一條json格式的文本數據,既有中文,也有英文。

!ls -n /data2/minigpt/dataset/tokenize/tokenizer_train.jsonl
-rw-rw-r-- 1  736729803 Nov  4 22:04 /data2/minigpt/dataset/tokenize/tokenizer_train.jsonl

在這里插入圖片描述
在這里插入圖片描述

定義一個函數用于從JSONL文件中讀取文本數據,考慮到語料庫會比較大,所以采用yield生成器來延遲到訪問時再加載數據。

def read_texts_from_jsonl(file_path):with open(file_path, 'r', encoding='utf-8') as f:for i, line in enumerate(f):data = json.loads(line)yield data['text']
data_path = '/data2/minigpt/dataset/tokenize/tokenizer_train.jsonl'
texts = read_texts_from_jsonl(data_path)
type(texts)
generator

可以看到,函數read_texts_from_jsonl返回的并不是真正的數據,而是一個生成器generator。可以通過next函數像訪問iterator一樣訪問迭代數據。

next(texts)
'好的。現在請你將這個文本中的所有的逗號都替換成空格。 好的,請稍等一下,現在我會將文本中的所有逗號替換為空格。處理后文本為:"這是一個句子 目的是看看是否可以正確地從這個句子中刪除關鍵詞。"。處理結果如何?'

3. 訓練過程

3.1 模型選擇

使用BPE模型來初始化Tokenizer實例。

tokenizer = Tokenizer(models.BPE())

BPE是一種基于子詞的分詞方法,例如:

  • cats -> cat + s
  • helpful -> help + ful
  • congratulation -> con + gr + at + ulation

這種基于子詞的分詞方法,相比基于完整單詞和基于單個字符有以下好處:

  1. 子詞相比于單詞(可以認為多個子詞的組合)數量要可控,這能避免詞表過大,并且能避免生僻詞帶來的未知令牌問題。
  2. 子詞相比于字符語義性更強,像單個字符f是沒有語義的,但子詞ful可以表達滿的,比較像英語里的詞根詞綴。
3.2 預分詞器選擇

為tokenizer設置預分詞器,預分詞器有以下幾類:

  • Whitespace:按空格分隔,粒度為單詞,適用于空格分隔的語言,例如英語。
  • Regex:按自定義正則表達式分隔,適用于需要自定義復雜分詞規則的場景。
  • ByteLevel:按字節分割,適用于特殊字符、非英語場景(例如中文)。

由于我們主要面向中文,所以這里采用ByteLevel的pre_tokenizer。

tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)
tokenizer.pre_tokenizer
<tokenizers.pre_tokenizers.ByteLevel at 0x7f41641266f0>

預分詞器的方法列表

dir(tokenizer.pre_tokenizer)
    [……'add_prefix_space','alphabet','custom','pre_tokenize','pre_tokenize_str','use_regex']

那pre_tokenizer具體對文本作了什么處理呢?可以通過下面兩個例子來觀察下。

  1. 測試英文文本處理。
tokenizer.pre_tokenizer.pre_tokenize_str("Pre-tokenize a :class:`~tokenizers.PyPreTokenizedString` in-place")
    [('Pre', (0, 3)),('-', (3, 4)),('tokenize', (4, 12)),('?a', (12, 14)),('?:', (14, 16)),('class', (16, 21)),(':`~', (21, 24)),('tokenizers', (24, 34)),('.', (34, 35)),('PyPreTokenizedString', (35, 55)),('`', (55, 56)),('?in', (56, 59)),('-', (59, 60)),('place', (60, 65))]

可以看到,pre_tokenizer將文本按照空格和特殊字符作了初步分詞,空格處理成了特殊字符?,并記錄了每個詞的起始和結束位置。

  1. 測試中文文本處理。
zh_sentence = "在查處虛開增值稅專用發票案件中,常常涉及進項留抵稅額和稅款損失的認定和處理。"
tokenizer.pre_tokenizer.pre_tokenize_str(zh_sentence)
    [('??¨??¥?¤?è??????¢?????¨??????¨????¥¨??ī??????', (0, 15)),('???', (15, 16)),('????????ī???è??é?1?????μ?¨?é¢?????¨????????¤±???è?¤???????¤????', (16, 37)),('???', (37, 38))]

中文基本也是按照特殊符號進行了分詞,但分詞的結果是一堆不認識的字符,這些字符是如何產生的呢?

3.3 預分詞原理探究

預分詞常常使用類似下面一樣的正則表達式先對文本進行分隔。

import regex as rePRETOKENIZE_REGEX = r"""(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}\p{P}]?\p{L}+|\p{N}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+"""
pat = re.compile(PRETOKENIZE_REGEX)
tokens = re.findall(pat, zh_sentence)
tokens
    ['在查處虛開增值稅專用發票案件中', ',', '常常涉及進項留抵稅額和稅款損失的認定和處理', '。']

其中,各部分正則表達式的作用如下:

  • (?i:'s|'t|'re|'ve|'m|'ll|'d): 匹配常見的英文縮略形式,例如:'s(is 或 has),'t(not),'re(are),'ve(have),'m(am),'ll(will),'d(would 或 had)。
  • [^\r\n\p{L}\p{N}\p{P}]?\p{L}+:匹配一個或多個 Unicode 字母\p{L},這里的unicode字母包括英文、中文、拉丁等所有語言中的字母,允許前面有一個非換行符\r\n、非字母\p{L}、非數字\p{N}和非標點\p{P}的字符,相當于是匹配空格、制表符等空白字符。
  • \p{N}:匹配任何 Unicode 數字字符。
  • ?[^\s\p{L}\p{N}]+[\r\n]*:匹配非空白、非字母、非數字的字符,允許前面有一個空格,后面跟隨換行符,相當于是匹配標點符號后面跟換行符。
  • \p{L}:匹配任何 Unicode 字母字符,包括拉丁字母、希臘字母、漢字等所有語言中的字母。
  • \p{N}:匹配任何 Unicode 數字字符,涵蓋阿拉伯數字、羅馬數字等所有形式的數字字符。
  • \p{P}:匹配任何 Unicode 標點字符,涵蓋句號、逗號、引號、括號等所有形式、所有語言中的標點符號。

對于英文以外的其它語言(例如中文),需要進行utf-8編碼,將字符編碼為字節。

utf-8編碼的目的是解決英文、中文、日文、俄文等多語言的問題,因為世界上所有語言的字符都可以用一個或多個utf-8字節的組合來表示。

tokens_utf8 = [token.encode("utf-8") for token in tokens]
tokens_utf8
[b'\xe5\x9c\xa8\xe6\x9f\xa5\xe5\xa4\x84\xe8\x99\x9a\xe5\xbc\x80\xe5\xa2\x9e\xe5\x80\xbc\xe7\xa8\x8e\xe4\xb8\x93\xe7\x94\xa8\xe5\x8f\x91\xe7\xa5\xa8\xe6\xa1\x88\xe4\xbb\xb6\xe4\xb8\xad',b'\xef\xbc\x8c',b'\xe5\xb8\xb8\xe5\xb8\xb8\xe6\xb6\x89\xe5\x8f\x8a\xe8\xbf\x9b\xe9\xa1\xb9\xe7\x95\x99\xe6\x8a\xb5\xe7\xa8\x8e\xe9\xa2\x9d\xe5\x92\x8c\xe7\xa8\x8e\xe6\xac\xbe\xe6\x8d\x9f\xe5\xa4\xb1\xe7\x9a\x84\xe8\xae\xa4\xe5\xae\x9a\xe5\x92\x8c\xe5\xa4\x84\xe7\x90\x86',b'\xe3\x80\x82']

但有個問題是:Ascii碼中是會包含回車、制表、換行等控制字符的,同樣utf-8編碼中也會有。而我們最終構造的詞表必須是可顯示的文本,所以還要做一個工作是把控制字符都轉換為可顯示字符,為此需要制作一個unicode字節編碼表,用于將單字節(256以內)都編碼為可顯示字符。

0-255范圍內的可顯示字符分為三段:

  • 從 !(ASCII 33)到 ~(ASCII 126)。
  • 從 ?(Unicode 161)到 ?(Unicode 172)。
  • 從 ?(Unicode 174)到 ?(Unicode 255)。

這三段以外的ASCII碼均無法正常顯示,需要用可顯示字符來填充替代。

def bytes_to_unicode():# 收集0-255范圍內的可顯示字符對應的數字值,ord函數用于將字符編碼為數字bs = (list(range(ord("!"), ord("~") + 1)) + list(range(ord("?"), ord("?") + 1)) + list(range(ord("?"), ord("?") + 1)))cs = bs[:]n = 0# 補充0-255范圍內不可顯示字符對應的數字,并轉換為256以上可顯示字符對應的數字值for b in range(2**8):if b not in bs:bs.append(b)cs.append(2**8 + n)n += 1# chr函數用于將數字轉換回unicode字符,并創建一個字節值到字符值的映射表。cs = [chr(n) for n in cs]return dict(zip(bs, cs))byte_encoder = bytes_to_unicode()
json.dumps(byte_encoder, ensure_ascii=False)
    '{"33": "!", "34": "\\"", "35": "#", "36": "$", "37": "%", "38": "&", "39": "\'", "40": "(", "41": ")", "42": "*", "43": "+", "44": ",", "45": "-", "46": ".", "47": "/", "48": "0", "49": "1", "50": "2", "51": "3", "52": "4", "53": "5", "54": "6", "55": "7", "56": "8", "57": "9", "58": ":", "59": ";", "60": "<", "61": "=", "62": ">", "63": "?", "64": "@", "65": "A", "66": "B", "67": "C", "68": "D", "69": "E", "70": "F", "71": "G", "72": "H", "73": "I", "74": "J", "75": "K", "76": "L", "77": "M", "78": "N", "79": "O", "80": "P", "81": "Q", "82": "R", "83": "S", "84": "T", "85": "U", "86": "V", "87": "W", "88": "X", "89": "Y", "90": "Z", "91": "[", "92": "\\\\", "93": "]", "94": "^", "95": "_", "96": "`", "97": "a", "98": "b", "99": "c", "100": "d", "101": "e", "102": "f", "103": "g", "104": "h", "105": "i", "106": "j", "107": "k", "108": "l", "109": "m", "110": "n", "111": "o", "112": "p", "113": "q", "114": "r", "115": "s", "116": "t", "117": "u", "118": "v", "119": "w", "120": "x", "121": "y", "122": "z", "123": "{", "124": "|", "125": "}", "126": "~", "161": "?", "162": "¢", "163": "£", "164": "¤", "165": "¥", "166": "|", "167": "§", "168": "¨", "169": "?", "170": "a", "171": "?", "172": "?", "174": "?", "175": "ˉ", "176": "°", "177": "±", "178": "2", "179": "3", "180": "′", "181": "μ", "182": "?", "183": "·", "184": "?", "185": "1", "186": "o", "187": "?", "188": "?", "189": "?", "190": "?", "191": "?", "192": "à", "193": "á", "194": "?", "195": "?", "196": "?", "197": "?", "198": "?", "199": "?", "200": "è", "201": "é", "202": "ê", "203": "?", "204": "ì", "205": "í", "206": "?", "207": "?", "208": "D", "209": "?", "210": "ò", "211": "ó", "212": "?", "213": "?", "214": "?", "215": "×", "216": "?", "217": "ù", "218": "ú", "219": "?", "220": "ü", "221": "Y", "222": "T", "223": "?", "224": "à", "225": "á", "226": "a", "227": "?", "228": "?", "229": "?", "230": "?", "231": "?", "232": "è", "233": "é", "234": "ê", "235": "?", "236": "ì", "237": "í", "238": "?", "239": "?", "240": "e", "241": "?", "242": "ò", "243": "ó", "244": "?", "245": "?", "246": "?", "247": "÷", "248": "?", "249": "ù", "250": "ú", "251": "?", "252": "ü", "253": "y", "254": "t", "255": "?", "0": "ā", "1": "ā", "2": "?", "3": "?", "4": "?", "5": "?", "6": "?", "7": "?", "8": "?", "9": "?", "10": "?", "11": "?", "12": "?", "13": "?", "14": "?", "15": "?", "16": "?", "17": "?", "18": "ē", "19": "ē", "20": "?", "21": "?", "22": "?", "23": "?", "24": "?", "25": "?", "26": "ě", "27": "ě", "28": "?", "29": "?", "30": "?", "31": "?", "32": "?", "127": "?", "128": "?", "129": "?", "130": "?", "131": "?", "132": "?", "133": "?", "134": "?", "135": "?", "136": "ī", "137": "ī", "138": "?", "139": "?", "140": "?", "141": "?", "142": "?", "143": "?", "144": "?", "145": "?", "146": "?", "147": "?", "148": "?", "149": "?", "150": "?", "151": "?", "152": "?", "153": "?", "154": "?", "155": "?", "156": "?", "157": "?", "158": "?", "159": "?", "160": "?", "173": "?"}'

這樣,每個字節值都從 Unicode 表的開頭獲得一個分配給它的可見字符。這一點非常重要,因為每個utf-8字符都是由一到多個字節組成的,將這個長度為256的編碼表中的字節進行組合,理論上就能對世界上所有語言中的字符進行編碼,并且還不會出現未知標記。

使用這個unicode字節編碼表將前面utf-8編碼后的文本序列進行ByteLevel級的編碼。

tokens_unicode = ["".join(byte_encoder[b] for b in token) for token in tokens_utf8]
tokens_unicode
['??¨??¥?¤?è??????¢?????¨??????¨????¥¨??ī??????','???','????????ī???è??é?1?????μ?¨?é¢?????¨????????¤±???è?¤???????¤????','???']

可以看到,結果與使用pre_tokenizer預分詞的結果完全相同。

3.4 構建訓練器

BPE訓練器中需要指定幾個參數:

  • vocab_size:訓練后詞表中的詞條數量,BPE是一個從短詞到長詞的組合過程,達到詞表大小后就會停止訓練。
  • special_tokens:特殊token,和語言模型的特殊token相同,例如開始、結束、填充標記。
  • initial_alphabet:初始字符表,使用上面長度為256的unicode字節編碼表作為初始字符表。

通過pre_tokenizers.ByteLevel.alphabet()可以獲得初始字符編碼表。

json.dumps(pre_tokenizers.ByteLevel.alphabet(), ensure_ascii=False)
'["?", "\\\\", "?", "v", "?", "?", "?", "°", "?", "?", "?", "?", "]", "Q", "?", "G", "?", "?", "é", "H", "9", ")", "×", "í", "ó", "o", "£", "~", "ā", "s", "?", "2", "Y", "í", "a", "·", "?", "y", "?", "2", "4", "ù", "?", "?", "?", "ī", "z", "K", "?", "N", "?", "1", "n", "b", "ó", "?", "?", "V", "?", "6", "?", "?", "O", "?", "j", "h", "?", "?", "¨", "ˉ", "?", "I", "0", "?", "=", "?", "?", "?", "?", "?", "ê", "B", "a", "W", "_", "S", "?", "?", "q", "?", "?", "ò", "?", "?", "L", "?", "U", "#", "?", "?", "?", "′", "?", "M", "&", "D", "¤", "?", "?", "Y", "R", "e", "?", "?", ">", "?", "?", "d", "é", "à", "?", "<", "?", "1", "?", "/", "?", "?", "X", "ê", "u", "?", "m", "w", "ì", "¢", "?", "C", "t", "?", "ì", "ě", "?", "l", "ē", "?", "?", "3", "÷", "{", "$", "y", "?", "?", "?", "?", "?", "?", "?", "a", "T", ";", "?", "ú", "f", "§", "Z", "+", "\'", "?", "A", "?", "?", "?", "?", "c", "%", "ē", "?", "ī", "?", "?", "(", "?", "?", "?", "^", "P", "±", "x", ",", "i", "?", "?", "-", "3", "?", "*", "?", "ú", "?", "?", "?", "5", "ò", "?", "?", "t", "J", "?", ":", "ü", "¥", "`", "è", "è", "?", "?", "}", "!", "r", "T", "g", "\\"", "[", "à", "ā", "?", "7", "?", "?", "F", "?", "á", "á", "D", ".", "@", "E", "?", "?", "e", "?", "ù", "?", "?", "?", "|", "ě", "p", "ü", "8", "|", "k", "o", "μ"]'

定義特殊token,分別為填充、開始、結束標記。

special_tokens = ["<|endoftext|>", "<|im_start|>", "<|im_end|>"]

構建訓練器,詞條數量設置為32000。

trainer = trainers.BpeTrainer(vocab_size=32000,special_tokens=special_tokens,  # 確保這三個token被包含show_progress=True,initial_alphabet=pre_tokenizers.ByteLevel.alphabet()
)

使用上面的texts生成器作為語料庫,使用trainer開始訓練分詞器。

tokenizer.train_from_iterator(texts, trainer=trainer)

注:這個訓練過程的用時長短與文本數據大小有關,我們前面加載的文本數據有700多MB, 大概需要十幾分鐘。

3.5 保存訓練結果

在保存結果之前,需要先設置相匹配的解碼器,否則ASCII以外的字符可能無法正常解碼。

上面編碼階段使用了ByteLevel的預分詞器,相對應的解碼階段也需要使用ByteLevel,表示將token id轉換為token后,還需要進行一次unicode字節級別的解碼,才能正常顯示中文等多語言字符。

tokenizer.decoder = decoders.ByteLevel()

將訓練的分詞器保存到指定目錄。

tokenizer_dir = "/data2/minigpt/models/tokenizer_v3"
os.makedirs(tokenizer_dir, exist_ok=True)
tokenizer.save(os.path.join(tokenizer_dir, "tokenizer.json"))
tokenizer.model.save(tokenizer_dir)
['/data2/minigpt/models/tokenizer_v3/vocab.json','/data2/minigpt/models/tokenizer_v3/merges.txt']

還需要一個分詞器配置文件,包括模型類型、是否使用小寫字母等。

config = {"add_bos_token": False,"add_eos_token": False,"add_prefix_space": True,"added_tokens_decoder": {"0": {"content": "<|endoftext|>","lstrip": False,"normalized": False,"rstrip": False,"single_word": False,"special": True},"1": {"content": "<|im_start|>","lstrip": False,"normalized": False,"rstrip": False,"single_word": False,"special": True},"2": {"content": "<|im_end|>","lstrip": False,"normalized": False,"rstrip": False,"single_word": False,"special": True}},"additional_special_tokens": [],"bos_token": "<|im_start|>","clean_up_tokenization_spaces": False,"eos_token": "<|im_end|>","legacy": True,"model_max_length": 1000000000000000019884624838656,"pad_token": None,"sp_model_kwargs": {},"spaces_between_special_tokens": False,"tokenizer_class": "PreTrainedTokenizerFast","unk_token": "<|endoftext|>","use_default_system_prompt": False,"chat_template": "{% if messages[0]['role'] == 'system' %}{% set system_message = messages[0]['content'] %}{% endif %}{% if system_message is defined %}{{ system_message }}{% endif %}{% for message in messages %}{% set content = message['content'] %}{% if message['role'] == 'user' %}{{ '<|im_start|>user\\n' + content + '<|im_end|>\\n<|im_start|>assistant\\n' }}{% elif message['role'] == 'assistant' %}{{ content + '<|im_end|>' + '\\n' }}{% endif %}{% endfor %}"
}

保存分詞器配置

with open(os.path.join(tokenizer_dir, "tokenizer_config.json"), "w", encoding="utf-8") as config_file:json.dump(config, config_file, ensure_ascii=False, indent=4)print("Tokenizer training completed and saved.")
Tokenizer training completed and saved.

查看磁盤上的詞表文件。

!ls -l /data2/minigpt/models/tokenizer_v3
total 2548
-rw-rw-r-- 1 golfxiao golfxiao  407951 Oct 10 21:45 merges.txt
-rw-rw-r-- 1 golfxiao golfxiao    1686 Oct 10 21:45 tokenizer_config.json
-rw-rw-r-- 1 golfxiao golfxiao 1572840 Oct 10 21:45 tokenizer.json
-rw-rw-r-- 1 golfxiao golfxiao  621912 Oct 10 21:45 vocab.json
  • vocab.json:詞匯表文件,包含詞條和對應的索引。
  • merges.txt: 合并表文件,定義了子詞的合并規則。
  • tokenizer.json: 完整的分詞器文件,它包含了分詞器的所有信息,包括詞匯表、合并規則、特殊標記等。
  • tokenizer_config.json: 分詞器配置文件,包括了起始token、結束token的定義,以及提示詞模板。

4. 測試分詞器

from transformers import AutoTokenizer# 加載剛訓練的tokenizer
tokenizer_dir = "/data2/minigpt/models/tokenizer_v3"
tokenizer_trained = AutoTokenizer.from_pretrained(tokenizer_dir)
4.1 英文文本測試

先測試英文文本的分詞。

text_en = "Pre-tokenize a :class:`~tokenizers.PyPreTokenizedString` in-place"
tokenized = tokenizer_trained.tokenize(text_en)
tokenized
    ['Pre','-','token','ize','?a','?:','class',':','`','~','token','izers','.','Py','Pre','T','oken','ized','String','`','?in','-','place']

tokenize方法只負責分詞,就是將一串文本切分為token列表。我們在給模型輸入時一般需要的是token_id,這時就需要使用encode方法,同時完成token切分和文本到數字的序列化。

token_ids_en = tokenizer_trained.encode(text_en)
token_ids_en
    [19714,15,24535,1038,260,6938,9939,28,66,96,24535,11344,16,22966,19714,54,9071,1228,13863,66,295,15,2383]

對上面的token_id進行反序列化,以測試解碼功能。

tokenizer_trained.decode(token_ids_en)
'Pre-tokenize a :class:`~tokenizers.PyPreTokenizedString` in-place'

可以看到,解碼的結果與原始英文串完全相同。

4.2 中文文本測試

下面測試下中文文本的序列化和反序列化。

text_zh = "在查處虛開增值稅專用發票案件中,常常涉及進項留抵稅額和稅款損失的認定和處理。"
token_ids_zh = tokenizer_trained.encode(text_zh)
token_ids_zh
    [368,1698,1319,4304,953,30571,2147,411,646,3917,6723,413,270,6679,4743,631,1467,3692,9083,3534,2676,315,3534,1805,8576,269,1374,627,12769,286]
tokenizer_trained.decode(token_ids_zh)
    '在查處虛開增值稅專用發票案件中,常常涉及進項留抵稅額和稅款損失的認定和處理。'

我們剛訓練的分詞器在中文和英文上都能正常進行的文本的序列化和反序列化操作。

小結:本文借助huggingface提供的tokenizers框架,以一個真實的語料庫為案例,演示了分詞器訓練的過程,并最終得到了一個切實可用的分詞器。但tokenizers框架封裝的比較多,所以在訓練過程中對多語言的編碼和解碼部分作了內部實現的剖析和講解,如果你還對其它部分(如BPE算法)感興趣,下面的參考內容或許能為你提供進一步的幫助。

參考閱讀

  • 帶你從零認識語言模型
  • 手搓BPE算法
  • 什么是tokenizer?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/66421.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/66421.shtml
英文地址,請注明出處:http://en.pswp.cn/web/66421.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C# OpenCvSharp 部署3D人臉重建3DDFA-V3

目錄 說明 效果 模型信息 landmark.onnx net_recon.onnx net_recon_mbnet.onnx retinaface_resnet50.onnx 項目 代碼 下載 參考 C# OpenCvSharp 部署3D人臉重建3DDFA-V3 說明 地址&#xff1a;https://github.com/wang-zidu/3DDFA-V3 3DDFA_V3 uses the geometri…

從零開始學數據庫 day2 DML

從零開始學數據庫&#xff1a;DML操作詳解 在今天的數字化時代&#xff0c;數據庫的使用已經成為了各行各業的必備技能。無論你是想開發一個簡單的應用&#xff0c;還是想要管理復雜的數據&#xff0c;掌握數據庫的基本操作都是至關重要的。在這篇博客中&#xff0c;我們將專注…

Java 8 Stream API

文章目錄 Java 8 Stream API1. Stream2. Stream 的創建3. 常見的 Stream 操作3.1 中間操作3.2 終止操作 4. Stream 的并行操作 Java 8 Stream API Java 8 引入了 Stream API&#xff0c;使得對集合類&#xff08;如 List、Set 等&#xff09;的操作變得更加簡潔和直觀。Stream…

運行fastGPT 第五步 配置FastGPT和上傳知識庫 打造AI客服

運行fastGPT 第五步 配置FastGPT和上傳知識庫 打造AI客服 根據上一步的步驟&#xff0c;已經調試了ONE API的接口&#xff0c;下面&#xff0c;我們就登陸fastGPT吧 http://xxx.xxx.xxx.xxx:3000/ 這個就是你的fastGPT后臺地址&#xff0c;可以在configer文件中找到。 賬號是…

第4章 Kafka核心API——Kafka客戶端操作

Kafka客戶端操作 一. 客戶端操作1. AdminClient API 一. 客戶端操作 1. AdminClient API

【王樹森搜索引擎技術】相關性02:評價指標(AUC、正逆序比、DCG)

相關性的評價指標 Pointwise評價指標&#xff1a;Area Under the Curve&#xff08;AUC&#xff09;Pairwise評價指標&#xff1a;正逆序比&#xff08;Positive to Negative Ratio, PNR&#xff09;Listwise評價指標&#xff1a;Discounted Cumulative Gain(DCG)用AUC和PNR作…

人物一致性訓練測評數據集

1.Pulid 訓練:由1.5M張從互聯網收集的高質量人類圖像組成,圖像標題由blip2自動生成。 測試:從互聯網上收集了一個多樣化的肖像測試集,該數據集涵蓋了多種膚色、年齡和性別,共計120張圖像,我們稱之為DivID-120,作為補充資源,還使用了最近開源的測試集Unsplash-50,包含…

Android 項目依賴沖突問題:Duplicate class found in modules

問題描述與處理處理 1、問題描述 plugins {id com.android.application }android {compileSdk 34defaultConfig {applicationId "com.my.dialog"minSdk 21targetSdk 34versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.run…

計算機網絡 | 什么是公網、私網、NAT?

關注&#xff1a;CodingTechWork 引言 計算機網絡是現代信息社會的基石&#xff0c;而網絡通信的順暢性和安全性依賴于有效的IP地址管理和網絡轉換機制。在網絡中&#xff0c;IP地址起到了標識設備和進行數據傳輸的核心作用。本文將詳細討論公網IP、私網IP以及NAT轉換等網絡技…

python+django+Nacos實現配置動態更新-集中管理配置(實現mysql配置動態讀取及動態更新)

一、docker-compose.yml 部署nacos服務 version: "3" services:mysql:container_name: mysql# 5.7image: mysql:5.7environment:# mysql root用戶密碼MYSQL_ROOT_PASSWORD: rootTZ: Asia/Shanghai# 初始化數據庫(后續的初始化sql會在這個庫執行)MYSQL_DATABASE: nac…

深度學習項目--基于LSTM的火災預測研究(pytorch實現)

&#x1f368; 本文為&#x1f517;365天深度學習訓練營 中的學習記錄博客&#x1f356; 原作者&#xff1a;K同學啊 前言 LSTM模型一直是一個很經典的模型&#xff0c;這個模型當然也很復雜&#xff0c;一般需要先學習RNN、GRU模型之后再學&#xff0c;GRU、LSTM的模型講解將…

基于 WEB 開發的汽車養護系統設計與實現

標題:基于 WEB 開發的汽車養護系統設計與實現 內容:1.摘要 本文介紹了基于 WEB 開發的汽車養護系統的設計與實現。文章首先闡述了系統的背景和目的&#xff0c;即隨著汽車保有量的增加&#xff0c;汽車養護需求日益增長&#xff0c;傳統的汽車養護方式已經無法滿足人們的需求&…

GitLab集成Jira

GitLab與Jira集成的兩種方式 GitLab 提供了兩種 Jira 集成&#xff0c;即Jira議題集成和Jira開發面板集成&#xff0c;可以配置一個或者兩個都配置。 具體集成步驟可以參考官方文檔Jira 議題集成&#xff08;極狐GitLab文檔&#xff09;和Jira 開發面板集成&#xff08;極狐G…

【爬蟲】某某查cookie逆向

代碼僅供技術人員進行學習和研究使用&#xff0c;請勿將其用于非法用途或以任何方式竊取第三方數據。使用該代碼產生的所有風險均由用戶自行承擔&#xff0c;作者不對用戶因使用該代碼而造成的任何損失或損害承擔任何責任。 加密參數 加密參數主要是cookie&#xff0c;其中只有…

A5.Springboot-LLama3.2服務自動化構建(二)——Jenkins流水線構建配置初始化設置

下面我們接著上一篇文章《A4.Springboot-LLama3.2服務自動化構建(一)——構建docker鏡像配置》繼續往下分析,在自動化流水線構建過程當中的相關初始化設置和腳本編寫。 一、首先需要先安裝Jenkins 主部分請參考我前面寫的一篇文章《Jenkins持續集成與交付安裝配置》 二、…

如何設置HTTPS站點防御?

設置HTTPS站點防御涉及到多個層面的安全措施&#xff0c;包括但不限于配置Web服務器、應用安全頭信息、使用內容安全策略&#xff08;CSP&#xff09;、啟用HSTS和OCSP Stapling等。下面是一些關鍵的步驟來增強HTTPS網站的安全性&#xff1a; 1. 使用強加密協議和密鑰交換算法…

[Java]類和對象

1. 什么是類&#xff1f; 類&#xff08;Class&#xff09;是藍圖或者模板。它定義了對象的屬性和行為。 類就是一種抽象的模板&#xff0c;你可以通過它創建多個對象。類定義了對象的屬性&#xff08;變量&#xff09;和行為&#xff08;方法&#xff09;。我們可以把類理解…

win32匯編環境,窗口程序中基礎列表框的應用舉例

;運行效果 ;win32匯編環境,窗口程序中基礎列表框的應用舉例 ;比如在窗口程序中生成列表框&#xff0c;增加子項&#xff0c;刪除某項&#xff0c;取得指定項內容等 ;直接抄進RadAsm可編譯運行。重點部分加備注。 ;以下是ASM文件 ;>>>>>>>>>>>…

【機器學習實戰入門】使用LSTM機器學習預測股票價格

機器學習在股票價格預測中有重要的應用。在這個機器學習項目中&#xff0c;我們將討論如何預測股票的收益。這是一個非常復雜的任務&#xff0c;充滿了不確定性。我們將會把這個項目分成兩部分進行開發&#xff1a; 首先&#xff0c;我們將學習如何使用 LSTM 神經網絡預測股票…

【編程語言】C/C++語言常見標準和規范

C/C 是兩種功能強大且廣泛使用的編程語言。盡管它們沒有像 Java 那樣強制性的命名規則&#xff0c;但為了提高代碼的可讀性和可維護性&#xff0c;遵循一些普遍認同的編程規范和標準仍然是非常重要的。本文將探討 C/C 編程中的一些命名規范及標準&#xff0c;以幫助開發者編寫更…