深入淺出遷移學習:從理論到實踐

1. 引言:為什么需要遷移學習?

在深度學習爆發的這十年里,我們見證了模型性能的飛速提升 ——ResNet 在圖像分類上突破人類視覺極限,BERT 在 NLP 任務上刷新基準,GPT 系列更是開啟了大語言模型時代。但這些亮眼成果的背后,隱藏著兩個核心前提:大規模標注數據充足的計算資源

現實場景中,這兩個前提往往難以滿足:

  • 醫學影像任務中,一張肺結節標注圖需要放射科醫生花費數小時審核,數據集規模通常只有數千張;
  • 工業缺陷檢測任務中,某類罕見缺陷的樣本可能只有幾十甚至十幾張,無法支撐深度學習模型的訓練;
  • 小語種 NLP 任務(如維吾爾語情感分析),標注數據稀缺,從零訓練模型性能極差;
  • 邊緣設備部署場景中,計算資源有限,無法承擔從零訓練一個百萬參數模型的成本。

傳統深度學習的本質是 “孤立學習”—— 每個任務都需要獨立的數據集和訓練過程,模型無法復用已學知識。而人類則擅長 “舉一反三”:學會識別貓之后,再學識別狗會更容易;掌握英語語法后,學習法語會更快。遷移學習(Transfer Learning, TL)?正是模仿人類這種學習方式的技術,它將從 “源域任務” 中學到的知識,遷移到 “目標域任務” 中,從而解決目標域數據稀缺、計算資源不足的問題。

本文將從理論到實踐,系統講解遷移學習的核心概念、分類體系、常用方法,并通過兩個完整的 PyTorch 代碼實例(計算機視覺 + 自然語言處理)幫助讀者落地,最后探討遷移學習的挑戰與未來方向。無論你是深度學習初學者,還是需要解決實際問題的算法工程師,都能從本文中獲得啟發。

2. 遷移學習核心概念解析

在深入遷移學習的方法前,我們必須先理清幾個核心概念 —— 這些概念是理解所有遷移學習技術的基礎,也是避免混淆的關鍵。

2.1 什么是遷移學習?

遷移學習的官方定義可概括為: 利用源域(Source Domain)和源任務(Source Task)的知識,來提升目標域(Target Domain)和目標任務(Target Task)的學習性能。

簡單來說,就是 “借雞生蛋”:用已有的、數據充足的任務(如 ImageNet 圖像分類)的訓練成果,幫助數據稀缺的新任務(如寵物狗品種分類)提升效果。

需要注意的是,遷移學習的核心前提是源域與目標域 / 任務存在 “相關性”。如果源任務是 “識別汽車”,目標任務是 “識別詩歌情感”,兩者毫無關聯,遷移不僅無效,還可能產生負面影響(即 “負遷移”)。

2.2 遷移學習 vs 傳統機器學習:關鍵差異

為了更清晰地理解遷移學習,我們對比傳統機器學習與遷移學習的核心差異:

對比維度傳統機器學習遷移學習
數據假設訓練數據(源域)與測試數據(目標域)同分布源域與目標域可不同分布
任務獨立性每個任務獨立訓練,無知識復用復用源任務知識,輔助目標任務訓練
數據依賴依賴大規模標注數據可在目標域數據稀缺時工作
泛化能力僅對同分布數據泛化好對不同分布數據的泛化能力更強
典型場景ImageNet 分類、MNIST 手寫體識別醫學影像檢測、小語種文本分類

2.3 核心術語定義:領域(Domain)與任務(Task)

遷移學習中,“領域” 和 “任務” 是兩個最基礎的概念,所有遷移場景都圍繞這兩個概念的關系展開。

2.3.1 領域(Domain):數據的 “來源地”

領域定義了數據的分布特征,通常表示為?\(D = \{X, P(X)\}\),其中:

  • X:特征空間(Feature Space),即數據的表示維度。例如,圖像任務中X是像素矩陣(如\(224 \times 224 \times 3\)),文本任務中X是詞向量(如768維 BERT 嵌入);
  • \(P(X)\):邊緣概率分布(Marginal Probability Distribution),即特征在空間中的分布規律。例如,“白天的貓圖像” 和 “夜晚的貓圖像” 屬于不同領域 —— 前者像素亮度高,后者亮度低,即\(P(X)\)不同。

當兩個領域的X或\(P(X)\)不同時,我們稱它們為 “不同領域”。例如:

  • 源域:ImageNet 中的自然圖像(X為\(224 \times 224 \times 3\),\(P(X)\)為自然場景分布);
  • 目標域:醫院的肺結節 CT 影像(X為\(512 \times 512 \times 1\),\(P(X)\)為醫學影像分布)。
2.3.2 任務(Task):模型的 “目標”

任務定義了模型需要解決的問題,通常表示為?\(T = \{Y, f(\cdot)\}\),其中:

  • Y:標簽空間(Label Space),即模型輸出的類別集合。例如,二分類任務中\(Y = \{0, 1\}\),1000 類分類任務中\(Y = \{0, 1, ..., 999\}\);
  • \(f(\cdot)\):目標預測函數(Target Prediction Function),即模型需要學習的映射關系(\(f: X \rightarrow Y\))。例如,情感分析任務中\(f(\cdot)\)是 “文本→積極 / 消極” 的映射,目標檢測任務中\(f(\cdot)\)是 “圖像→邊界框 + 類別” 的映射。

當兩個任務的Y或\(f(\cdot)\)不同時,我們稱它們為 “不同任務”。例如:

  • 源任務:ImageNet 1000 類分類(Y為 1000 個自然物體類別,\(f(\cdot)\)是 “圖像→物體類別”);
  • 目標任務:肺結節檢測(Y為 “結節 / 非結節”,\(f(\cdot)\)是 “CT 影像→結節位置 + 類別”)。

2.4 數據分布差異:遷移學習的 “攔路虎”

遷移學習的核心挑戰是源域與目標域的分布差異—— 如果分布完全相同,直接用傳統機器學習即可,無需遷移。根據分布差異的類型,可分為三類:

2.4.1 協變量偏移(Covariate Shift)

定義:特征空間X相同,但邊緣概率分布\(P(X)\)不同;條件概率分布\(P(Y|X)\)相同(即 “輸入變了,但輸入到輸出的映射不變”)。 例子

  • 源域:白天拍攝的貓圖像(\(P(X)\)中亮度高的樣本占比高);
  • 目標域:夜晚拍攝的貓圖像(\(P(X)\)中亮度低的樣本占比高);
  • 任務:貓的二分類(\(P(Y|X)\)不變 —— 無論白天黑夜,貓的特征到 “貓” 標簽的映射相同)。

這是最常見的分布差異,微調(Fine-tuning)即可有效解決。

2.4.2 標簽偏移(Label Shift)

定義:特征空間X相同,條件概率分布\(P(X|Y)\)相同,但標簽邊緣分布\(P(Y)\)不同(即 “輸入到標簽的映射不變,但標簽的比例變了”)。 例子

  • 源域:垃圾郵件分類訓練集(\(P(Y)\)中垃圾郵件占 30%,正常郵件占 70%);
  • 目標域:垃圾郵件分類測試集(\(P(Y)\)中垃圾郵件占 60%,正常郵件占 40%);
  • 任務:垃圾郵件二分類(\(P(X|Y)\)不變 —— 垃圾郵件的文本特征(如 “免費”“中獎”)與正常郵件的特征映射不變)。

標簽偏移常見于數據收集偏差場景,可通過調整樣本權重(如對少數類樣本加權)解決。

2.4.3 概念偏移(Concept Shift)

定義:特征空間X相同,但條件概率分布\(P(Y|X)\)不同(即 “輸入到標簽的映射變了”)。 例子

  • 源域:2010 年的 “優質用戶” 分類(\(P(Y|X)\)中 “消費額> 1000 元” 為優質用戶);
  • 目標域:2024 年的 “優質用戶” 分類(\(P(Y|X)\)中 “月活躍度> 20 次” 為優質用戶);
  • 任務:優質用戶二分類(\(P(Y|X)\)變了 —— 輸入特征 “消費額” 到標簽 “優質用戶” 的映射改變)。

概念偏移是最棘手的差異,通常需要重新標注數據或動態調整模型。

3. 遷移學習的分類體系

遷移學習的應用場景多樣,為了更好地選擇方法,學術界通常從 “任務關系”“遷移內容”“分布差異” 三個維度對其分類。

3.1 按學習目標與任務關系分類

該分類基于 “源任務與目標任務是否相同”“目標域是否有標簽”,是最常用的分類方式。

3.1.1 歸納式遷移學習(Inductive Transfer Learning)

核心特征:源任務與目標任務不同(\(T_S \neq T_T\)),目標域有標簽(\(Y_T \neq \emptyset\))。 本質:通過源任務學習 “通用規律”,輔助目標任務的歸納學習。 例子

  • 源任務:ImageNet 1000 類分類(學習通用圖像特征);
  • 目標任務:寵物狗 100 品種分類(利用通用圖像特征,提升狗品種分類精度);
  • 邏輯:識別狗品種需要的 “邊緣、紋理、形狀” 等特征,在 ImageNet 分類中已被充分學習,無需從零訓練。

適用場景:目標任務有少量標注數據,且與源任務存在 “特征復用性”(如所有圖像任務都需要邊緣檢測特征)。

3.1.2 演繹式遷移學習(Transductive Transfer Learning)

核心特征:源任務與目標任務相同(\(T_S = T_T\)),源域有標簽(\(Y_S \neq \emptyset\)),目標域無標簽(\(Y_T = \emptyset\))。 本質:利用源域的標簽信息,解決目標域的無監督學習問題(也稱為 “半監督遷移學習”)。 例子

  • 源域:有標簽的英語情感分析數據(\(Y_S = \{積極, 消極\}\));
  • 目標域:無標簽的中文情感分析數據(\(Y_T = \emptyset\));
  • 任務:情感二分類(\(T_S = T_T\));
  • 邏輯:英語和中文的情感表達有共性(如 “開心” 對應 “happy”),利用英語數據學習情感特征,輔助中文無標簽數據的分類。

適用場景:目標域無標注數據,但與源域任務完全一致(如跨語言、跨場景的相同任務)。

3.1.3 無監督遷移學習(Unsupervised Transfer Learning)

核心特征:源任務與目標任務不同(\(T_S \neq T_T\)),目標域無標簽(\(Y_T = \emptyset\))。 本質:從源域無監督學習 “結構特征”,遷移到目標域的無監督任務中。 例子

  • 源域:無標簽的自然圖像(學習圖像的邊緣、紋理等結構特征);
  • 目標域:無標簽的醫學 CT 影像(利用自然圖像的結構特征,輔助 CT 影像的聚類或分割);
  • 任務:源任務是自然圖像聚類,目標任務是 CT 影像聚類(\(T_S \neq T_T\))。

適用場景:目標域完全無標簽,且與源域共享 “低層次結構特征”(如圖像的邊緣、文本的語法結構)。

3.2 按遷移內容分類

該分類基于 “從源域遷移什么類型的知識”,直接對應具體的技術實現。

3.2.1 參數遷移(Parameter Transfer)

核心思想:源域訓練的模型參數(或部分參數)可作為目標域模型的初始化參數,避免從零訓練。 本質:遷移模型的 “參數級知識”—— 假設源域模型的部分參數(如卷積層)對目標域任務同樣有效。 典型方法:微調(Fine-tuning)、模型蒸餾(Model Distillation)。 例子

  • 源域:用 ImageNet 訓練 ResNet-50,得到卷積層參數(負責提取邊緣、紋理);
  • 目標域:將 ResNet-50 的卷積層參數凍結,僅訓練全連接層(適配目標任務的類別),或微調所有參數(讓卷積層適應目標域特征)。

適用場景:源域與目標域的模型結構相似(如都是圖像分類模型),且低層次特征可復用。

3.2.2 特征遷移(Feature Transfer)

核心思想:將源域和目標域的特征映射到一個 “共享特征空間”,使兩者在該空間中的分布差異最小化,再用共享特征訓練目標任務模型。 本質:遷移 “特征級知識”—— 不直接遷移參數,而是遷移 “特征表示能力”。 典型方法:領域自適應網絡(DANN)、對比學習(Contrastive Learning)。 例子

  • 源域:有標簽的自然圖像,目標域:無標簽的醫學影像;
  • 訓練一個特征提取器,將自然圖像和醫學影像映射到同一空間,使兩者的分布盡可能接近;
  • 用源域的標簽訓練分類器,再用該分類器對目標域的映射特征進行預測。

適用場景:源域與目標域的模型結構不同,但特征可通過映射對齊(如跨模態遷移:文本→圖像)。

3.2.3 實例遷移(Instance Transfer)

核心思想:從源域中篩選出與目標域 “相似” 的樣本,賦予高權重,用于目標域模型的訓練。 本質:遷移 “樣本級知識”—— 假設源域中部分樣本與目標域樣本分布接近,可作為目標域的 “補充數據”。 典型方法:加權樣本訓練(如基于 KNN 的權重計算)、樣本選擇算法。 例子

  • 源域:10 萬張普通汽車圖像,目標域:100 張新能源汽車圖像;
  • 計算源域樣本與目標域樣本的相似度(如余弦距離),篩選出 1000 張最相似的普通汽車圖像;
  • 用這 1000 張高權重樣本 + 100 張目標域樣本訓練新能源汽車分類模型。

適用場景:源域樣本量大,但僅部分樣本與目標域相關(如小樣本目標檢測)。

3.2.4 關系知識遷移(Relational Knowledge Transfer)

核心思想:遷移源域中樣本之間的 “關聯關系”,而非單個樣本或參數。 本質:遷移 “結構級知識”—— 假設源域和目標域的樣本間存在相似的關聯模式。 典型方法:圖譜遷移(Knowledge Graph Transfer)、關系網絡(Relational Networks)。 例子

  • 源域:知識圖譜 “人 - 購買 - 商品”(學習 “用戶 - 行為 - 物品” 的關聯模式);
  • 目標域:推薦系統 “用戶 - 點擊 - 視頻”(遷移 “用戶 - 行為 - 物品” 的關聯模式,提升推薦精度)。

適用場景:源域與目標域的樣本關聯模式相似(如推薦系統、知識圖譜)。

3.3 按領域與任務分布分類

該分類基于 “源域與目標域的分布差異程度”,聚焦于解決 “領域自適應” 問題。

3.3.1 領域自適應(Domain Adaptation, DA)

核心特征:僅存在一個源域和一個目標域,目標是縮小兩者的分布差異。 分類

  • 監督 DA:目標域有少量標簽;
  • 半監督 DA:目標域有部分標簽;
  • 無監督 DA:目標域無標簽(最常見)。

例子:將 “白天的交通場景圖像”(源域)的模型,自適應到 “夜晚的交通場景圖像”(目標域)。

3.3.2 領域泛化(Domain Generalization, DG)

核心特征:存在多個源域,目標是訓練一個 “泛化性強” 的模型,使其能直接應用于未見過的目標域(無需目標域數據)。 本質:從多個源域中學習 “領域不變特征”,應對未知目標域的分布差異。 例子:用 “白天、陰天、雨天” 三個源域的交通圖像訓練模型,使其能直接應用于 “霧天”(未見過的目標域)的交通場景檢測。

適用場景:目標域數據完全不可得(如邊緣設備部署,無法獲取目標場景數據)。

4. 常用遷移學習方法深度解析

了解分類后,我們聚焦于工業界最常用的 4 類方法,深入講解其原理、實現細節與適用場景。

4.1 參數遷移:站在預訓練模型的肩膀上

參數遷移是最直觀、最常用的遷移學習方法,核心是 “復用預訓練模型的參數”。其中,微調(Fine-tuning)?是參數遷移的代表,幾乎所有計算機視覺和 NLP 任務都會用到。

4.1.1 微調(Fine-tuning):原理與策略

原理

  1. 預訓練階段:在大規模源域數據集(如 ImageNet、Wikipedia)上訓練一個基礎模型(如 ResNet、BERT),學習通用知識;
  2. 適配階段:將預訓練模型的輸出層替換為適配目標任務的層(如將 ResNet 的 1000 類輸出改為 10 類);
  3. 微調階段:用目標域數據集訓練整個模型(或部分層),使模型參數適應目標任務。

為什么微調有效? 預訓練模型在大規模數據上學習到了 “通用特征”:

  • 計算機視覺中,底層卷積層學習邊緣、紋理,中層學習部件(如眼睛、耳朵),高層學習整體特征(如貓、狗);
  • NLP 中,BERT 的底層學習詞法、語法,高層學習語義、上下文關聯。

這些通用特征對相似任務(如從 ImageNet 分類到寵物分類)具有極強的復用性,微調只需少量數據即可調整參數,適配目標任務。

4.1.2 凍結層(Layer Freezing):為什么要凍結?如何凍結?

微調時,我們通常不會直接訓練所有層,而是凍結部分底層,僅訓練上層或輸出層。原因如下:

  • 底層學習的是通用特征(如邊緣、紋理),對所有圖像任務都有效,無需修改;
  • 高層學習的是源域特定特征(如 ImageNet 中的 “飛機、船”),需要調整以適配目標任務(如 “貓、狗”);
  • 凍結底層可減少參數數量,降低過擬合風險(尤其目標域數據少時)。

凍結策略

  1. 全凍結底層:僅訓練輸出層。適用于目標域數據極少(如幾百張),且源域與目標域相似性高(如從 “動物分類” 到 “貓品種分類”);
  2. 部分凍結:凍結前 k 層,訓練剩余層。例如,ResNet-50 有 49 個卷積層 + 1 個全連接層,可凍結前 30 層,訓練后 20 層;
  3. 漸進式解凍:先凍結所有底層,訓練輸出層;再解凍部分中層,聯合訓練;最后解凍所有層,用小學習率微調。適用于目標域數據中等(如幾千張),且相似性一般的場景。

經驗法則:目標域數據越少、與源域越相似,凍結的層數越多;反之,凍結層數越少。

4.1.3 部分參數遷移:聚焦任務相關層

當源域與目標域的模型結構不同時(如源域是分類模型,目標域是檢測模型),無法直接微調所有參數,此時可采用部分參數遷移

  • 提取預訓練模型的 “任務無關層”(如 ResNet 的卷積層),作為目標模型的特征提取器;
  • 目標模型的 “任務相關層”(如檢測模型的邊界框回歸層)從零訓練。

例如,目標檢測模型 Faster R-CNN 的 backbone 通常采用預訓練的 ResNet—— 將 ResNet 的卷積層作為特征提取器(遷移參數),RPN 層和 RoI Head 層(任務相關層)從零訓練。

4.2 特征遷移:學習領域無關的通用特征

當源域與目標域的分布差異較大(如自然圖像 vs 醫學影像),直接微調效果不佳時,需要通過特征遷移將兩者的特征對齊到同一空間,縮小分布差異。其中,領域自適應網絡(Domain-Adversarial Neural Network, DANN)?是最經典的方法。

4.2.1 DANN:對抗訓練的魔力

DANN 由紐約大學 Yann LeCun 團隊提出,核心思想源于 GAN(生成對抗網絡),通過 “特征提取器” 與 “領域判別器” 的對抗訓練,學習領域無關的特征

網絡結構:DANN 包含三個核心模塊(如圖 1 所示):

  1. 特征提取器(Feature Extractor, G):輸入源域或目標域數據,輸出特征向量。目標是讓特征既能被標簽預測器正確分類,又能欺騙領域判別器;
  2. 標簽預測器(Label Predictor, F):輸入特征向量,預測源域數據的標簽。目標是最小化源域數據的分類損失(確保特征有任務區分度);
  3. 領域判別器(Domain Discriminator, D):輸入特征向量,判斷特征來自源域還是目標域。目標是最大化領域分類損失(準確區分領域);而特征提取器 G 的目標是最小化領域分類損失(欺騙 D)。

訓練過程

  1. 固定 D,訓練 G 和 F:最小化源域數據的分類損失(讓 F 能正確預測標簽),同時最小化 D 的領域分類損失(讓 G 生成的特征無法被 D 區分領域);
  2. 固定 G,訓練 D:最大化 D 的領域分類損失(讓 D 能區分 G 生成的特征來自源域還是目標域);
  3. 交替訓練,直到收斂。此時,G 生成的特征是 “領域無關且任務相關” 的,可直接用于目標域任務。
4.2.2 特征對齊(Feature Alignment):縮小領域差距

特征對齊是特征遷移的核心目標,除了 DANN 的對抗對齊,還有以下常用方法:

  • 統計對齊:通過最小化源域和目標域特征的統計差異(如均值、方差、最大均值差異 MMD),實現特征對齊。例如,MMD 通過計算兩個分布的核函數距離,最小化該距離以對齊特征;
  • 對比對齊:通過對比學習,讓源域和目標域的相似樣本在特征空間中靠近,不同樣本遠離。例如,SimCLR 通過數據增強生成正樣本對,最小化正樣本對的距離,最大化負樣本對的距離;
  • 自監督對齊:利用目標域的無標簽數據進行自監督學習(如掩碼圖像建模 MAE),讓目標域特征與源域特征的表示方式一致。

4.3 實例遷移:給相似樣本 “加權投票”

當源域樣本量大,但僅部分樣本與目標域相關時,實例遷移是最優選擇。其核心是 “篩選相似樣本,加權訓練”。

4.3.1 樣本權重計算:距離與密度的考量

實例遷移的關鍵是計算源域樣本與目標域樣本的 “相似度”,并賦予相似樣本更高的權重。常用的權重計算方法:

  1. 基于距離的權重:計算源域樣本xiS?與目標域樣本xjT?的距離(如歐氏距離、余弦距離),距離越小,權重越大。公式如下:
    wi?=1+dist(xiS?,xˉT)1?,其中xˉT是目標域樣本的均值;
  2. 基于密度的權重:如果源域樣本xiS?位于目標域樣本的 “高密度區域”(即周圍有很多目標域樣本),則權重更大。可通過 KNN 或核密度估計(KDE)計算;
  3. 基于模型的權重:用目標域少量標簽訓練一個初步模型,用該模型預測源域樣本的置信度,置信度高的樣本(即模型認為與目標域相似的樣本)權重更大。
4.3.2 實例遷移的適用場景與局限

適用場景

  • 源域樣本量大,但存在大量噪聲或無關樣本(如網絡爬取的圖像數據);
  • 目標域樣本極少(如幾十張),需要補充相似樣本以避免過擬合。

局限

  • 若源域與目標域的分布差異過大,可能篩選不出相似樣本,遷移無效;
  • 若源域中存在 “偽相似樣本”(表面相似但標簽不同),會導致負遷移。

4.4 關系知識遷移:遷移 “關聯模式”

關系知識遷移適用于 “樣本間關聯模式相似” 的場景,如推薦系統、知識圖譜、邏輯推理任務。其核心是遷移 “關系結構”,而非單個樣本或參數。

4.4.1 關系知識的表示與遷移

關系知識通常用 “圖結構” 表示,例如:

  • 推薦系統中,“用戶 - 物品 - 評分” 構成 bipartite 圖,關系是 “用戶對物品的偏好”;
  • 知識圖譜中,“實體 - 關系 - 實體” 構成三元組,關系是 “實體間的語義關聯”。

遷移方法通常分為兩步:

  1. 關系建模:在源域中訓練一個關系模型(如圖神經網絡 GNN),學習樣本間的關聯模式;
  2. 關系遷移:將源域的關系模型參數(或關系嵌入)作為目標域關系模型的初始化,或直接復用關系推理規則。
4.4.2 跨任務關系遷移案例

以推薦系統的跨領域遷移為例:

  • 源域:“用戶 - 電影 - 評分” 數據,學習用戶的觀影偏好關系(如喜歡科幻電影的用戶也喜歡動作電影);
  • 目標域:“用戶 - 書籍 - 評分” 數據,遷移源域的用戶偏好關系(如喜歡科幻電影的用戶可能喜歡科幻書籍);
  • 實現:用源域數據訓練一個 GNN 模型,學習用戶和物品的嵌入;將用戶嵌入遷移到目標域,作為書籍推薦的初始嵌入,提升推薦精度。

5. 遷移學習實踐:PyTorch 代碼實例

理論講完后,我們通過兩個完整的代碼實例,分別演示計算機視覺(圖像分類)?和自然語言處理(文本分類)?中的遷移學習應用。這兩個案例覆蓋了工業界最常見的遷移場景,代碼可直接運行。

5.1 案例 1:計算機視覺 ——CIFAR-10 圖像分類(ResNet 微調)

5.1.1 實驗背景與目標
  • 源域:ImageNet 數據集(130 萬張圖像,1000 類),預訓練模型為 ResNet-18;
  • 目標域:CIFAR-10 數據集(5 萬張訓練圖,1 萬張測試圖,10 類:飛機、汽車、鳥、貓、鹿、狗、青蛙、馬、船、卡車);
  • 任務:CIFAR-10 圖像分類,對比 “從零訓練 ResNet-18” 和 “微調預訓練 ResNet-18” 的性能差異;
  • 實驗目標:驗證微調在數據有限場景下的優勢(我們將 CIFAR-10 訓練集抽樣至 10%,模擬小樣本場景)。
5.1.2 實驗環境準備

需安裝以下庫:

bash

pip install torch torchvision matplotlib numpy scikit-learn
5.1.3 數據加載與預處理

CIFAR-10 數據集可通過torchvision.datasets直接下載,預處理需與預訓練 ResNet-18 的輸入要求一致(ImageNet 的預處理方式):

python

運行

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, Subset
from torchvision import datasets, transforms
from torchvision.models import resnet18
import matplotlib.pyplot as plt
import numpy as np
from sklearn.model_selection import train_test_split# 1. 配置超參數
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
BATCH_SIZE = 64
EPOCHS = 20
LEARNING_RATE = 1e-4  # 微調學習率通常較小
NUM_CLASSES = 10  # CIFAR-10有10類
SAMPLE_RATIO = 0.1  # 抽樣10%的訓練集,模擬小樣本場景# 2. 數據預處理(與ImageNet預訓練一致)
# 訓練集:隨機裁剪、水平翻轉、歸一化
train_transform = transforms.Compose([transforms.RandomResizedCrop(224),  # ResNet-18輸入尺寸為224x224transforms.RandomHorizontalFlip(),transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406],  # ImageNet均值std=[0.229, 0.224, 0.225])   # ImageNet標準差
])# 測試集:僅 resize 和歸一化
test_transform = transforms.Compose([transforms.Resize(256),transforms.CenterCrop(224),transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225])
])# 3. 加載CIFAR-10數據集
full_train_dataset = datasets.CIFAR10(root="./data", train=True, download=True, transform=train_transform
)
test_dataset = datasets.CIFAR10(root="./data", train=False, download=True, transform=test_transform
)# 4. 抽樣10%的訓練集,模擬小樣本場景
train_indices, _ = train_test_split(range(len(full_train_dataset)),test_size=1 - SAMPLE_RATIO,random_state=42,stratify=full_train_dataset.targets  # 保持類別分布一致
)
train_dataset = Subset(full_train_dataset, train_indices)# 5. 創建DataLoader
train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2
)
test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2
)# 查看數據集規模
print(f"訓練集樣本數:{len(train_dataset)}")  # 約5000張
print(f"測試集樣本數:{len(test_dataset)}")    # 10000張
5.1.4 構建兩種模型:從零訓練 vs 微調

我們構建兩個 ResNet-18 模型,分別用于對比:

  1. 從零訓練模型:所有參數隨機初始化;
  2. 微調模型:加載 ImageNet 預訓練參數,替換輸出層,凍結前 10 層(卷積層),訓練剩余層。

python

運行

def build_model(fine_tune=True):"""構建模型:fine_tune=True 表示微調預訓練模型,False表示從零訓練"""if fine_tune:# 1. 加載預訓練ResNet-18(默認加載ImageNet預訓練參數)model = resnet18(pretrained=True)  # PyTorch 1.13+需用 weights=ResNet18_Weights.DEFAULT# 2. 替換輸出層(適配CIFAR-10的10類)in_features = model.fc.in_features  # 獲取全連接層輸入特征數model.fc = nn.Linear(in_features, NUM_CLASSES)# 3. 凍結前10層(卷積層),僅訓練后幾層和全連接層# ResNet-18的層結構:conv1 -> layer1 -> layer2 -> layer3 -> layer4 -> fc# 凍結 conv1 + layer1 + layer2 的前幾層(共10層)freeze_layer_num = 10for i, (name, param) in enumerate(model.named_parameters()):if i < freeze_layer_num:param.requires_grad = False  # 凍結參數,不更新else:param.requires_grad = True   # 解凍參數,更新else:# 從零訓練:不加載預訓練參數,所有參數隨機初始化model = resnet18(pretrained=False)in_features = model.fc.in_featuresmodel.fc = nn.Linear(in_features, NUM_CLASSES)# 所有參數均可訓練for param in model.parameters():param.requires_grad = True# 移動模型到GPU/CPUmodel = model.to(DEVICE)return model# 構建兩個模型
model_scratch = build_model(fine_tune=False)  # 從零訓練
model_finetune = build_model(fine_tune=True)  # 微調
5.1.5 模型訓練與評估函數

定義訓練和評估函數,用于統一訓練兩個模型:

python

運行

def train_model(model, train_loader, criterion, optimizer, epoch):"""訓練模型一個epoch"""model.train()  # 開啟訓練模式total_loss = 0.0total_correct = 0total_samples = 0for batch_idx, (data, target) in enumerate(train_loader):# 數據移動到DEVICEdata, target = data.to(DEVICE), target.to(DEVICE)# 前向傳播output = model(data)loss = criterion(output, target)# 反向傳播與優化optimizer.zero_grad()loss.backward()optimizer.step()# 統計損失和準確率total_loss += loss.item() * data.size(0)_, predicted = torch.max(output, 1)total_correct += (predicted == target).sum().item()total_samples += data.size(0)# 每100個batch打印一次進度if batch_idx % 100 == 0:print(f"Epoch [{epoch+1}/{EPOCHS}], Batch [{batch_idx}/{len(train_loader)}], "f"Loss: {loss.item():.4f}, Acc: {100.*total_correct/total_samples:.2f}%")# 計算一個epoch的平均損失和準確率avg_loss = total_loss / total_samplesavg_acc = 100. * total_correct / total_samplesreturn avg_loss, avg_accdef evaluate_model(model, test_loader, criterion):"""評估模型在測試集上的性能"""model.eval()  # 開啟評估模式(關閉dropout、batchnorm更新)total_loss = 0.0total_correct = 0total_samples = 0with torch.no_grad():  # 禁用梯度計算,節省內存for data, target in test_loader:data, target = data.to(DEVICE), target.to(DEVICE)output = model(data)loss = criterion(output, target)total_loss += loss.item() * data.size(0)_, predicted = torch.max(output, 1)total_correct += (predicted == target).sum().item()total_samples += data.size(0)avg_loss = total_loss / total_samplesavg_acc = 100. * total_correct / total_samplesprint(f"Test Loss: {avg_loss:.4f}, Test Acc: {avg_acc:.2f}%")return avg_loss, avg_acc
5.1.6 訓練兩個模型并記錄結果

使用相同的損失函數(交叉熵)和優化器(Adam)訓練兩個模型,記錄訓練過程中的損失和準確率:

python

運行

# 定義損失函數和優化器(兩個模型使用相同配置)
criterion = nn.CrossEntropyLoss()
optimizer_scratch = optim.Adam(model_scratch.parameters(), lr=LEARNING_RATE)
optimizer_finetune = optim.Adam(model_finetune.parameters(), lr=LEARNING_RATE)# 記錄訓練過程
history = {"scratch": {"train_loss": [], "train_acc": [], "test_loss": [], "test_acc": []},"finetune": {"train_loss": [], "train_acc": [], "test_loss": [], "test_acc": []}
}# 訓練從零開始的模型
print("="*50)
print("Training Model from Scratch")
print("="*50)
for epoch in range(EPOCHS):train_loss, train_acc = train_model(model_scratch, train_loader, criterion, optimizer_scratch, epoch)test_loss, test_acc = evaluate_model(model_scratch, test_loader, criterion)# 記錄結果history["scratch"]["train_loss"].append(train_loss)history["scratch"]["train_acc"].append(train_acc)history["scratch"]["test_loss"].append(test_loss)history["scratch"]["test_acc"].append(test_acc)# 訓練微調模型
print("\n" + "="*50)
print("Training Fine-tuned Model")
print("="*50)
for epoch in range(EPOCHS):train_loss, train_acc = train_model(model_finetune, train_loader, criterion, optimizer_finetune, epoch)test_loss, test_acc = evaluate_model(model_finetune, test_loader, criterion)# 記錄結果history["finetune"]["train_loss"].append(train_loss)history["finetune"]["train_acc"].append(train_acc)history["finetune"]["test_loss"].append(test_loss)history["finetune"]["test_acc"].append(test_acc)
5.1.7 結果可視化與分析

繪制訓練 / 測試損失和準確率曲線,對比兩個模型的性能:

python

運行

# 設置中文字體
plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei']
plt.rcParams['axes.unicode_minus'] = False# 創建2x2的子圖
fig, axes = plt.subplots(2, 2, figsize=(15, 12))# 1. 訓練損失對比
axes[0, 0].plot(range(1, EPOCHS+1), history["scratch"]["train_loss"], label="從零訓練", marker='o', linewidth=2)
axes[0, 0].plot(range(1, EPOCHS+1), history["finetune"]["train_loss"], label="微調", marker='s', linewidth=2)
axes[0, 0].set_title("訓練損失對比", fontsize=14)
axes[0, 0].set_xlabel("Epoch", fontsize=12)
axes[0, 0].set_ylabel("損失", fontsize=12)
axes[0, 0].legend()
axes[0, 0].grid(True)# 2. 訓練準確率對比
axes[0, 1].plot(range(1, EPOCHS+1), history["scratch"]["train_acc"], label="從零訓練", marker='o', linewidth=2)
axes[0, 1].plot(range(1, EPOCHS+1), history["finetune"]["train_acc"], label="微調", marker='s', linewidth=2)
axes[0, 1].set_title("訓練準確率對比", fontsize=14)
axes[0, 1].set_xlabel("Epoch", fontsize=12)
axes[0, 1].set_ylabel("準確率(%)", fontsize=12)
axes[0, 1].legend()
axes[0, 1].grid(True)# 3. 測試損失對比
axes[1, 0].plot(range(1, EPOCHS+1), history["scratch"]["test_loss"], label="從零訓練", marker='o', linewidth=2)
axes[1, 0].plot(range(1, EPOCHS+1), history["finetune"]["test_loss"], label="微調", marker='s', linewidth=2)
axes[1, 0].set_title("測試損失對比", fontsize=14)
axes[1, 0].set_xlabel("Epoch", fontsize=12)
axes[1, 0].set_ylabel("損失", fontsize=12)
axes[1, 0].legend()
axes[1, 0].grid(True)# 4. 測試準確率對比
axes[1, 1].plot(range(1, EPOCHS+1), history["scratch"]["test_acc"], label="從零訓練", marker='o', linewidth=2)
axes[1, 1].plot(range(1, EPOCHS+1), history["finetune"]["test_acc"], label="微調", marker='s', linewidth=2)
axes[1, 1].set_title("測試準確率對比", fontsize=14)
axes[1, 1].set_xlabel("Epoch", fontsize=12)
axes[1, 1].set_ylabel("準確率(%)", fontsize=12)
axes[1, 1].legend()
axes[1, 1].grid(True)# 保存圖片
plt.tight_layout()
plt.savefig("transfer_learning_cifar10.png", dpi=300)
plt.show()# 輸出最終結果
print("\n" + "="*50)
print("最終結果對比")
print("="*50)
print(f"從零訓練模型 - 測試準確率:{history['scratch']['test_acc'][-1]:.2f}%")
print(f"微調模型 - 測試準確率:{history['finetune']['test_acc'][-1]:.2f}%")
print(f"準確率提升:{history['finetune']['test_acc'][-1] - history['scratch']['test_acc'][-1]:.2f}%")
5.1.8 預期結果與分析

在小樣本場景(CIFAR-10 訓練集僅 5000 張)下,預期結果如下:

  • 從零訓練模型:測試準確率約 65%-70%,訓練后期可能過擬合(訓練準確率高,測試準確率低);
  • 微調模型:測試準確率約 80%-85%,收斂速度快(前 5 個 epoch 即可達到較高準確率),過擬合風險低。

原因分析

  1. 預訓練 ResNet-18 的底層卷積層學習了通用圖像特征(邊緣、紋理),無需在小樣本上重新學習;
  2. 僅訓練上層和全連接層,參數數量少,降低了過擬合風險;
  3. 微調的學習率小,避免了破壞預訓練的通用特征。

5.2 案例 2:自然語言處理 ——IMDB 情感分析(BERT 微調)

5.2.1 實驗背景與目標
  • 源域:Wikipedia 英文語料,預訓練模型為 BERT-base-uncased(12 層 Transformer,768 維嵌入);
  • 目標域:IMDB 電影評論數據集(5 萬條評論,正 / 負情感各 2.5 萬條);
  • 任務:IMDB 情感二分類,對比 “從零訓練 LSTM” 和 “微調 BERT” 的性能差異;
  • 實驗目標:驗證 BERT 微調在文本分類任務中的優勢,尤其是在小樣本場景下。
5.2.2 實驗環境準備

需安裝 Hugging Face 的transformers庫(用于加載 BERT 模型和 Tokenizer):

bash

pip install torch transformers datasets matplotlib numpy scikit-learn
5.2.3 數據加載與預處理

使用datasets庫加載 IMDB 數據集,并用 BERT 的 Tokenizer 處理文本(將文本轉換為 BERT 的輸入格式:token_id、attention_mask、token_type_id):

python

運行

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from transformers import BertTokenizer, BertForSequenceClassification
from datasets import load_dataset
import matplotlib.pyplot as plt
import numpy as np
from sklearn.model_selection import train_test_split# 1. 配置超參數
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
BATCH_SIZE = 32
EPOCHS = 5
LEARNING_RATE = 2e-5  # BERT微調學習率通常很小(避免破壞預訓練知識)
NUM_CLASSES = 2  # 情感二分類(正/負)
SAMPLE_RATIO = 0.1  # 抽樣10%的訓練集,模擬小樣本場景
BERT_MODEL_NAME = "bert-base-uncased"  # 小寫英文BERT模型# 2. 加載IMDB數據集
dataset = load_dataset("imdb")
train_dataset = dataset["train"]
test_dataset = dataset["test"]# 3. 抽樣10%的訓練集
train_indices, _ = train_test_split(range(len(train_dataset)),test_size=1 - SAMPLE_RATIO,random_state=42,stratify=train_dataset["label"]  # 保持情感分布一致
)
train_dataset = train_dataset.select(train_indices)# 4. 加載BERT Tokenizer
tokenizer = BertTokenizer.from_pretrained(BERT_MODEL_NAME)# 5. 文本預處理函數:將文本轉換為BERT輸入格式
def preprocess_function(examples):return tokenizer(examples["text"],padding="max_length",  # 填充到BERT的最大輸入長度(512)truncation=True,       # 截斷超過512的文本max_length=512)# 6. 應用預處理函數
train_dataset = train_dataset.map(preprocess_function, batched=True)
test_dataset = test_dataset.map(preprocess_function, batched=True)# 7. 轉換為PyTorch張量格式
train_dataset.set_format(type="torch",columns=["input_ids", "attention_mask", "token_type_ids", "label"],device=DEVICE
)
test_dataset.set_format(type="torch",columns=["input_ids", "attention_mask", "token_type_ids", "label"],device=DEVICE
)# 8. 創建DataLoader
train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True
)
test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False
)# 查看數據集規模
print(f"訓練集樣本數:{len(train_dataset)}")  # 約2500條
print(f"測試集樣本數:{len(test_dataset)}")    # 25000條
5.2.4 構建兩種模型:從零訓練 LSTM vs 微調 BERT

構建兩個模型用于對比:

  1. 從零訓練 LSTM:用隨機初始化的詞嵌入和 LSTM 構建文本分類模型;
  2. 微調 BERT:加載預訓練 BERT 模型,添加分類頭,微調部分層。

python

運行

class LSTMClassifier(nn.Module):"""從零訓練的LSTM文本分類模型"""def __init__(self, vocab_size, embedding_dim, hidden_dim, num_classes):super(LSTMClassifier, self).__init__()# 詞嵌入層(隨機初始化)self.embedding = nn.Embedding(vocab_size, embedding_dim)# LSTM層self.lstm = nn.LSTM(embedding_dim, hidden_dim, num_layers=2, bidirectional=True, batch_first=True)# 全連接層(雙向LSTM輸出維度為2*hidden_dim)self.fc = nn.Linear(hidden_dim * 2, num_classes)# Dropout層(防止過擬合)self.dropout = nn.Dropout(0.5)def forward(self, input_ids, attention_mask=None):# 輸入:input_ids (batch_size, seq_len)# 詞嵌入:(batch_size, seq_len, embedding_dim)embedded = self.dropout(self.embedding(input_ids))# LSTM輸出:(batch_size, seq_len, 2*hidden_dim)lstm_out, _ = self.lstm(embedded)# 取最后一個時間步的輸出:(batch_size, 2*hidden_dim)last_hidden = lstm_out[:, -1, :]# 全連接層輸出:(batch_size, num_classes)logits = self.fc(self.dropout(last_hidden))return logitsdef build_models():# 1. 構建從零訓練的LSTM模型# 詞表大小:使用BERT的詞表大小(避免重新構建詞表)vocab_size = tokenizer.vocab_sizelstm_model = LSTMClassifier(vocab_size=vocab_size,embedding_dim=128,  # 詞嵌入維度hidden_dim=256,     # LSTM隱藏層維度num_classes=NUM_CLASSES).to(DEVICE)# 2. 構建微調的BERT模型# 加載預訓練BERT,添加分類頭(num_labels=2)bert_model = BertForSequenceClassification.from_pretrained(BERT_MODEL_NAME,num_labels=NUM_CLASSES).to(DEVICE)# 凍結BERT的前6層(僅訓練后6層和分類頭)freeze_layer_num = 6for i, (name, param) in enumerate(bert_model.bert.named_parameters()):# BERT的層名格式:layer.0.attention.self.query.weight(第0層)if "layer." in name:layer_idx = int(name.split("layer.")[1].split(".")[0])if layer_idx < freeze_layer_num:param.requires_grad = False# 嵌入層也凍結elif "embeddings" in name:param.requires_grad = Falsereturn lstm_model, bert_model# 構建兩個模型
model_lstm, model_bert = build_models()

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

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

相關文章

嵌入式人別再瞎折騰了!這8個開源項目,解決按鍵/隊列/物聯網所有痛點,小白也能抄作業

嵌入式人別再瞎折騰了&#xff01;這8個開源項目&#xff0c;解決按鍵/隊列/物聯網所有痛點&#xff0c;小白也能抄作業 你是不是也有過這樣的崩潰時刻&#xff1a;想做個按鍵控制&#xff0c;結果長按、連擊、組合鍵的邏輯寫了200行if-else&#xff0c;最后還時不時串鍵&#…

C++篇(7)string類的模擬實現

一、string的成員變量string和數據結構中的順序表類似&#xff0c;本質上可以理解成字符順序表&#xff0c;其成員變量仍然是_str&#xff0c;_size和_capacity。但是&#xff0c;C標準庫里面也有一個string&#xff0c;和我們要自己實現的string類沖突了&#xff0c;該如何解決…

【直接套模板】如何用 Web of Science 精準檢索文獻?

在文獻檢索的時候遇到一些問題&#xff0c;單獨使用關鍵詞檢索出來的文章數量太多&#xff0c;如果是多加一些限定詞&#xff0c;又什么都檢索不到&#xff1a;比如我明明知道某篇論文已經發表&#xff0c;但在 Web of Science (WoS) 里卻檢索不到。這其實和檢索式的寫法密切相…

HTTP 協議:從原理到應用的深度剖析

一、什么是HTTP協議&#xff1f;HTTP協議&#xff0c;全稱 Hyper Text Transfer Protocol&#xff08;超?本傳輸協議&#xff09;的縮寫&#xff0c;是?于服務器與客戶端瀏覽器之間傳輸超?本數據&#xff08;?字、圖?、視頻、?頻&#xff09;的應?層協議。它規定了客戶端…

【算法--鏈表】138.隨機鏈表的復制--通俗講解

算法通俗講解推薦閱讀 【算法–鏈表】83.刪除排序鏈表中的重復元素–通俗講解 【算法–鏈表】刪除排序鏈表中的重復元素 II–通俗講解 【算法–鏈表】86.分割鏈表–通俗講解 【算法】92.翻轉鏈表Ⅱ–通俗講解 【算法–鏈表】109.有序鏈表轉換二叉搜索樹–通俗講解 【算法–鏈表…

為什么現在企業注重數據可視化?一文講清可視化數據圖表怎么做

目錄 一、企業注重數據可視化的原因 1.提升數據理解效率 2.發現數據中的規律和趨勢 3.促進企業內部溝通與協作 4.增強決策的科學性 5.提升企業競爭力 二、可視化數據圖表的基本概念 1.常見的可視化圖表類型 2.可視化圖表的構成要素 3.可視化圖表的設計原則 三、制作…

Cursor 輔助開發:快速搭建 Flask + Vue 全棧 Demo 的實戰記錄

Cursor 輔助開發&#xff1a;快速搭建 Flask Vue 全棧 Demo 的實戰記錄 &#x1f31f; Hello&#xff0c;我是摘星&#xff01; &#x1f308; 在彩虹般絢爛的技術棧中&#xff0c;我是那個永不停歇的色彩收集者。 &#x1f98b; 每一個優化都是我培育的花朵&#xff0c;每一個…

實戰:用 Python 搭建 MCP 服務 —— 模型上下文協議(Model Context Protocol)應用指南

&#x1f4cc; 實戰&#xff1a;用 Python 搭建 MCP 服務 —— 模型上下文協議&#xff08;Model Context Protocol&#xff09;應用指南 標簽&#xff1a;#MCP #AI工程化 #Python #LLM上下文管理 #Agent架構&#x1f3af; 引言&#xff1a;為什么需要 MCP&#xff1f; 在構建大…

宋紅康 JVM 筆記 Day16|垃圾回收相關概念

一、今日視頻區間 P154-P168 二、一句話總結 System.gc()的理解&#xff1b;內存溢出與內存泄漏&#xff1b;Stop The World;垃圾回收的并行與并發&#xff1b;安全點與安全區域&#xff1b;再談引用&#xff1a;強引用&#xff1b;再談引用&#xff1a;軟引用&#xff1b;再談…

OpenCV 高階 圖像金字塔 用法解析及案例實現

目錄 一、什么是圖像金字塔&#xff1f; 二、圖像金字塔的核心作用 三、圖像金字塔的核心操作&#xff1a;上下采樣 3.1 向下采樣&#xff08; pyrDown &#xff09;&#xff1a;從高分辨率到低分辨率 1&#xff09;原理與步驟 2&#xff09;關鍵注意事項 3&#xff09;…

【ARMv7】系統復位上電后的程序執行過程

引子&#xff1a;對于ARMv7-M系列SOC來說&#xff0c;上電后程序復位執行的過程相對來說比較簡單&#xff0c;因為絕大部分芯片&#xff0c;都是XIP&#xff08;eXecute In Place&#xff0c;就地執行&#xff09;模式執行程序&#xff0c;不需要通過BooROM->PL(preloader)-…

神經網絡的初始化:權重與偏置的數學策略

在深度學習中&#xff0c;神經網絡的初始化是一個看似不起眼&#xff0c;卻極其重要的環節。它就像是一場漫長旅程的起點&#xff0c;起點的選擇是否恰當&#xff0c;往往決定了整個旅程的順利程度。今天&#xff0c;就讓我們一起深入探討神經網絡初始化的數學策略&#xff0c;…

第 16 篇:服務網格的未來 - Ambient Mesh, eBPF 與 Gateway API

系列文章:《Istio 服務網格詳解》 第 16 篇:服務網格的未來 - Ambient Mesh, eBPF 與 Gateway API 本篇焦點: 反思當前主流 Sidecar 模式的挑戰與權衡。 深入了解 Istio 官方的未來演進方向:Ambient Mesh (無邊車模式)。 探討革命性技術 eBPF 將如何從根本上重塑服務網格的…

擺動序列:如何讓數組“上下起伏”地最長?

文章目錄摘要描述題解答案題解代碼分析代碼解析示例測試及結果時間復雜度空間復雜度總結摘要 今天我們要聊的是 LeetCode 第 376 題 —— 擺動序列。 題目的意思其實很有意思&#xff1a;如果一個序列里的相鄰差值能保持正負交替&#xff0c;就叫做“擺動”。比如 [1, 7, 4, 9…

玩轉Docker | 使用Docker部署KissLists任務管理工具

玩轉Docker | 使用Docker部署KissLists任務管理工具 前言 一、KissLists介紹 KissLists簡介 KissLists核心特點 KissLists注意事項 二、系統要求 環境要求 環境檢查 Docker版本檢查 檢查操作系統版本 三、部署KissLists服務 下載KissLists鏡像 編輯部署文件 創建容器 檢查容器狀…

【滑動窗口】C++高效解決子數組問題

個人主頁 &#xff1a; zxctscl 專欄 【C】、 【C語言】、 【Linux】、 【數據結構】、 【算法】 如有轉載請先通知 文章目錄前言1 209. 長度最小的子數組1.1 分析1.2 代碼2 3. 無重復字符的最長子串2.1 分析2.2 代碼3 1004. 最大連續1的個數 III3.1 分析3.2 代碼4 1658. 將 x …

[rStar] 搜索代理(MCTS/束搜索)

第2章&#xff1a;搜索代理(MCTS/束搜索) 歡迎回到rStar 在前一章中&#xff0c;我們學習了求解協調器&#xff0c;它就像是解決數學問題的項目經理。 它組織整個過程&#xff0c;但本身并不進行"思考"&#xff0c;而是將這項工作委托給其專家團隊。 今天&#x…

Electron 核心模塊速查表

為了更全面地覆蓋常用 API&#xff0c;以下表格補充了更多實用方法和場景化示例&#xff0c;同時保持格式清晰易讀。 一、主進程模塊 模塊名核心用途關鍵用法 示例注意事項app應用生命周期管理? 退出應用&#xff1a;app.quit()? 重啟應用&#xff1a;app.relaunch() 后需…

Qt C++ 圖形繪制完全指南:從基礎到進階實戰

Qt C 圖形繪制完全指南&#xff1a;從基礎到進階實戰 前言 Qt框架提供了強大的2D圖形繪制能力&#xff0c;通過QPainter類及其相關組件&#xff0c;開發者可以輕松實現各種復雜的圖形繪制需求。本文將系統介紹Qt圖形繪制的核心技術&#xff0c;并通過實例代碼演示各種繪制技巧…

二分搜索邊界問題

在使用二分搜索的時候&#xff0c;更新條件不總是相同&#xff0c;雖然說使用bS目的就是為了target&#xff0c;但也有如下幾種情況&#xff1a;求第一個target的索引求第一個>target的索引求第一個>target的索引求最后一個target的索引求最后一個<target的索引求最后…