Stanford CS336 assignment1 | Byte-Pair Encoding (BPE) Tokenizer

BPE

  • 一、 BPETrain
    • 1、 unicode standard and unicode encoding
    • 2、 子詞分詞(subword tokenization)
    • 3、 BPE的訓練
      • a、 Vocabulary initialization
      • b、 Pre-tokenization
      • c、 Compute BPE merges
    • 4、 train_BPE更多實現上的細節
  • 二、 BPETokenizer
    • init函數
    • from_files
    • encode
    • decode
    • encode_iterable
  • 三、 如何測試
  • 四、 github完整代碼
  • 五、 總結

一、 BPETrain

1、 unicode standard and unicode encoding

unicode標準是一個字符集,它將字符對應到一個整數(被稱為碼點 code point),unicode standard 16包含154,998個字符,涵蓋168種語言。
unicode encoding是一種編碼方式,它將unicode字符對應到一個字節序列,Unicode standard定義了三種encoding 方式分別為utf-8、utf-16、utf-32,其中utf-8目前最為常用。
三者的對比如下:

特性UTF-8UTF-16UTF-32
字符長度1-4 字節2 或 4 字節固定 4 字節
ASCII 兼容性完全兼容不兼容不兼容
存儲效率高效(尤其是英文文本)中等低效
處理復雜度處理非 ASCII 字符稍復雜需要處理代理對簡單(固定寬度)
適用場景互聯網、文件系統、數據庫操作系統、編程語言特定需求

關于這部分內容可以看一下Python中二進制文件操作,了解一下python操作二進制字節文件的內容。

2、 子詞分詞(subword tokenization)

字詞分詞是一種介于byte-level tokenization和word-level tokenization的分詞技術,兩者的對比如下:

對比維度Word-level Tokenization(詞級分詞)Byte-level Tokenization(字節級分詞)
分詞單位以“詞”為最小單位(如 “你好”, “apple”, “the”)以“字節”為最小單位(如 b’h’, b’\xe4’)
粒度最粗粒度最細粒度
詞匯量通常幾千到幾十萬個詞固定為 256 個基礎 token(0~255)
是否支持未知詞無法處理未登錄詞(OOV)完全支持所有字符(無 OOV)
是否語言相關通常需要語言特定的詞典或規則語言無關,適用于任何語言
輸入長度較短較長(每個字符可能拆成多個字節)

word-level無法解決oov問題,同時詞表長度太大,但是輸入長度短,而byte-level可以結局oov問題,同時詞表較短,但是輸入長度太長,對于現在的LLM,輸入長度過長會帶來更大計算量,同時會有長距離依賴問題,為了trade-off兩者,subword是一種很好的解決辦法。

subword-level的思想很簡單,就是將byte sequence中出現頻次高的內容作為一個詞表中新的entry。

關于如何選擇subword加入詞表,可以使用1994年Gage提出的BPE算法(Byte pair encoding)

3、 BPE的訓練

bpe的訓練過程主要分為三步:

  1. 詞表初始化(Vocabulary initialization)
  2. 預分詞(Pre-tokenization)
  3. 合并(Compute BPE merges)

a、 Vocabulary initialization

詞表初始化(Vocabulary initialization):由于訓練的是byte-level的BPE初始詞表的大小應該是256。同時需要將,文本中會有一些special_tokens,這些special_tokens是不參與bpe的訓練的,直接將這些內容加入到初始詞表中。

## initialize vocabulary step
def initialize_vocabulary(special_tokens: list[str]
) -> dict[int, bytes]:vocabulary = {}vocabulary.update({i: special_tokens[i].encode("utf-8") for i in range(0, len(special_tokens))})vocabulary.update({i + len(vocabulary): bytes([i]) for i in range(256)})  return vocabulary

b、 Pre-tokenization

預分詞(Pre-tokenization):如果直接開始進行merge,那么每次都需要遍歷整個數據集的文本進行merge,這是一項耗時的操作,同時可能會導致dog!dog.這兩個詞僅僅因為標點符號不一樣就成為兩個完全不同的subword被分配不同的id,盡管這兩個詞在語義上高度相似,它們也被認為是兩個完全不同的詞。pre-tokenization就是為了解決上面的問題,pre-tokenization可以被看作是一種粗粒度的tokenization,例如text是一個pre-token,同時text在全文中出現了10詞,就不再需要看(t,e)pair在全文中出現了多少此,而是直接給(t,e)pair增加10。

這里我實現的pre_tokenization是直接返回的dict[tuple[bytes], int],也就是返回的每個tuple[bytes]出現的次數。
舉個例子這里輸入為"Hello word<|endoftext|>Hello "special_tokens=["<|endoftext|>"]得到的結果就是

{(b'H', b'e', b'l', b'l', b'o'): 2,(b'w', b'o', b'r', b'l', b'd'): 1
}

這里的pre_tokenization會被拋棄,同時也沒有保留原來的順序,這樣實現其實不好,對于后面使用tokenizer進行encode是不方便的,還需要重新實現,其實可以直接保留special_tokens, 同時保留原來的順序,使用list可以滿足這樣的要求,對于每個tuple[bytes]出現的頻率統計可以放到下一步merge中去做。

## pre_tokenization step
def pre_tokenization(input: str, special_tokens: list[str]
) -> dict[tuple[bytes], int]:escaped_tokens = [re.escape(tok) for tok in special_tokens]split_pattern = "|".join(escaped_tokens) # 按special_tokens分割inputmatch_pattern = re.compile(r"""'(?:[sdmt]|ll|ve|re)| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+""") # 分割后匹配除去special_tokens中的wordsplit_texts = re.split(split_pattern, input) # 得到分割后的文本,格式為listpre_tokens = {}for split_text in split_texts:for word in match_pattern.finditer(split_text):word_str = word.group(0).encode("utf-8")bytes_word_tuple = tuple(bytes([word]) for word in word_str)pre_tokens[bytes_word_tuple] = pre_tokens.get(bytes_word_tuple, 0) + 1 return pre_tokens

c、 Compute BPE merges

合并(Compute BPE merges):迭代合并時不考慮夸pre-token的情況,同時當有多個byte pair頻率相同時,選擇字典序更大的byte pair進行合并。

出了初始詞表中的token、和BPE算法合并產生的token,還有一些special token,這些token有的用來表示元數據具有一些特殊的作用,也應該被加入到詞表中。

get_pair_freq:由于pre_tokenization步驟得到的結果是如下格式:

{(b'H', b'e', b'l', b'l', b'o'): 2,(b'w', b'o', b'r', b'l', b'd'): 1
}

而merge需要相鄰bytes pair出現頻率最高的那一對然后合并,所以get_pair_freq的作用就是統計相鄰bytes pair的頻率:

def get_pair_freq(word_counts: Counter[tuple[bytes]]
) -> Counter[tuple[bytes]]:freq_pair: Counter[tuple[bytes]] = {}for word, cnt in word_counts.items():for i in range(len(word) - 1):pair = (word[i], word[i + 1])freq_pair[pair] = freq_pair.get(pair, 0) + cntreturn freq_pair

find_pair是為了獲得出現頻率最高的pair,當有多個這樣的pair時會返回字典序最大的。

## merge_tools 
def find_pair(freq_pair: Counter[tuple[bytes]]
) -> tuple[bytes]:max_value = max(freq_pair.values())max_pair = max([k for k, v in freq_pair.items() if v == max_value])return max_pair

對于pre_tokenization得到的數據是一個list[tuple[bytes]],分開處理每一個tuple[bytes]

{(b'H', b'e', b'l', b'l', b'o'): 2,(b'w', b'o', b'r', b'l', b'd'): 1
}

也就是說處理對于上面的數據分別處理

(b'H', b'e', b'l', b'l', b'o')
(b'w', b'o', b'r', b'l', b'd')

get_merged_word這個函數就是對每一個tuple[bytes]進行merge,然后返回merge后得到的新tuple[bytes]

## merge_tools
def get_merged_word(word: tuple[bytes], cmp_pair: tuple[bytes]
) -> tuple[bytes]:new_word = [] # 存儲merge后的wordlength, cur = len(word), 0while cur < length:if cur + 1 < length: # 當還能組成的pair時if (word[cur], word[cur + 1]) == cmp_pair: # 找到了可以merge的對象new_word.append(word[cur] + word[cur + 1])cur += 2else:new_word.append(word[cur])cur += 1    else:new_word.append(word[cur])cur += 1return tuple(new_word)

4、 train_BPE更多實現上的細節

由于pre_token非常耗時,所以采用多進程并行處理,如何進行多進程并行處理?
首先是將數據集進行chunk,具體的chunk規則可以參考assignment1-basics/cs336_basics/pretokenization_example.py中的代碼,find_chunk_boundaries這個函數將輸入chunk成幾個完整的內容,他并不是單一嚴格按字節分割,而是會在字節后面的第一個special_token位置進行分割。分割的方式如圖所示:
請添加圖片描述
然后分別對每個分割得到chunking進行多進程并行pre-token,多進程可以使用python的內置模塊multiprocessing,如果不了解可以參考Python多進程并行multiprocess基礎。

下面是多進程訓練的代碼:merge_pre_tokens用于將得到的多個pre_tokens字典合并為一個字典。

def merge_pre_tokens(dicts: list[Counter[tuple[bytes]]]
) -> Counter[tuple[bytes]]:merged_counter = Counter()for counter in dicts:merged_counter.update(counter)return merged_counter## 多進程進行pre_tokenization
def parallel_pre_tokenization(file_path: str, special_tokens: list[str], num_workers: int = None
) -> Counter[tuple[bytes]]:params = []with open(file_path, 'rb') as f:boundary = find_chunk_boundaries(f, num_workers, special_tokens[0].encode("utf-8")) for left, right in zip(boundary[:-1], boundary[1:]):f.seek(left)chunk = f.read(right - left).decode("utf-8", errors="ignore")params.append((chunk, special_tokens))with Pool(processes=num_workers) as pool:result_dicts = pool.starmap(pre_tokenization, params)return merge_pre_tokens(result_dicts)

最后可以優化merge的過程,由于merge的過程會每次都去遍歷pre_tokens,然后統計byte-pair的出現次數,最后找到byte-pair的最大值作為本次merge的byte-pair。這個過程需要遍歷所有的tokens,可以采用一種增量遍歷的方式。

預處理一個全局byte-pair出現的頻次表格式如下:

freq = {(b'a', b'b'): 3,(b'a', b'c'): 2,(b'a', b'd'): 10,(b'a', b'e'): 11,(b'ad', b'e'): 13
}

本次更新選中了(b'a', b'd')作為best_pair,找出來含有best_pair的token,然后對于一個滿足的wordA,先全局byte-pair中把A的所有byte-pair減去,然后加上新生成的word產生的pair。

將上面的三個步驟集成起來就得到下面的訓練函數

def train_bpe(input_path: str, vocab_size: int, special_tokens: list[str]
) -> tuple[dict[int, bytes], list[tuple[bytes, bytes]]]:## setp1 initinalize vocabularyvocabulary: dict[int, bytes] = initialize_vocabulary(special_tokens)## setp2 pre tokenization# file_path = "assignment1-basics/data/TinyStoriesV2-GPT4-train.txt"word_counts = parallel_pre_tokenization(input_path,special_tokens,16)cur_id: int = len(vocabulary)merges: list[tuple[bytes, bytes]] = []## step3 BPE mergeneed_merge_cnt: int = vocab_size - cur_idpair_freqs  = get_pair_freq(word_counts)for i in tqdm(range(need_merge_cnt)): # 迭代merge頻次最高的byte-pairif not pair_freqs:breakbest_pair = find_pair(pair_freqs)merges.append(best_pair)vocabulary[cur_id] = best_pair[0] + best_pair[1]cur_id += 1# 找出所有需要更新的wordwords_need_update = {}for word, cnt in word_counts.items():if best_pair[0] in word and best_pair[1] in word:for i in range(len(word) - 1):if (word[i], word[i + 1]) == best_pair:words_need_update[word] = cntbreak# 更新word_countsfor word, cnt in words_need_update.items():# 增量更新pair頻率表for i in range(len(word) - 1):pair = (word[i], word[i + 1])pair_freqs[pair] = pair_freqs.get(pair, 0) - cntdel word_counts[word]new_word = get_merged_word(word, best_pair)word_counts[new_word] = word_counts.get(new_word, 0) + cntfor i in range(len(new_word) - 1):pair = (new_word[i], new_word[i + 1])pair_freqs[pair] = pair_freqs.get(pair, 0) + cntreturn vocabulary, merges

二、 BPETokenizer

cs336的文檔中已經說明了BPETokenizer類中必須實現的接口。
在這里插入圖片描述

init函數

即初始化tokenizer,這里的vocab,merges,special_tokens都和上面訓練時的格式類型一致。

def __init__(self, vocab: dict[int, bytes], merges: list[tuple[bytes, bytes]], special_tokens: list[str] | None = None): self.vocab = vocabself.merges = mergesself.special_tokens = special_tokens

from_files

文檔中要求實現一個可以從路徑中加載vocab和merges的功能。這里我是仿照他給的pytest測試里assignment1-basics/tests/test_tokenizer.pyget_tokenizer_from_vocab_merges_path寫的,里面的bytes_to_unicode函數就是將256個字節都能可視化顯示,因為有很多控制字符space什么的是沒法,顯示的,這里是因為它測試讀取的vocab、merges保存的格式是這樣的所以讀取的時候還要將它保存的格式轉換為0~255的bytes。測試用的vocab、merges在assignment1-basics/tests/fixtures/gpt2_vocab.jsonassignment1-basics/tests/fixtures/gpt2_merges.text

@classmethoddef from_files(cls, vocab_filepath: str, merges_filepath: str, special_tokens: list[str] | None = None) -> BPETokenizer:@lru_cachedef bytes_to_unicode() -> dict[int, str]:bs = list(range(ord("!"), ord("~") + 1)) + list(range(ord("?"), ord("?") + 1)) + list(range(ord("?"), ord("?") + 1))cs = bs[:]n = 0for b in range(2**8):if b not in bs:bs.append(b)cs.append(2**8 + n)n += 1characters = [chr(n) for n in cs]d = dict(zip(bs, characters))return ddef bytes_to_str(b: bytes) -> str:byte_to_uni = bytes_to_unicode()s = ""for bit in b:s.join(byte_to_uni[bit])return sdef str_to_bytes(s: str) -> bytes:byte_to_uni = bytes_to_unicode()byte_decoder = {v: k for k, v in byte_to_uni.items()}ans = bytearray()for c in s:ans.extend([byte_decoder[c]])return bytes(ans)# 處理vocabtry:with open(vocab_filepath, "r", encoding="utf-8") as f:vocab_ = json.load(f)except Exception as e:raise RuntimeError(f"Error loading vocabulary from {vocab_filepath}: {e}")vocab = {v: str_to_bytes(k) for k, v in vocab_.items()}# 處理mergesmerges_ = []with open(merges_filepath, 'r', encoding="utf-8") as f:for line in f:clean_line = line.strip()if clean_line and len(clean_line.split(" ")) == 2:merges_.append(tuple(clean_line.split(" ")))if special_tokens:for special_token in special_tokens:byte_encoded_special_token = special_token.encode("utf-8")if byte_encoded_special_token not in set(vocab.values()):vocab[len(vocab)] = byte_encoded_special_tokenmerges = [(str_to_bytes(str1), str_to_bytes(str2),)for str1, str2 in merges_]return cls(vocab, merges, special_tokens)

encode

當我們訓練好了一個BPEtokenzier后,就可以通過得到vocab和一個merge對輸入的文本進行tokenization。
這里encode的步驟分為三步,第一步首先進行pre-tokenization,然后進行merge,最后在詞表中查看每個詞元對應的id。

首先是pre-tokenization,訓練時的pre-tokenization是先按special_tokens進行split,然后將special_tokens丟棄,然后再按gpt2的pat模式去分割。在使用時,不能舍棄special_tokens,同時需要保留每個詞的順序。

十分需要注意并小心的corner case就是special_tokens為None的情況,當special_tokens為None時,不都對special_tokens使用sorted,同時第一步按special_tokens分割,結果應該是[text],list包裹原始文本。

def pre_tokenization(self,text: str, ) -> list[tuple[bytes]]:special_tokens = sorted(self.special_tokens, key=lambda x: -len(x)) if self.special_tokens is not None else []escaped_tokens = [re.escape(tok) for tok in special_tokens] if special_tokens else []split_pattern = "(" + "|".join(escaped_tokens) + ")"    # 按special_tokens分割inputmatch_pattern = re.compile(r"""'(?:[sdmt]|ll|ve|re)| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+""")split_texts = re.split(split_pattern, text) if len(escaped_tokens) != 0 else [text]# 得到分割后的文本,格式為listpre_tokens = []for split_text in split_texts:   if self.special_tokens != None and split_text in self.special_tokens:pre_tokens.append((split_text.encode('utf-8'),))else:for word in match_pattern.finditer(split_text):word_str = word.group(0).encode("utf-8")bytes_word_tuple = tuple(bytes([word]) for word in word_str)pre_tokens.append(bytes_word_tuple)return pre_tokens

其次是merge,這里merge要按保存的byte-pair的順序去merge,因為本身文本訓練的時候merge就是按順序的,最開始寫這個merge的時候沒有按順序debug了好久

這里merge這一步我實現了兩個函數,首先是merge函數,這個merge函數用于pre_tokenization得到的單個的tokens就是tuple[bytes]的merge,tokens的形狀類似是這樣的(b'h', b'e', b'l', b'l', b'o'),然后返回的結果的類型是tuple[bytes],對于(b'h', b'e', b'l', b'l', b'o')這個例子,返回的結果可能是(b'he', b'll', b'o')

def merge(self,pre_token: tuple[bytes],ranked_merges: dict[bytes, int]) -> tuple[bytes]:    while True:cur_min_rank = len(ranked_merges)best_pair = Nonefor i in range(len(pre_token) - 1):pair = pre_token[i] + pre_token[i + 1]rk = ranked_merges.get(pair, float('inf'))if rk < cur_min_rank:cur_min_rank = rkbest_pair = pairif best_pair is None:breaknew_token = []i = 0while i < len(pre_token):if i + 1 < len(pre_token) and pre_token[i] + pre_token[i + 1] == best_pair:new_token.append(best_pair)i += 2 else:new_token.append(pre_token[i])i += 1pre_token = new_tokenreturn pre_token

merge_pre_tokens是將pre_tokenization得到的pre_tokens列表里面的每個pre_tokens都應用上面的merge合并,得到的結果就是最終的合并后的tokens。

這里的小細節需要注意的是對于special_tokens,其不需要merge,也就是說我們在遍歷的時候遇到了(b'<|endoftext|>, )'的時候直接將他append進我們的結果列表中即可

def merge_pre_tokens(self,pre_tokens: list[tuple[bytes]],) -> list[tuple[bytes]]:merged_tokens: list[tuple[bytes]]= []special_tokens_bytes = ([tuple(special_token.encode('utf-8')) for special_token in self.special_tokens]if self.special_tokens else [])ranked_merges = {bytes1 + bytes2: idx for idx, (bytes1, bytes2) in enumerate(self.merges)}for pre_token in pre_tokens:if pre_token in special_tokens_bytes:merged_tokens.append(pre_token)else:merged_tokens.append(self.merge(pre_token, ranked_merges))return merged_tokens

最后實現文檔要求的接口encode,集成上面的功能,在vocab中查找每個bytes對應的id進行替換,然后返回id的列表即可。

def encode(self, text: str) -> list[int]:token_to_id = {token: id for id, token in self.vocab.items()}tokens = []pre_tokens = self.pre_tokenization(text)merged_tokens = self.merge_pre_tokens(pre_tokens)joined_tokens = []for word in merged_tokens:for b in word:joined_tokens.append(b)return [token_to_id.get(token, -1) for token in joined_tokens]

decode

decode函數就很簡單了,查找詞表vocab,將每個token_id還原回bytes,然后進行拼接,再按utf-8的格式decode成str即可。 errors="replace"這個參數的作用實現的是文檔里面黃色的部分,即可能decode的輸入token_ids并非是配套的encode得到的,就可能有不合法的部分。無法用unicode解碼的就用U+FFFD替換。
在這里插入圖片描述

def decode(self, ids: list[int]) -> str:joined_bytes = bytearray()for id in ids:joined_bytes.extend(self.vocab[id])return bytes(joined_bytes).decode("utf-8", errors="replace")

encode_iterable

當需要encode比較大的文件時,可能無法將本文全部加載進內存,這時就需要流式讀取,一部分一部分進行encode。已經實現了上面的encode這個功能就比較簡單了,就是調用一下encode,然后使用python中的yield from即可。關于yield from的用法參考Python中yield和yield from

def encode_iterable(self, iterable: Iterable[str]) -> Iterator[int]:for chunk in iterable:if not chunk:continuetoken_ids = self.encode(chunk)yield from token_ids

三、 如何測試

這門課程可以使用pytest在本地進行測試,先進入assignment1-basic/tests中。
要測試train_bpe部分的內容就運行

pytest train_bpe.py

上面的pytest命令會運行train_bpe.py中以**test_**開頭的所有測試函數。
要測試tokenizer部分的內容就運行:

pytest test_tokenizer.py

當然對于具體哪個測試沒過可以看一下測試代碼,也方便debug。
關于pytest的應用可以參考這個鏈接coming soon(還沒寫,寫好再發)

然后你就可以順利通過測試了~~,需要注意的是test_tokenizer的最后一個測試出現XFailed沒有關系,可以進到test中看一下那個函數,里面有說明。
請添加圖片描述
請添加圖片描述

四、 github完整代碼

github倉庫鏈接cs336 assignment1 BPETokenizer

五、 總結

關于這個BPE Tokenizer的細節確實很多,實現的時候也學到了很多東西。

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

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

相關文章

RockAI 的破壁之戰:Yan 架構如何啃下“端側煉丹”硬骨頭?

過去兩年&#xff0c;AI 模型的發展敘事幾乎被兩大陣營主導&#xff1a;無所不能的云端模型與充滿想象的端側模型。行業曾描繪一個誘人藍圖&#xff1a;隨著輕量化模型能力的提升&#xff0c;AI 終將擺脫云端束縛&#xff0c;在每個人的設備上實現永不離線的貼身智能。然而&…

交叉驗證:原理、作用與在機器學習流程中的位置

交叉驗證&#xff08;Cross-Validation&#xff09;是機器學習中評估模型性能、選擇最優參數和防止過擬合的核心技術。它在整個機器學習流程中扮演著關鍵角色。一、為什么需要交叉驗證&#xff1f;1. 解決訓練/測試劃分的局限性??問題??&#xff1a;隨機單次劃分訓練集/測試…

js怎么判斷一個未申明的變量?

在 JavaScript 中&#xff0c;判斷一個變量是否未聲明&#xff08;未定義&#xff09;需要特別注意&#xff0c;因為直接訪問未聲明的變量會拋出 ReferenceError 錯誤。 最安全的方式是使用 typeof 操作符&#xff0c;因為它對未聲明的變量操作時不會報錯&#xff0c;而是返回 …

C++進階-封裝紅黑樹模擬實現map和set(難度較高)

目錄 1.預備知識 2.初步代碼 3.對紅黑樹實現的代碼進行改造 4.對map和set的改造 5.對RBTree::insert的改造 6.對RBTree::Find函數的改造 7.實現iterator(最重要) 8.實現const_iterator 9.完成set和map的key不能修改 10.實現map的operator[] 11.代碼匯總 12.總結 1.預…

安裝MySQL可視化管理工具DBeaver教程

系統&#xff08;kelin&#xff09;上安裝MySQL可視化管理工具DBeaver教程 背景說明 在國產操作系統麒麟&#xff08;基于Debian/Ubuntu&#xff09;環境下&#xff0c;MySQL數據庫管理常依賴命令行&#xff0c;效率較低且不便于直觀操作。借助 DBeaver 這類跨平臺的圖形化數據…

非機動車亂停放識別準確率↑37%:陌訊多特征融合算法實戰解析

一、行業痛點&#xff1a;非機動車治理的技術瓶頸根據《2023 城市靜態交通治理報告》顯示&#xff0c;國內一線城市非機動車亂停放占用人行道比例超 60%&#xff0c;傳統監控方案存在三大技術難點&#xff1a;遮擋干擾&#xff1a;共享單車與私人電動車堆疊導致目標完整性缺失&…

Eclipse 安裝插件指南

Eclipse 安裝插件指南 引言 Eclipse 是一款強大的集成開發環境(IDE),廣泛應用于Java、C/C++、PHP等多種編程語言。為了提高開發效率,Eclipse 支持通過插件來擴展其功能。本文將詳細介紹如何在Eclipse中安裝插件,幫助您快速提升開發體驗。 插件概述 Eclipse 插件是用于…

區塊鏈 和 一致性哈希的結合

怎么結合呢&#xff1f; 我們先來回顧一下一致性哈希代碼實現里面的結構 // Consistent holds the information about the members of the consistent hash circle. type Consistent struct {mu sync.RWMutex // 讀寫鎖&#xff0c;用于保護并發訪問共享數據config Con…

使用yolo11訓練智慧醫療-孤獨癥兒童行為檢測數據集VOC+YOLO格式7295張34類別步驟和流程

【數據集介紹】數據集中有很多增強圖片&#xff0c;也有很多視頻連續截取圖片請查看圖片預覽數據集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路徑的txt文件&#xff0c;僅僅包含jpg圖片以及對應的VOC格式xml文件和yolo格式txt文件)圖片數量(jpg文件個數)&#xff1a;…

vim 組件 使用pysocket進行sock連接

vim組件實現 以下是使用 Vim 插件架構實現 Python Socket 客戶端的完整方案&#xff0c;支持集成到 Vim 控件并實現雙向通信&#xff1a; ~/.vim/plugin/socket_client.vim" 定義全局命令和快捷鍵 command! -nargs* SocketConnect call s:StartSocketClient(<f-args>…

FFmpeg+javacpp中純音頻播放

FFmpegjavacpp中純音頻播放1. Java Sound播放2、整合音頻信息AudioInfo3、添加ExecutorService執行播放FFmpegjavacppjavacv使用 FFmpegjavacpp中FFmpegFrameGrabber FFmpegjavacpp中仿ffplay播放 JavaCV 1.5.12 API JavaCPP Presets for FFmpeg 7.1.1-1.5.12 API1. Java Soun…

洛谷P1036 [NOIP 2002 普及組] 選數

P1036 [NOIP 2002 普及組] 選數 題目描述 已知 nnn 個整數 x1,x2,??,xnx_1,x_2,\cdots,x_nx1?,x2?,?,xn?&#xff0c;以及 111 個整數 kkk&#xff08;k<nk<nk<n&#xff09;。從 nnn 個整數中任選 kkk 個整數相加&#xff0c;可分別得到一系列的和。例如當 n4n…

Linux學習記錄(八)文件共享

本文記錄在Vmware中啟用文件共享時的一些注意事項&#xff1a;1.提前安裝vmware-tools&#xff0c;可以通過Vmware的虛擬機菜單欄中拿到文件&#xff0c;然后直接運行vmware-install.pl文件進行安裝&#xff1b;也可以通過指令sudo apt-get install open-vm-tools進行安裝。推薦…

洛谷 火燒赤壁 差分/貪心

題目背景曹操平定北方以后&#xff0c;公元 208 年&#xff0c;率領大軍南下&#xff0c;進攻劉表。他的人馬還沒有到荊州&#xff0c;劉表已經病死。他的兒子劉琮聽到曹軍聲勢浩大&#xff0c;嚇破了膽&#xff0c;先派人求降了。孫權任命周瑜為都督&#xff0c;撥給他三萬水軍…

Linux 用戶與組管理全解析

Linux 用戶與組管理一、用戶和組的基本概念 1. 用戶賬號類型 超級用戶&#xff08;root&#xff09;&#xff1a;默認擁有系統最高權限&#xff08;UID0&#xff09;&#xff0c;僅建議用于系統管理與維護&#xff0c;日常操作應使用普通用戶。普通用戶&#xff1a;由管理員創建…

開疆智能ModbusTCP轉Profient網關連接ER機器人配置案例

本案例時西門子1200PLC通過ModbusTCP轉Profinet網關連接埃斯頓機器人的配置案例&#xff0c;網關作為ModbusTCP的客戶端連接機器人。配置過程&#xff1a;首先打開機器人通訊手冊。查詢機器人支持的功能碼及默認IP和端口號打開網關配置軟件“Gateway Configuration Studio”新建…

Docker換源加速(更換鏡像源)詳細教程(2025.3最新可用鏡像,全網最詳細)

文章目錄前言可用鏡像源匯總換源方法1-臨時換源換源方法2-永久換源&#xff08;推薦&#xff09;常見問題及對應解決方案1.換源后&#xff0c;可以成功pull&#xff0c;但是search會出錯補充1.如何測試鏡像源是否可用2.Docker內的Linux換源教程換源速通版&#xff08;可以直接無…

機器學習【三】SVM

本文系統介紹了支持向量機(SVM)的理論與實踐。理論部分首先區分了線性可分與不可分問題&#xff0c;闡述了SVM通過尋找最優超平面實現分類的核心思想&#xff0c;包括支持向量、間隔最大化等關鍵概念。詳細講解了硬間隔與軟間隔SVM的數學原理&#xff0c;以及核函數(線性核、多…

DevOps平臺大比拼:Gitee、Jenkins與CircleCI如何選型?

DevOps平臺大比拼&#xff1a;Gitee、Jenkins與CircleCI如何選型&#xff1f; 在數字化轉型浪潮席卷全球的當下&#xff0c;DevOps已成為企業提升研發效能的關鍵引擎。面對市場上紛繁復雜的DevOps工具鏈&#xff0c;如何選擇最適合自身業務需求的平臺成為技術決策者的重要課題。…

開源醫院信息管理系統:基于若依框架的智慧醫療解決方案

引言在數字化浪潮的推動下&#xff0c;醫療行業正加速向信息化、智能化轉型。醫院信息管理系統&#xff08;HIS&#xff09;作為醫療管理的核心工具&#xff0c;直接影響醫院的運營效率和服務質量。近期&#xff0c;一款基于 若依框架 Vue 的開源醫院管理系統&#xff08;hosp…