GPT系列--類GPT2源碼剖析

無需多言,大家應該都用過了,如今都更新到GPT-5了。

1. GPT-1

回到2018年的NLP,神仙打架,BERT與GPT不分先后。

GPT是“Generative Pre-Training”的簡稱,生成式的預訓練。BERT和GPT肯定是GPT難訓練,引用量也是BERT更多。

BERT是結合上下文,進行完形填空,相對來說比較簡單。但GPT只給上文,讓預測下文,能參考的東西只有 一部分。

GPT網絡結構跟BERT類似,BERT是Transformer的編碼器,GPT是Transformer的解碼器,因為它不能用后文的信息,只能用前文特征。

decoder 模塊的核心是 因果 self-attention(只能看到前文),這正是自回歸生成所需要的。encoder 模塊則允許雙向(全局)self-attention,用于一次性編碼整個序列,適合理解任務。

目標函數:

θ 是模型參數,u_{i-1} 到?u_{i-k} 都是上文,預測下文 u_i。

訓練好這個GPT-1模型之后,要想進行使用,所有下游任務都需要微調

2. GPT-2

能不能不微調,直接訓出一個zero-shot的,開箱即用的模型。

下游任務有很多很多,不訓練怎么能讓模型知道要做什么呢?通過一些提示告訴模型需要完成什么任務。

大模型在規模、數據和訓練目標的共同作用下,能通過提示(prompt)在很多下游任務上“零次/少次學習(zero-/few-shot)地表現很好”,但在可靠性、效率和特定任務精度上,微調(或參數高效微調)仍然常常必要。

總結來說就是模型更大了(1.17億→15億),而且下游任務不需要微調。還是一個decoder-only 的 Transformer 自回歸語言模型。

自回歸模型要進行預測,但是會不會陷入一個死循環呢?所以我們希望模型預測多樣化一些,這就涉及到了采樣策略,不能一直選概率最大的,也要選一些其他的。

2.1 這就涉及到了 Temperature

溫度就是說對預測結果進行概率重新設計。溫度為1就還相當于走softmax,誰得分高誰就概率最大

當溫度除以一個小于1的值,會讓原本預測的值之間的差異變大,再經過softmax,誰得分高誰概率更大了,所以溫度降低就會抑制多樣性,最終結果就是概率最大的結果。

當除上大一點的數,就會縮小值之間的差距,所以概率就會趨向平均,所以升溫多樣性會大一些,但是可能會胡說八道,就是他輸出了很多小概率的詞。

這時模型在采樣時不能讓他采樣到賊離譜的結果,就是概率很小,但不為0,也是有可能被采樣的,這就不合適了,所以引入top-k

2.2 Top?k 與 Top p

TOPK和TOPP都是要剔除那些概率特別小的結果,概率排序后TOPK選前10個,那之后的值就全都為0了。

Top-K:只把概率最高的 K 個 token 放進候選池,從中采樣。

Top-P(nucleus):把概率最高的前若干個 token(最小集合)取出,使它們的累計概率 ≥ P,然后在這個集合里采樣

這倆不沖突,都能設置,誰先到上限誰以誰為準。

3. GPT-3

GPT-3 不是把架構換了,而是把 尺度(參數、訓練數據與 compute)和工程實踐 放大到一個新的量級,從而出現了明顯的 in-context / few-shot 能力 和若干“突現”行為,這使得模型在很多任務上能在不微調的情況下通過 prompt 就表現得很好。

GPT-2:最大約 1.5B 參數(還有更小的 117M/345M/762M 等變體)。

GPT-3:最大 ~175B 參數(大約是 GPT-2 最大版的 ~100×),這個尺度躍升是核心因素之一。

不用做微調,沒有再訓練的過程。

4. DeBug GPT2

GPT-2是開源的,所以可以來運行學習一下。

4.1 預處理

if __name__ == '__main__':preprocess()
def preprocess():"""對故事數據集進行預處理"""# 設置參數parser = argparse.ArgumentParser()parser.add_argument('--vocab_file', default='vocab/chinese_vocab.model', type=str, required=False,help='詞表路徑')parser.add_argument('--log_path', default='log/preprocess.log', type=str, required=False, help='日志存放位置')parser.add_argument('--data_path', default='data/novel', type=str, required=False, help='數據集存放位置')parser.add_argument('--save_path', default='data/train.pkl', type=str, required=False, help='對訓練數據集進行tokenize之后的數據存放位置')parser.add_argument('--win_size', default=200, type=int, required=False, help='滑動窗口的大小,相當于每條數據的最大長度')parser.add_argument('--step', default=200, type=int, required=False, help='滑動窗口的滑動步幅')args = parser.parse_args()
    # 初始化日志對象logger = set_logger(args.log_path)# 初始化tokenizertokenizer = CpmTokenizer(vocab_file="vocab/chinese_vocab.model")#pip install jiebaeod_id = tokenizer.convert_tokens_to_ids("<eod>")   # 文檔結束符
   # 讀取小說數據集目錄下的所有文件train_list = []logger.info("start tokenizing data")for file in tqdm(os.listdir(args.data_path)):file = os.path.join(args.data_path, file)

novel中有15本小說,所以0/15,然后拿到第一本

        with open(file, "r", encoding="utf8")as reader: # 打開這個數據lines = reader.readlines() # 讀取所有行for i in range(len(lines)): # 遍歷每一行if lines[i].isspace() != True and lines[i] != '\n': # 如果非空且不是結束# 那就編碼這行,然后加上終止符token_ids = tokenizer.encode(lines[i].strip(), add_special_tokens=False) + [eod_id]

一句話里都有些什么,然后加了個終止符

                    if i%1000 == 0:# 打印當前步和數據print('cur_step', i, lines[i].strip())else: # 如果是空或\n就跳過continue

                # 對于每條數據,使用滑動窗口對其進行截斷win_size = args.win_size # 200step = args.step # 200start_index = 0end_index = win_sizedata = token_ids[start_index:end_index] # 取數據截斷train_list.append(data) # 添加到訓練list中start_index += step # 首尾滑動end_index += step# 這條剩下的數據長度,如果大于或等于50,才加入訓練數據集# 其實就是在對長的進行拆分while end_index+50 < len(token_ids): data = token_ids[start_index:end_index]train_list.append(data)start_index += stepend_index += step# 如果小于50,則這條數據集結束,去下一條

短的本身就是一個數據,長的會切分成多個。

執行完這個預處理之后會產生一個.pkl文件

4.2 traing

if __name__ == '__main__':main()
def main():# 初始化參數args = set_args()# ---
def set_args():parser = argparse.ArgumentParser()parser.add_argument('--device', default='0,1', type=str, required=False, help='設置使用哪些顯卡')parser.add_argument('--no_cuda', action='store_true', help='不使用GPU進行訓練')parser.add_argument('--vocab_path', default='vocab/chinese_vocab.model', type=str, required=False,help='sp模型路徑')parser.add_argument('--model_config', default='config/cpm-small.json', type=str, required=False,help='需要從頭訓練一個模型時,模型參數的配置文件')parser.add_argument('--train_path', default='data/train.pkl', type=str, required=False, help='經過預處理之后的數據存放路徑')parser.add_argument('--max_len', default=200, type=int, required=False, help='訓練時,輸入數據的最大長度')parser.add_argument('--log_path', default='log/train.log', type=str, required=False, help='訓練日志存放位置')parser.add_argument('--ignore_index', default=-100, type=int, required=False, help='對于ignore_index的label token不計算梯度')parser.add_argument('--epochs', default=100, type=int, required=False, help='訓練的最大輪次')parser.add_argument('--batch_size', default=16, type=int, required=False, help='訓練的batch size')parser.add_argument('--gpu0_bsz', default=6, type=int, required=False, help='0號卡的batch size')parser.add_argument('--lr', default=1.5e-4, type=float, required=False, help='學習率')parser.add_argument('--eps', default=1.0e-09, type=float, required=False, help='AdamW優化器的衰減率')parser.add_argument('--log_step', default=10, type=int, required=False, help='多少步匯報一次loss')parser.add_argument('--gradient_accumulation_steps', default=6, type=int, required=False, help='梯度積累的步數')parser.add_argument('--max_grad_norm', default=1.0, type=float, required=False)parser.add_argument('--save_model_path', default='model', type=str, required=False,help='模型輸出路徑')parser.add_argument('--pretrained_model', default='model/zuowen_epoch40', type=str, required=False,help='預訓練的模型的路徑')parser.add_argument('--seed', type=int, default=1234, help='設置隨機種子')parser.add_argument('--num_workers', type=int, default=0, help="dataloader加載數據時使用的線程數量")# parser.add_argument('--patience', type=int, default=0, help="用于early stopping,設為0時,不進行early stopping.early stop得到的模型的生成效果不一定會更好。")parser.add_argument('--warmup_steps', type=int, default=4000, help='warm up步數')# parser.add_argument('--label_smoothing', default=True, action='store_true', help='是否進行標簽平滑')args = parser.parse_args()return args
    # ---# 設置使用哪些顯卡進行訓練os.environ["CUDA_VISIBLE_DEVICES"] = args.deviceargs.cuda = not args.no_cuda# if args.batch_size < 2048 and args.warmup_steps <= 4000:#     print('[Warning] The warmup steps may be not enough.\n' \#           '(sz_b, warmup) = (2048, 4000) is the official setting.\n' \#           'Using smaller batch w/o longer warmup may cause ' \#           'the warmup stage ends with only little data trained.')# 創建日志對象logger = set_logger(args.log_path)# 當用戶使用GPU,并且GPU可用時args.cuda = torch.cuda.is_available() and not args.no_cudadevice = 'cuda:0' if args.cuda else 'cpu'args.device = devicelogger.info('using device:{}'.format(device))# 設置隨機種子set_random_seed(args.seed, args.cuda)# 初始化tokenizer https://www.sciencedirect.com/science/article/pii/S266665102100019Xtokenizer = CpmTokenizer(vocab_file="vocab/chinese_vocab.model")args.eod_id = tokenizer.convert_tokens_to_ids("<eod>")  # 文檔結束符args.pad_id = tokenizer.pad_token_id# 創建模型的輸出目錄if not os.path.exists(args.save_model_path):os.mkdir(args.save_model_path)# 創建模型if args.pretrained_model:  # 加載預訓練模型model = GPT2LMHeadModel.from_pretrained(args.pretrained_model)else:  # 初始化模型model_config = GPT2Config.from_json_file(args.model_config)model = GPT2LMHeadModel(config=model_config)model = model.to(device)logger.info('model config:\n{}'.format(model.config.to_json_string()))assert model.config.vocab_size == tokenizer.vocab_size# 多卡并行訓練模型if args.cuda and torch.cuda.device_count() > 1:# model = DataParallel(model).cuda()model = BalancedDataParallel(args.gpu0_bsz, model, dim=0).cuda()logger.info("use GPU {} to train".format(args.device))# 計算模型參數數量num_parameters = 0parameters = model.parameters()for parameter in parameters:num_parameters += parameter.numel()# 這參數量不大logger.info('number of model parameters: {}'.format(num_parameters))# 記錄參數設置logger.info("args:{}".format(args))# 加載訓練集和驗證集# ========= Loading Dataset ========= #train_dataset = load_dataset(logger, args)train(model, logger, train_dataset, args)
def load_dataset(logger, args):"""加載訓練集"""logger.info("loading training dataset")train_path = args.train_path # .pkl文件with open(train_path, "rb") as f:train_list = pickle.load(f)# test# train_list = train_list[:24]train_dataset = CPMDataset(train_list, args.max_len)return train_dataset
def train(model, logger, train_dataset, args):train_dataloader = DataLoader(train_dataset, batch_size=args.batch_size, shuffle=True, num_workers=args.num_workers, collate_fn=collate_fn,drop_last=True)# 打印總共會遍歷多少個 batch# len(train_dataloader):每個 epoch 的 batch 數logger.info("total_steps:{}".format(len(train_dataloader)* args.epochs))# 計算訓練過程中的 optimizer 步數總數。# // args.gradient_accumulation_steps:整除梯度累積步數,表示每進行 gradient_accumulation_steps 個 mini-batch 才執行一次 optimizer.step()# 每 epoch 的 optimizer 步數大約是len(train_dataloader) / gradient_accumulation_stepst_total = len(train_dataloader) // args.gradient_accumulation_steps * args.epochs# 創建優化器optimizer = transformers.AdamW(model.parameters(), lr=args.lr, eps=args.eps)#設置warmup,創建一個線性學習率調度器scheduler = transformers.get_linear_schedule_with_warmup(optimizer, num_warmup_steps=args.warmup_steps, num_training_steps=t_total)logger.info('start training')train_losses = []   # 記錄每個epoch的平均loss# ========== start training ========== ## 遍歷每個epochfor epoch in range(args.epochs):# 每個epochtrain_loss = train_epoch(model=model, train_dataloader=train_dataloader,optimizer=optimizer, scheduler=scheduler,logger=logger, epoch=epoch, args=args)train_losses.append(round(train_loss, 4))logger.info("train loss list:{}".format(train_losses))# ---
def train_epoch(model, train_dataloader, optimizer, scheduler, logger,epoch, args):# 訓練模式model.train()device = args.device# 忽略的索引ignore_index = args.ignore_indexepoch_start_time = datetime.now()total_loss = 0  # 記錄下整個epoch的loss的總和epoch_correct_num = 0   # 每個epoch中,預測正確的word的數量epoch_total_num = 0  # 每個epoch中,預測的word的總數量# 遍歷dataloaderfor batch_idx, (input_ids, labels) in enumerate(train_dataloader):# ------

DataLoader?會先用?sampler?選索引、調用?dataset.__getitem__?取到樣本列表,然后立刻調用 collate_fn(batch)?將這批樣本打包對齊成張量。也就是每個 batch 調一次,在進入訓練循環、送入模型之前。

# DataLoader 在組裝一個 batch 時對變長序列進行對齊(padding)。
def collate_fn(batch):# 將一個 batch 中不同長度的樣本(token 序列)按最長序列補齊為同一長度,返回可直接喂給模型的 input_ids 和 labels 張量。input_ids = rnn_utils.pad_sequence(batch, batch_first=True, padding_value=5)labels = rnn_utils.pad_sequence(batch, batch_first=True, padding_value=-100)return input_ids, labels

一個batch里的數據長度得相同,標簽也同理,5號對應的標簽就是-100,就是我們不考慮的

這4個樣本中最大長度應該就是151的,label也是這么大

接下來把數據都放到gpu中:

    # ------ # 捕獲cuda out of memory exceptiontry:input_ids = input_ids.to(device)labels = labels.to(device)# 對網絡模型forwardoutputs = model.forward(input_ids, labels=labels)

這個網絡模型最后輸出特征是30000,也就是說我們的語料庫中有3w個詞,最終就是要預測這個詞是3w當中的哪一個。

forward后就會得到預測的詞屬于3w個類別的概率。

然后計算當前的損失,求個平均

            logits = outputs.logitsloss = outputs.lossloss = loss.mean()
?# 統計該batch的預測token的正確數與總數batch_correct_num, batch_total_num = calculate_acc(logits, labels, ignore_index=ignore_index)# ------
def calculate_acc(logit, labels, ignore_index=-100):# 把預測值展開,拿到所有預測結果logit = logit[..., :-1, :].contiguous().view(-1, logit.size(-1))# 把標簽也展開labels = labels[..., 1:].contiguous().view(-1)# 對于每條數據,返回最大的值和其index,也就是得到該位置預測的 token id_, logit = logit.max(dim=-1)  # 進行非運算,返回一個tensor,若labels的第i個位置為pad_id,則置為0,否則為1# 也就是說如果是要忽略的就不管了non_pad_mask = labels.ne(ignore_index)n_correct = logit.eq(labels).masked_select(non_pad_mask).sum().item()n_word = non_pad_mask.sum().item()return n_correct, n_word

GPT-2 在時間步 t 輸出的是對 下一個 token(t+1) 的預測分布(logits 的第 t 列對應標簽的第 t+1 個 token)。

舉個非常直觀的數字例子(batch=1,seq_len=4):

原 labels(token id 序列):?labels = [L0, L1, L2, L3]

模型在每個時刻輸出 logits(每個時刻是對 vocab 的打分):logits(按時間步看)產生的是對下一個 token 的預測:

  • logits at t=0 -> 預測 L1

  • logits at t=1 -> 預測 L2

  • logits at t=2 -> 預測 L3

  • logits at t=3 -> 預測 L4(logits 的最后一幀通常沒有對應的標簽,因此在這個對齊里被丟棄)

所以做:

  • logits[..., :-1, :] 對應 logits for t = 0,1,2

  • labels[..., 1:] 對應 labels L1, L2, L3

這里僅作演示,實際中比較的是token id
logit:晚上  吃 大面
label:今天 晚上 吃 大肉

真實標簽 labels 向右切一格去一一對應,然后逐項比較,n_correct = 2,n_word = 3。

labels[0] = "今天" 沒有被比較,因為模型在 t=-1(沒有這個時刻)沒法去預測它;同理模型在最后一個時刻也是少于labels一位的。相當于logit本身就向左移了一位。

這就是對了98個。

            # 統計該epoch的預測token的正確數與總數epoch_correct_num += batch_correct_numepoch_total_num += batch_total_num# 計算該batch的accuracybatch_acc = batch_correct_num / batch_total_numtotal_loss += loss.item()# 如果梯度積累的步數大于1,我們默認是6if args.gradient_accumulation_steps > 1:# 求累積出來的損失到每一步上的均值loss = loss / args.gradient_accumulation_stepsloss.backward()# 梯度裁剪--當梯度很大時,optimizer.step() 會做出巨大的參數更新,導致訓練不穩定、loss 振蕩甚至 NaN。torch.nn.utils.clip_grad_norm_(model.parameters(), args.max_grad_norm)# “在時間維度上把多個小 batch 的梯度合并”,從而在效果上等同于一次用更大 batch 做反向傳播,所以這樣可以間接增加batch# 進行一定step的梯度累計之后,更新參數,達到6次累積才更新,否則就攢著if (batch_idx + 1) % args.gradient_accumulation_steps == 0:# 更新參數optimizer.step()# 更新學習率scheduler.step()# 清空梯度信息optimizer.zero_grad()if (batch_idx + 1) % args.log_step == 0:logger.info("batch {} of epoch {}, loss {}, batch_acc {}, lr {}".format(batch_idx + 1, epoch + 1, loss.item() * args.gradient_accumulation_steps, batch_acc, scheduler.get_lr()))del input_ids, outputsexcept RuntimeError as exception:if "out of memory" in str(exception):logger.info("WARNING: ran out of memory")if hasattr(torch.cuda, 'empty_cache'):torch.cuda.empty_cache()else:logger.info(str(exception))raise exception# 記錄當前epoch的平均loss與accuracyepoch_mean_loss = total_loss / len(train_dataloader)epoch_mean_acc = epoch_correct_num / epoch_total_numlogger.info("epoch {}: loss {}, predict_acc {}".format(epoch + 1, epoch_mean_loss, epoch_mean_acc))# save modellogger.info('saving model for epoch {}'.format(epoch + 1))model_path = join(args.save_model_path, 'epoch{}'.format(epoch + 1))if not os.path.exists(model_path):os.mkdir(model_path)model_to_save = model.module if hasattr(model, 'module') else modelmodel_to_save.save_pretrained(model_path)logger.info('epoch {} finished'.format(epoch + 1))epoch_finish_time = datetime.now()logger.info('time for one epoch: {}'.format(epoch_finish_time - epoch_start_time))return epoch_mean_loss

4.3 部署與網頁預測展示

# 這個前端在加上cache之后會先走這個函數
@st.cache(allow_output_mutation=True)
def get_model(device, model_path):tokenizer = CpmTokenizer(vocab_file="vocab/chinese_vocab.model")eod_id = tokenizer.convert_tokens_to_ids("<eod>")  # 文檔結束符sep_id = tokenizer.sep_token_idunk_id = tokenizer.unk_token_idmodel = GPT2LMHeadModel.from_pretrained(model_path)model.to(device)model.eval()return tokenizer, model, eod_id, sep_id, unk_id

這個模型加載之后是一直存在的。

if __name__ == '__main__':writer()
def writer():st.markdown("""## GPT生成模型""")st.sidebar.subheader("配置參數")generate_max_len = st.sidebar.number_input("generate_max_len", min_value=0, max_value=512, value=32, step=1)top_k = st.sidebar.slider("top_k", min_value=0, max_value=10, value=3, step=1)top_p = st.sidebar.number_input("top_p", min_value=0.0, max_value=1.0, value=0.95, step=0.01)temperature = st.sidebar.number_input("temperature", min_value=0.0, max_value=100.0, value=1.0, step=0.1)parser = argparse.ArgumentParser()parser.add_argument('--generate_max_len', default=generate_max_len, type=int, help='生成標題的最大長度')parser.add_argument('--top_k', default=top_k, type=float, help='解碼時保留概率最高的多少個標記')parser.add_argument('--top_p', default=top_p, type=float, help='解碼時保留概率累加大于多少的標記')parser.add_argument('--max_len', type=int, default=512, help='輸入模型的最大長度,要比config中n_ctx小')parser.add_argument('--temperature', type=float, default=temperature, help='輸入模型的最大長度,要比config中n_ctx小')args = parser.parse_args()context = st.text_area("請輸入標題", max_chars=512)title = st.text_area("請輸入正文", max_chars=512)if st.button("點我生成結果"):start_message = st.empty()start_message.write("自毀程序啟動中請稍等 10.9.8.7 ...")start_time = time.time()result = predict_one_sample(model, tokenizer, device, args, title, context)end_time = time.time()start_message.write("生成完成,耗時{}s".format(end_time - start_time))st.text_area("生成結果", value=result, key=None)else:st.stop()

關鍵的就是這一步:?

result = predict_one_sample(model, tokenizer, device, args, title, context)

title?和?context?是原始的中文字符串,然后用分詞器編碼成 token id 序列再送入模型。

def predict_one_sample(model, tokenizer, device, args, title, context):title_ids = tokenizer.encode(title, add_special_tokens=False)context_ids = tokenizer.encode(context, add_special_tokens=False)input_ids = title_ids + [sep_id] + context_idscur_len = len(input_ids)last_token_id = input_ids[-1]  # 初始輸入序列的“最后一個 token 的 id”input_ids = torch.tensor([input_ids], dtype=torch.long, device=device)while True:next_token_id = generate_next_token(input_ids,args)# ------

其中,generate_next_token(input_ids,args):

def generate_next_token(input_ids,args):"""對于給定的上文,生成下一個單詞"""# 只根據當前位置的前context_len個token進行生成,只保留最近 200 個 tokeninput_ids = input_ids[:, -200:]# 模型做預測outputs = model(input_ids=input_ids)# 所有的結果logits = outputs.logits# next_token_logits表示由最后一個時間步的的hidden_state對應的所有prediction_scores# 越靠后看到的前文越多,預測條件越豐富,所以取最后時間步下的的概率結果next_token_logits = logits[0, -1, :]next_token_logits = next_token_logits / args.temperature# 對于<unk>的概率設為無窮小,也就是說模型的預測結果不可能是[UNK]這個tokennext_token_logits[unk_id] = -float('Inf')filtered_logits = top_k_top_p_filtering(next_token_logits, top_k=args.top_k, top_p=args.top_p)# torch.multinomial表示從候選集合中選出無放回地進行抽取num_samples個元素,權重越高,抽到的幾率越高,返回元素的下標next_token_id = torch.multinomial(F.softmax(filtered_logits, dim=-1), num_samples=1)return next_token_id

這就通過上文預測到下一個位置的詞了,然后回到循環中:

        # ------# 把原來的input和預測出的詞進行拼接input_ids = torch.cat((input_ids, next_token_id.unsqueeze(0)), dim=1)cur_len += 1# 由預測的 token id 還原到 token,也就是詞表里的一個符號/子詞/字符。用于后面的判斷word = tokenizer.convert_ids_to_tokens(next_token_id.item())# 超過最大長度,并且換行if cur_len >= args.generate_max_len and last_token_id == 8 and next_token_id == 3:break# 超過最大長度,并且生成標點符號if cur_len >= args.generate_max_len and word in [".", "。", "!", "!", "?", "?", ",", ","]:break# 生成結束符if next_token_id == eod_id:break# 持續循環,把預測出的詞不斷加到input后面,然后通過input取預測新詞# input_ids 在那句代碼里確實是初始上文 + 模型生成出來的所有 token id 的拼接# tokenizer.decode(...) 會把這些 id 按 tokenizer 的規則拼回可讀的字符串。 result = tokenizer.decode(input_ids.squeeze(0))# title + [sep_id] + context + 生成的預測content = result.split("<sep>")[1]  # 生成的最終內容return content

5. GPT-4?

## 待更

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

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

相關文章

這是一款沒有任何限制的免費遠程手機控制手機的軟件

這是一款沒有任何限制的免費遠程手機控制手機的軟件支持安卓和蘋果1.安裝1.1被控制端安裝airdroid1.2控制端air mirror2.登錄賬號控制端和被控制端登錄同一個賬號3.控制打開控制端軟件選擇要控制的機器直接點“遠程控制“

Observability:更智能的告警來了:更快的分診、更清晰的分組和可操作的指導

作者&#xff1a;來自 Elastic Drew Post 探索 Elastic Stack 告警的最新增強功能&#xff0c;包括改進的相關告警分組、將儀表盤鏈接到告警規則&#xff0c;以及將調查指南嵌入到告警中。 在 9.1 版本中&#xff0c;我們對告警進行了重大升級&#xff0c;幫助 SRE 和運維人員更…

數智之光燃盛景 共同富裕創豐饒

8月29日&#xff0c;2025數博會“一帶一路”國際大數據產業發展暨數智賦能新時代、共同富裕向未來的會議在貴陽國際生態會議中心隆重舉行。作為全球大數據領域的重要盛會&#xff0c;此次活動吸引了來自聯合國機構、國際組織、科研院所、知名企業等社會各界的百余位代表&#x…

【網絡編程】recv函數的本質是什么?

一、為什么說recv函數的本質是 “copy”&#xff1f; recv是用于從網絡連接&#xff08;或其他 IO 對象&#xff09;接收數據的函數&#xff0c;它的核心動作不是 “從網絡上拉取數據”&#xff0c;而是 “把已經到達內核緩沖區的數據復制到用戶程序的緩沖區”。 具體流程拆解&…

JSP程序設計之輸入/輸出對象 — out對象

目錄1、out對象概述2.實例&#xff1a;out對象方法運用輸入/輸出對象&#xff0c;可以控制頁面的輸入和輸出&#xff0c;用于訪問與所有請求和響應有關的數據&#xff0c;包括out、request和response對象。 1、out對象概述 out對象是JspWriter類的一個實例&#xff0c;是一個…

UE里為什么要有提升變量

1、為了簡潔當一個類里面的函數比較多&#xff0c;并且使用比較頻繁的時候&#xff0c;就要不斷的從這個類節點往外拉線&#xff0c;從而獲取不同的函數節點&#xff0c;這樣的藍圖就會看起來比較亂&#xff0c;這時候&#xff0c;就可以將這個常用的類提升為變量。2、為了存儲…

玩轉物聯網只需十行代碼,可它為何悄悄停止維護

文章目錄玩轉物聯網只需十行代碼&#xff0c;可它為何悄悄停止維護1 背景&#xff1a;MQTT 遇上 asyncio&#xff0c;為什么選 hbmqtt&#xff1f;2 hbmqtt 是什么&#xff1f;3 安裝&#xff1a;一行命令&#xff0c;但別裝最新4 五大核心 API&#xff1a;10 行代碼跑通發布訂…

從零開始學大模型之預訓練語言模型

預訓練語言模型 本文較長&#xff0c;建議點贊收藏&#xff0c;以免遺失。更多AI大模型開發 學習視頻/籽料/面試題 都在這>>Github<< >>Gitee<< 3.1 Encoder-only PLM 在上一章&#xff0c;我們詳細講解了給 NLP 領域帶來巨大變革注意力機制以及使用…

JMeter接口測試全流程解析

1. Jmeter的界面介紹和功能組件&#xff08;元件&#xff09;1、測試計劃&#xff1a;Jmeter的起點和容器2、線程組&#xff1a;代表一定的虛擬用戶&#xff08;一個用戶一個線程&#xff09;3、取樣器&#xff1a;發送請求的最小單元4、邏輯控制器&#xff1a;控制組件的執行順…

Effective Modern C++ 條款26:避免在通用引用上重載

在C編程中&#xff0c;函數重載是一項強大的特性&#xff0c;它允許我們為不同的參數類型提供不同的實現。然而&#xff0c;當涉及到通用引用&#xff08;universal references&#xff09;時&#xff0c;重載可能會帶來意想不到的問題。Effective Modern C的條款26明確指出&am…

OpenLayers數據源集成 -- 章節一:圖像圖層詳解

前言在前面的文章中&#xff0c;我們學習了OpenLayers的基礎控件操作。本文將深入探討OpenLayers中的圖像圖層&#xff08;ImageLayer&#xff09;功能&#xff0c;通過一個完整的示例來展示如何使用ImageArcGISRest數據源加載ArcGIS服務&#xff0c;并詳細解釋圖層配置、事件監…

通義萬相wan2.2 Fun系列--Camera鏡頭控制與lnp首尾幀視頻模型

上節內容講解了wan2.2 fun control本節內容對wan2.2 fun系列模型的camera鏡頭控制模型與lnp首尾幀視頻模型進行測試與講解。 Wan2.2-Fun-Camera-Control是阿里基于Wan2.2框架推出的圖生視頻運鏡控制模型 。它支持512、768、1024等多分辨率的視頻預測&#xff0c;以81幀、每秒16…

JavaSE 集合從入門到面試:全面解析與實戰指南

JavaSE 集合從入門到面試&#xff1a;全面解析與實戰指南 在 Java 編程中&#xff0c;集合是處理數據的核心工具&#xff0c;幾乎所有 Java 應用都會用到集合框架。從簡單的列表存儲到復雜的數據分析&#xff0c;集合框架提供了豐富的數據結構和操作方法。本文將從基礎概念到面…

自建云音樂服務器:Navidrome+cpolar讓無損音樂隨身聽

文章目錄前言1. 安裝Docker2. 創建并啟動Navidrome容器3. 公網遠程訪問本地Navidrome3.1 內網穿透工具安裝3.2 創建遠程連接公網地址3.3 使用固定公網地址遠程訪問前言 “想聽自己的無損音樂還要開會員&#xff1f;”——音樂發燒友小王的煩惱。商業音樂平臺音質壓縮&#xff…

C3P0連接池適配HGDB

文章目錄文檔用途詳細信息文檔用途 講解常用的并且需要與數據庫進行交互的開源框架C3P0&#xff0c;以及C3P0框架是如何適配HGDB的。 詳細信息 1.C3P0概述 C3P0是一個開源的JDBC連接池&#xff0c;它實現了數據源和JNDI綁定&#xff0c;支持JDBC3規范和JDBC2的標準擴展。目…

ZeroGPU Spaces 加速實踐:PyTorch 提前編譯全解析

ZeroGPU 讓任何人都能在 Hugging Face Spaces 中使用強大的 Nvidia H200 硬件&#xff0c;而不需要因為空閑流量而長期占用 GPU。 它高效、靈活&#xff0c;非常適合演示&#xff0c;不過需要注意的是&#xff0c;ZeroGPU 并不能在所有場景下完全發揮 GPU 與 CUDA 棧的全部潛能…

8.ImGui-輸入框

免責聲明&#xff1a;內容僅供學習參考&#xff0c;請合法利用知識&#xff0c;禁止進行違法犯罪活動&#xff01; 本次游戲沒法給 內容參考于&#xff1a;微塵網絡安全 上一個內容&#xff1a;7.ImGui-單選框和復選框 單行輸入框使用 ImGui::InputText()&#xff0c;下圖中…

2025年- H120-Lc28. 找出字符串中第一個匹配項的下標(數組)--Java版

1.題目2.思路 短的子串相對不變 所以我們用長的字符串去截取in個長度的子串做遍歷 如果兩者相等 返回字符串第一個匹配項的索引的下標 3.代碼實現 class Solution {public int strStr(String haystack, String needle) {int s1haystack.length();int s2needle.length();//遍歷最…

uport1100系列轉接頭,SZ系列光電編碼器RS485通信

安裝uport1100系列轉接頭驅動 &#xff1a;選擇對應自己系統內核版本的驅動。否則爆出系統內核過高過低等問題。 查看系統內核版本指令&#xff1a; uname -r #簡要 uname -a #詳細驅動下載官網&#xff1a; https://www.moxa.com.cn/support/product-support/software-and-…

Java全棧開發面試實戰:從基礎到微服務架構

Java全棧開發面試實戰&#xff1a;從基礎到微服務架構 在一次互聯網大廠的Java全棧開發崗位面試中&#xff0c;一位名叫李明的28歲程序員&#xff0c;擁有計算機科學與技術本科學歷&#xff0c;工作年限為5年。他的主要職責包括設計和實現前后端分離的Web應用、參與微服務架構的…