CV 醫學影像分類、分割、目標檢測,之【皮膚病分類】項目拆解

CV 醫學影像分類、分割、目標檢測,之【皮膚病分類】項目拆解

    • 第1-12行:導入庫
    • 第14-17行:讀取標簽文件
    • 第19-21行:獲取疾病名稱
    • 第23-26行:獲取圖片名列表
    • 第28-35行:篩選有標簽的圖片
    • 第38-43行:提取標簽
    • 第47-51行:創建字典映射
    • 第53-59行:創建類別ID映射
    • 第61-70行:獲取篩選后圖片的標簽
    • 第72-90行:定義數據變換
    • 第92-107行:自定義數據集類
    • 第114-120行:劃分訓練集測試集
    • 第122-130行:創建數據加載器
    • 第132-146行:可視化數據
    • 第148-151行:加載預訓練模型
    • 第153-156行:定義損失和優化器
    • 第158-160行:GPU設置
    • 第163-211行:訓練函數
    • 第213-228行:訓練循環
    • 第230-233行:繪制損失曲線
    • 第235-260行:加載最佳模型測試
    • 第262-305行:預測新圖片
    • 核心流程總結
    • 替換不同模型

?


目標:構建一個基于深度學習的皮膚病分類系統,能夠自動識別8種皮膚病類型(黑色素瘤、黑素細胞痣、基底細胞癌、光化性角化病、良性角化病、皮膚纖維瘤、血管病變、鱗狀細胞癌)

皮膚病數據集可以在阿里云天池里面搜索獲取。

def skin_classification_main():"""皮膚病分類系統主函數 - 領導式全局規劃"""# 階段1:標簽數據讀取與處理部門label_processor = LabelProcessor()skinDisease, pic_data, df_Key = label_processor.load_and_parse_labels()# 階段2:圖片文件篩選與整理部門  image_organizer = ImageOrganizer()image_organizer.copy_valid_images(pic_data)# 階段3:類別映射構建部門mapping_builder = MappingBuilder()skinLable_dic, class_to_id, id_to_class = mapping_builder.create_mappings(pic_data, df_Key, skinDisease)# 階段4:數據集構建部門dataset_builder = DatasetBuilder()train_loader, test_loader = dataset_builder.create_dataloaders(class_to_id, skinLable_dic)# 階段5:模型構建部門model_builder = ModelBuilder()model, loss_fn, optimizer = model_builder.build_training_components()# 階段6:訓練執行部門trainer = ModelTrainer()train_history = trainer.train_model(model, train_loader, test_loader, epochs=150)# 階段7:訓練結果可視化部門visualizer = TrainingVisualizer()visualizer.plot_training_curves(train_history)# 階段8:模型評估部門evaluator = ModelEvaluator()final_performance = evaluator.evaluate_best_model(model, test_loader)# 階段9:預測展示部門predictor = PredictionDemo()predictor.demo_batch_prediction(model, test_loader, id_to_class)# 階段10:單圖預測部門single_predictor = SingleImagePredictor()result = single_predictor.predict_single_image(model, image_path, id_to_class)return model, result

第1-12行:導入庫

from PIL import Image

問1: PIL是什么縮寫?
答1: Python Imaging Library(Python圖像處理庫)

問2: 為什么用from…import而不是import?
答2: 只導入需要的Image類,避免命名空間污染

問3: Image類能做什么?
答3: 打開、創建、修改、保存各種格式的圖片文件

import torch

問4: torch的核心是什么?
答4: 張量(Tensor)運算和自動微分

問5: 什么是張量?
答5: 多維數組,0維是標量,1維是向量,2維是矩陣,3維以上叫張量

from torch.utils import data

問6: utils是什么?
答6: utilities工具集,data是數據加載工具

問7: 為什么需要專門的數據加載工具?
答7: 批量加載、打亂順序、多線程預處理

import numpy as np

問8: numpy和torch的區別?
答8: numpy在CPU運算,torch可在GPU運算且支持自動求導

import pandas as pd

問9: pandas擅長什么?
答9: 表格數據處理,像Excel一樣操作數據

from torchvision import transforms

問10: transforms是做什么變換?
答10: 圖像預處理:裁剪、旋轉、歸一化等

import torchvision

問11: torchvision和torch的關系?
答11: torchvision是torch的計算機視覺擴展包

import matplotlib.pyplot as plt

問12: pyplot的plt是約定俗成嗎?
答12: 是的,社區約定,便于代碼交流

import torch.nn.functional as F

問13: functional和nn.Module的區別?
答13: functional是無狀態函數,Module是有參數的層

import torch.nn as nn

問14: nn代表什么?
答14: Neural Network,神經網絡模塊

from tqdm import tqdm

問15: tqdm是什么意思?
答15: 阿拉伯語"進展",用來顯示進度條

import os
import glob
import shutil

問16: 這三個都是文件操作,有什么區別?
答16: os基礎操作,glob模式匹配,shutil高級操作


第14-17行:讀取標簽文件

df=pd.read_table('./skin_label.txt',sep='\t',header='infer')

問17: ./是什么路徑?
答17: 當前目錄,相對路徑

問18: header='infer’是什么意思?
答18: 自動推斷第一行是否為列名

df_Key=np.array(df.iloc[:,1:])

問19: iloc和loc的區別?
答19: iloc用整數位置索引,loc用標簽索引

問20: 為什么轉成numpy數組?
答20: numpy運算更快,且后續要用argmax

df_Key.shape

問21: shape返回什么?
答21: 元組(行數, 列數),這里是(6000, 9)


第19-21行:獲取疾病名稱

skinDisease=df.columns[1:].to_numpy()

問22: columns是什么?
答22: DataFrame的列名,Index對象

問23: to_numpy()和values的區別?
答23: to_numpy()是新方法,values將被棄用

skinDisease

問24: 不加print為什么也能輸出?
答24: Jupyter/交互模式下,最后一個表達式自動顯示


第23-26行:獲取圖片名列表

pic_data=np.array(df.iloc[:,0])

問25: 第0列是什么?
答25: 圖片文件名列

pic_data=pic_data.tolist()

問26: 為什么要轉成list?
答26: 后面要用in判斷,list的in操作比array快

len(pic_data)

問27: len對不同對象的含義?
答27: list是元素個數,string是字符數,dict是鍵值對數


第28-35行:篩選有標簽的圖片

imgs=glob.glob('./data/skin_data/*.jpg')

問28: 是什么通配符?
答28: 匹配任意字符,
.jpg匹配所有jpg文件

for im in imgs:

問29: im是什么類型?
答29: 字符串,完整文件路徑

    im_name=im[17:-4]

問30: 為什么是17?
答30: './data/skin_data/'正好17個字符

問31: -4是什么?
答31: 倒數第4個字符開始,去掉’.jpg’

    print(im_name)

問32: 這個print是調試用的?
答32: 是的,確認提取的文件名正確

    if im_name in pic_data:

問33: in的時間復雜度?
答33: list是O(n),set是O(1)

        print('E:/皮膚病分類/data/clear_skin_data/{}'.format(im_name))

問34: format和f-string的區別?
答34: format是舊語法,f-string(f’{im_name}')更簡潔

        shutil.copy(im,'E:/皮膚病分類/data/clear_skin_data/{}.jpg'.format(im_name))

問35: copy和move的區別?
答35: copy保留原文件,move是剪切


第38-43行:提取標簽

skin_label=[]

問36: 為什么用列表不用數組?
答36: 要逐個append,列表動態增長更高效

index=np.argmax(df_Key,axis=1)

問37: argmax返回什么?
答37: 最大值的索引位置

問38: axis=1和axis=0的記憶方法?
答38: axis=0沿著行方向(↓),axis=1沿著列方向(→)

for i in index:skin_index=skinDisease[i]skin_label.append(skin_index)

問39: i是什么值?
答39: 0-8的整數,表示疾病類別索引

問40: append和extend的區別?
答40: append加單個元素,extend加多個元素


第47-51行:創建字典映射

skinLable_dic={}
lableSkin_dic={}

問41: 為什么建兩個字典?
答41: 雙向映射:圖片→標簽,標簽→圖片

for i in range(6000):skinLable_dic[pic_data[i]]=skin_label[i]

問42: range(6000)和range(len(pic_data))哪個好?
答42: range(len(pic_data))更好,自適應數據長度


第53-59行:創建類別ID映射

class_id = list(set(skinLable_dic.values()))

問43: set的作用?
答43: 去重,獲取唯一的疾病類別

問44: 為什么又轉回list?
答44: set無序,list可以索引訪問

id_to_class={}
class_to_id={}
for i,e in enumerate(class_id):class_to_id[e]=iid_to_class[i]=e

問45: enumerate返回什么?
答45: (索引, 元素)的元組

問46: 為什么需要數字ID?
答46: 神經網絡輸出是數字,不是字符串


第61-70行:獲取篩選后圖片的標簽

clear_img_path=glob.glob('./data/clear_skin_data/*.jpg')

問47: 這是第二次glob,為什么?
答47: 獲取篩選后的圖片路徑列表

clear_img_lable=[]
for img in clear_img_path:img_name=img[23:-4]

問48: 23是怎么算的?
答48: './data/clear_skin_data/'是23個字符

    classes=skinLable_dic[img_name]ids=class_to_id[classes]clear_img_lable.append(ids)

問49: 這里做了幾次映射?
答49: 兩次:文件名→疾病名→數字ID


第72-90行:定義數據變換

train_transformer=transforms.Compose([  transforms.RandomHorizontalFlip(0.2),

問50: Compose是什么設計模式?
答50: 組合模式,串聯多個變換

問51: 0.2的概率是每張圖片獨立的嗎?
答51: 是的,每次調用獨立決定

   transforms.RandomRotation(68),

問52: 為什么是68度不是90度?
答52: 可能是經驗值,避免過度旋轉丟失信息

    transforms.RandomGrayscale(0.2),

問53: 灰度化的目的?
答53: 增強模型對顏色變化的魯棒性

   transforms.Resize((128,128)),

問54: 為什么是128不是224?
答54: 平衡精度和速度,128夠用且更快

   transforms.ToTensor(),

問55: Tensor和array的內存布局區別?
答55: Tensor是CHW(通道-高-寬),array通常是HWC

   transforms.Normalize(mean=[0.5,0.5,0.5],std=[0.5,0.5,0.5]) 

問56: 這個歸一化后的范圍?
答56: (pixel-0.5)/0.5,從[0,1]變為[-1,1]

問57: 為什么要歸一化到[-1,1]?
答57: 零中心化,有助于梯度下降收斂


第92-107行:自定義數據集類

class Skindataset(data.Dataset):

問58: 為什么必須繼承Dataset?
答58: DataLoader需要調用固定接口

    def __init__(self, img_paths, labels, transform):self.imgs = clear_img_pathself.labels = clear_img_lable

問59: 這里有bug嗎?
答59: 有!應該用參數img_paths和labels,不是全局變量

    def __getitem__(self, index):

問60: 這個方法什么時候被調用?
答60: DataLoader迭代時自動調用

        img = self.imgs[index]label = self.labels[index]pil_img = Image.open(img)    data = self.transforms(pil_img)

問61: 每次都打開文件會不會慢?
答61: 會,但省內存,是時間換空間

        return data, label

問62: 返回順序重要嗎?
答62: 重要,約定是(輸入, 標簽)

    def __len__(self):return len(self.imgs)

問63: 為什么需要__len__?
答63: DataLoader需要知道數據集大小來計算批次數


第114-120行:劃分訓練集測試集

s = int(len(clear_img_path)*0.8)

問65: 為什么是0.8?
答65: 經驗值,80%訓練20%測試

問66: int()是向下取整嗎?
答66: 是的,截斷小數部分

train_imgs = clear_img_path[:s]
test_imgs = clear_img_path[s:]

問67: 這樣分割有什么問題?
答67: 沒打亂,可能有順序偏差


第122-130行:創建數據加載器

train = Skindataset(train_imgs, train_labels, train_transformer)

問68: 這里會調用__init__嗎?
答68: 會,創建實例時自動調用

dl_train = data.DataLoader(train,batch_size=32,shuffle=True)

問69: batch_size=32的含義?
答69: 每次送入網絡32張圖片

問70: 為什么要batch不要單張?
答70: 并行計算快,梯度估計更穩定

問71: shuffle=True的作用?
答71: 打亂順序,防止模型記住順序


第132-146行:可視化數據

img, label = next(iter(dl_train))

問72: iter()做了什么?
答72: 創建迭代器對象

問73: next()返回什么?
答73: 一個批次的(圖片張量, 標簽張量)

plt.rcParams['font.sans-serif'] = ['SimHei']

問74: SimHei是什么?
答74: 黑體字體,支持中文顯示

skin_chinese={'MEL':'黑色素瘤','NV':'黑素細胞痣',...}

問75: 字典的鍵為什么用英文縮寫?
答75: 與數據集標簽保持一致

for i,(img,label) in enumerate(zip(img[:8],label[:8])):

問76: zip的作用?
答76: 將兩個序列配對成元組

    img=(img.permute(1,2,0).numpy()+1)/2

問77: permute(1,2,0)在做什么?
答77: CHW轉HWC,適配matplotlib

問78: +1再/2是為什么?
答78: [-1,1]恢復到[0,1]用于顯示

    plt.subplot(2,4,i+1)

問79: i+1是因為?
答79: subplot索引從1開始,不是0


第148-151行:加載預訓練模型

model=torchvision.models.resnet50()

問80: resnet50的50指什么?
答80: 網絡深度,50層

問81: 預訓練是在什么數據上?
答81: ImageNet,1000類日常物體

model.fc.out_features=8

問82: 這樣直接賦值有用嗎?
答82: 沒用,應該替換整個fc層

問83: 正確寫法是什么?
答83: model.fc = nn.Linear(model.fc.in_features, 8)


第153-156行:定義損失和優化器

loss_fn=nn.CrossEntropyLoss()

問84: 交叉熵適合什么任務?
答84: 多分類任務

問85: 交叉熵的數學本質?
答85: 衡量兩個概率分布的差異

from torch.optim import lr_scheduler

問86: lr_scheduler沒用到?
答86: 是的,導入了但沒使用

optim=torch.optim.Adam(model.parameters(),lr=0.001)

問87: Adam是什么的縮寫?
答87: Adaptive Moment Estimation

問88: lr=0.001是經驗值嗎?
答88: 是的,Adam的常用默認值


第158-160行:GPU設置

if torch.cuda.is_available():model.to('cuda')

問89: cuda是什么?
答89: NVIDIA的并行計算平臺

問90: to(‘cuda’)做了什么?
答90: 把模型參數移到GPU內存


第163-211行:訓練函數

def fit(epoch, model, trainloader, testloader):correct = 0total = 0running_loss = 0

問91: 這三個變量追蹤什么?
答91: 正確數、總數、累積損失

    model.train()

問92: train()模式改變什么?
答92: 啟用Dropout和BatchNorm的訓練行為

    for x, y in tqdm(trainloader):

問93: tqdm包裝的效果?
答93: 顯示進度條

        if torch.cuda.is_available():x, y = x.to('cuda'), y.to('cuda')

問94: 每個batch都要to(‘cuda’)嗎?
答94: 是的,數據在CPU,要移到GPU

        y_pred = model(x)

問95: model(x)等價于?
答95: model.forward(x)

        loss = loss_fn(y_pred, y)

問96: y_pred和y的形狀?
答96: y_pred是(32,8),y是(32,)

        optim.zero_grad()

問97: 不清零會怎樣?
答97: 梯度累加,相當于更大的batch

        loss.backward()

問98: backward()計算什么?
答98: loss對所有參數的偏導數

        optim.step()

問99: step()的更新公式?
答99: 參數 = 參數 - 學習率 × 梯度

        with torch.no_grad():

問100: no_grad()的作用?
答100: 禁用梯度計算,節省內存

            y_pred = torch.argmax(y_pred, dim=1)

問101: argmax后的形狀?
答101: 從(32,8)變為(32,)

            correct += (y_pred == y).sum().item()

問102: .item()的作用?
答102: 將單元素張量轉為Python數值

    epoch_loss = running_loss / len(trainloader.dataset)

問103: 為什么除以dataset長度不是batch數?
答103: 獲得每個樣本的平均損失

    model.eval()

問104: eval()改變什么?
答104: 關閉Dropout,BatchNorm用運行均值

    torch.save(static_dict,'./data/resnet_Chepoint/{}_train_acc_{}_test_acc_{}.pth'.format(epoch,round(epoch_acc, 3),round(epoch_test_acc,3)))

問105: .pth是什么格式?
答105: PyTorch的模型文件格式

問106: state_dict包含什么?
答106: 所有層的權重和偏置參數


第213-228行:訓練循環

epochs = 150

問107: 150輪夠嗎?
答107: 看驗證集性能,可能過擬合

for epoch in range(epochs):epoch_loss, epoch_acc, epoch_test_loss, epoch_test_acc = fit(...)

問108: 每輪都保存模型?
答108: 是的,可以選最好的


第230-233行:繪制損失曲線

plt.plot(range(1, epochs+1), train_loss, label='train_loss')

問109: range從1開始?
答109: 讓橫軸從1開始,更直觀


第235-260行:加載最佳模型測試

model.load_state_dict(torch.load('./data/resnet_Chepoint/143_train_acc_0.904_test_acc_0.981.pth'))

問110: 為什么選143輪的?
答110: 測試準確率最高(98.1%)


第262-305行:預測新圖片

t_img='C:/Users/MSI-NB/AppData/Local/Temp/vasssssss.jpeg'

問111: 這是什么路徑?
答111: Windows臨時文件夾的圖片

img_tensor=test_transformer(img)
img_tensor=img_tensor.unsqueeze(0)

問112: unsqueeze(0)做什么?
答112: 增加batch維度,(3,128,128)→(1,3,128,128)

pre=torch.argmax(out,axis=1).cpu().numpy()[0]

問113: .cpu()為什么需要?
答113: GPU張量不能直接轉numpy

id_to_class[pre]

問114: 最終輸出什么?
答114: 疾病類別名稱,如’MEL’(黑色素瘤)


核心流程總結

這個項目的本質是一個遷移學習流程:

  1. 數據準備:圖片+標簽 → 數字化
  2. 數據增強:翻轉旋轉 → 泛化能力
  3. 特征提取:ResNet50 → 圖像特征
  4. 微調分類:1000類 → 8類疾病
  5. 迭代優化:梯度下降 → 最小損失
  6. 模型應用:新圖片 → 疾病診斷

替換不同模型

如果只想快速替換模型,最少只需改2處:

  • 不同模型架構不同,最后一層名稱不同
  • 輸入尺寸要求可能不同

除了最后一層,還有什么要改? 輸入尺寸要求不同!

但手動修改,還是容易出現 BUG。

代碼中哪些地方依賴于ResNet50?

答1: 主要是兩處:

model = torchvision.models.resnet50()    # 第148行
model.fc.out_features = 8                # 第150行,這行還有bug
# ResNet系列
model = torchvision.models.resnet50()
model.fc = nn.Linear(model.fc.in_features, 8)  # fc層# VGG系列
model = torchvision.models.vgg16()
model.classifier[6] = nn.Linear(4096, 8)  # classifier是個Sequential# DenseNet系列
model = torchvision.models.densenet121()
model.classifier = nn.Linear(model.classifier.in_features, 8)  # classifier層# EfficientNet系列
model = torchvision.models.efficientnet_b0()
model.classifier[1] = nn.Linear(model.classifier[1].in_features, 8)  # classifier[1]# MobileNet系列
model = torchvision.models.mobilenet_v2()
model.classifier[1] = nn.Linear(model.classifier[1].in_features, 8)  # classifier[1]

更優雅的解決方案:使用timm庫

import timmdef get_model_timm(model_name='resnet50', num_classes=8, pretrained=True):"""問5:timm是什么?答5:PyTorch Image Models,包含700+預訓練模型問6:為什么用timm更好?答6:統一接口,自動處理最后一層"""model = timm.create_model(model_name,pretrained=pretrained,num_classes=num_classes  # 自動替換最后一層!)return model# 使用示例 - 可以用任何模型!
model = get_model_timm('resnet50', num_classes=8)
model = get_model_timm('efficientnet_b7', num_classes=8)
model = get_model_timm('vit_base_patch16_224', num_classes=8)  # Vision Transformer!

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

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

相關文章

【JavaEE】多線程 -- 線程狀態

目錄六大狀態舉例說明六大狀態 New 新建狀態:線程還沒出創建,只有Thread 實例化的對象,調用start 方法之前的狀態。Runnable 運行狀態:被系統調度后,CPU 正在執行的,Ready 就緒態,系統調度&…

網絡流初步

網絡流初步 文章目錄網絡流初步概念介紹最大流費用流概念介紹 網絡流不同之處在于它的本質圖論,但是把圖論的某些概念換了一個說法而已,初步只要了解網絡流的各個概念就可以明白的很快。 下述概念是本人自己定義的,對于網絡流的題目做的還不…

[系統架構設計師]系統架構基礎知識(一)

[系統架構設計師]系統架構基礎知識(一) 一.計算機系統基礎知識 1.計算機系統概述 硬件軟件及網絡組成的系統 2.計算機硬件基礎知識 馮 諾依曼結構:運算器,控制器,存儲器,輸入設備,輸出設備 專用…

深入解析Java代理模式:靈活控制對象訪問的核心技術

在日常開發中,我們常遇到這樣的場景:需要控制對象訪問權限、優化高成本操作,或給方法添加額外功能(如日志、事務)。代理模式(Proxy Pattern) 正是解決這類問題的金鑰匙。作為結構型設計模式的代…

【學習筆記】Java并發編程的藝術——第9章 Java中的線程池

第9章 Java中的線程池 線程池優勢: ①減少資源消耗 ②提高響應速度 ③統一管理 9.1 線程池的實現原理 當任務來后 ①判斷核心線程池是否已滿,若未滿,創建一個核心線程來執行任務 ②若無空閑核心線程且核心線程已滿,則將任務放入任…

Mybatis學習筆記(九)

常見問題與解決方案 簡要描述:總結MyBatis-Plus開發過程中常見的問題、錯誤及其解決方案,幫助開發者快速定位和解決問題。 核心概念: 常見錯誤:開發中經常遇到的錯誤類型性能問題:性能相關問題的排查和解決配置問題&am…

數據類型 list

一、介紹類似于數組,順序表,deque結構圖特點:元素有序,元素允許重復由于頭尾高效插入刪除,可以模擬棧,隊列二、常見 list 命令1、lpush key elem [elem ...]頭插元素,返回值列表長度2、lrange k…

pyqt5無法顯示opencv繪制文本和掩碼信息

背景:pyqt5無法顯示opencv繪制的標簽和mask;我們在使用YOLO做實例分割做推理時,會使用opencv做后處理結果繪制(含標簽繪制和掩碼繪制);結果opencv繪制的解碼卻無法在pyqt的解碼上面顯示。pyqt轉換代碼如下&…

如何生成嚴格遞增的分布式id?

本文字數:2604字預計閱讀時間:15分鐘01引言在現有分布式系統中,面對增長迅速的業務數據,id生成一直是非常重要的一環。而分布式系統的id生成方案需要滿足幾個重要特性:容錯高可用、高性能高并發、全局唯一。02技術背景…

【LeetCode】二叉樹相關算法題

目錄1、二叉樹介紹【1】核心概念【2】關鍵特性2、算法題【1】二叉樹的前序遍歷【2】二叉樹的后序遍歷1、二叉樹介紹 【1】核心概念 結構含義節點結構二叉樹由節點組成, 每個節點包含一個數據元素和最多兩個子節點:左子節點和右子節點根節點樹的頂部節點…

Vulnhub Deathnote靶機復現攻略

一、靶機安裝 下載地址:https://download.vulnhub.com/deathnote/Deathnote.ova 下載好后使用VB打開,配置如下 二、主機發現 使用相同連接方式的kali進行后續操作(172.16.2.7)根據mac地址進行確認。 nmap -sn 172.16.2.1/24 三、端口掃描 端口開放了…

DevEco Studio 6.0.0 元服務頁面跳轉失敗

背景,我使用最新的編輯器DevEco Studio 6.0.0,編寫一個元服務,發現使用跳轉頁面的時候失敗了!然后查看官方文檔,兩種方式都測試了,發現都不行。 方法1:Navigation路由跳轉無效,見官方…

docker重啟或系統重啟后harbor自動啟動

docker重啟或系統重啟后harbor自動啟動docker重啟或系統重啟后harbor自動啟動方法 1:在 docker-compose.yml 中配置重啟策略(推薦)方法 2:創建 Systemd 服務(更可靠)方法 3:使用 Docker 的 Rest…

OpenZeppelin Contracts 架構分層分析

OpenZeppelin Contracts 是一個面向以太坊(及兼容 EVM 的區塊鏈)生態系統的??模塊化、安全性優先、標準兼容的智能合約庫??。其內部代碼按照功能職責與抽象層級,可系統性地劃分為多個邏輯層次。理解這些層次及其依賴關系,對于…

Java-JVM的內存模型

一.JVM內存模型JVM內存模型可以從進程生命周期和線程生命周期1.線程生命周期每個線程都會有自己各自一份數據,不會存在線程安全問題1.程序計數器指示當前線程執行的字節碼指令的行號,以便線程執行時可以回到正確的位置2.虛擬機棧線程私有的,與…

Highcharts Dashboards | 打造企業級數據儀表板:從圖表到數據駕駛艙

企業日常決策、產品運營、業務監控,越來越依賴數據驅動。而儀表板(Dashboard)作為匯總展示多維度信息的“數據駕駛艙”,已成為企業可視化的核心場景之一。如果你正在尋找一款能夠快速、靈活、安全構建儀表板的前端圖表工具&#x…

基于Java的Markdown轉Word工具(標題、段落、表格、Echarts圖等)

項目源于我們開發的一款基于大模型的報告生成工具。由于需要將 Markdown 格式的內容導出為 Word 文檔,而市面上缺乏合適的現成工具,所以決定自己開發一個Markdown轉Word的工具。 🩷源碼地址:daydayup-zyn/md2doc-plus &#x1f…

Unity:PlayerPrefs筆記

寫在前面:寫本系列(自用)的目的是回顧已經學過的知識、記錄新學習的知識或是記錄心得理解,方便自己以后快速復習,減少遺忘。一、PlayerPrefs的基本方法1、存儲相關PlayerPrefs的數據存儲類似于鍵值對存儲,一個鍵對應一個值。Unity…

SQL tutorials

SQL Literature SQL運行在資料庫管理系統(Database Management System),如MySQL,Postgre SQL,Microsoft SQL Server, Oracle,etc。 SQL練習平臺:https://sqliteviz.com/ EXAMPLE SQL…

MySQL快速恢復數據的N種方案完全教程

目錄 1. 理解MySQL數據恢復的核心邏輯 1.1 數據丟失的常見場景 1.2 MySQL的“救命稻草”:關鍵文件和機制 2. 方案一:利用全量備份+binlog實現點對點恢復 2.1 準備工作 2.2 恢復步驟 2.3 實戰案例 3. 方案二:利用InnoDB的崩潰恢復機制 3.1 崩潰恢復的原理 3.2 恢復步…