行訓(xùn)練和預(yù)測(cè):從數(shù)據(jù)準(zhǔn)備到推理部署的完整實(shí)踐)
1. 從標(biāo)注數(shù)據(jù)到可運(yùn)行檢測(cè)模型object_detection_api 訓(xùn)練預(yù)測(cè)全流程拆解object_detection_api 是 TensorFlow 官方提供的一套目標(biāo)檢測(cè)工具箱它把 Faster R-CNN、SSD、CenterNet 等主流檢測(cè)網(wǎng)絡(luò)封裝成統(tǒng)一的配置驅(qū)動(dòng)流程。你只要準(zhǔn)備好標(biāo)注數(shù)據(jù)、寫一份 pipeline 配置文件就能用同一套命令完成訓(xùn)練、評(píng)估和導(dǎo)出推理圖。它適合誰(shuí)適合手里已經(jīng)有 VOC 或 COCO 格式標(biāo)注、想在自己機(jī)器上跑通一次完整訓(xùn)練并驗(yàn)證推理結(jié)果的開(kāi)發(fā)者而不是只想調(diào)個(gè)在線接口的人。我見(jiàn)過(guò)太多人卡在中間環(huán)節(jié)數(shù)據(jù)轉(zhuǎn) tfrecord 報(bào)編碼錯(cuò)、config 里路徑?jīng)]改全、訓(xùn)練跑起來(lái) loss 不動(dòng)、導(dǎo)出 pb 后預(yù)測(cè)框全錯(cuò)。這些問(wèn)題的根源往往不是模型本身而是數(shù)據(jù)格式和配置文件的細(xì)節(jié)沒(méi)對(duì)齊。這篇就按“數(shù)據(jù)準(zhǔn)備 → 配置 → 訓(xùn)練 → 評(píng)估 → 導(dǎo)出 → 預(yù)測(cè)”的順序把每一步的可復(fù)制命令和踩坑點(diǎn)寫清楚。整個(gè)流程可以拆成六個(gè)階段每個(gè)階段都有明確的輸入和輸出階段輸入輸出關(guān)鍵文件數(shù)據(jù)準(zhǔn)備標(biāo)注 XML 原圖tfrecordcreate_xx_tf_record.py配置預(yù)訓(xùn)練模型 config自定義 pipeline.configfaster_rcnn_resnet101_xx.config訓(xùn)練tfrecord configckpt 檢查點(diǎn)train.py評(píng)估ckpt val.recordmAP 指標(biāo)eval.py導(dǎo)出ckpt configfrozen_inference_graph.pbexport_inference_graph.py預(yù)測(cè)pb label_map可視化結(jié)果demo_xx.py如果你只是想在本地快速驗(yàn)證一個(gè)檢測(cè)模型能不能跑通這套流程完全夠用。下面從環(huán)境前置開(kāi)始一步步來(lái)。2. TaoToken 前置準(zhǔn)備模型下載與 API Key 配置object_detection_api 本身是本地訓(xùn)練框架但預(yù)訓(xùn)練模型權(quán)重、部分依賴包的下載以及后續(xù)如果你想用大模型輔助生成標(biāo)注或調(diào)試代碼會(huì)涉及網(wǎng)絡(luò)訪問(wèn)。這里我用 TaoToken 來(lái)做統(tǒng)一的模型與 API 接入層它的官網(wǎng)是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 端點(diǎn)是 https://taotoken.net/api 。先說(shuō)清楚它在這里的角色TaoToken 不是替代 TensorFlow 的框架而是幫你管理模型訪問(wèn)憑證和調(diào)用入口。比如你在調(diào)試階段想讓大模型幫你檢查 config 文件里的字段是否寫錯(cuò)或者根據(jù)報(bào)錯(cuò)日志給出修復(fù)建議就可以通過(guò)它的 API 來(lái)調(diào)用。對(duì)于長(zhǎng)期做檢測(cè)項(xiàng)目的人來(lái)說(shuō)把 API Key 和 Base URL 統(tǒng)一配置好后面切換模型或做批量推理會(huì)省很多事。配置方式很簡(jiǎn)單在項(xiàng)目根目錄建一個(gè)環(huán)境變量文件或者在終端里 exportexport TAOTOKEN_API_KEY你的API Key export TAOTOKEN_BASE_URLhttps://taotoken.net/api如果你用的是 Python 腳本調(diào)用可以這樣讀import os import requests api_key os.environ.get(TAOTOKEN_API_KEY) base_url os.environ.get(TAOTOKEN_BASE_URL) headers { Authorization: fBearer {api_key}, Content-Type: application/json } payload { model: claude-3-5-sonnet, messages: [ {role: user, content: 幫我檢查這段 pipeline config 的 num_classes 和 label_map 是否一致} ] } resp requests.post(f{base_url}/v1/messages, headersheaders, jsonpayload, timeout60) print(resp.json())API Key 的獲取入口在 https://taotoken.net/api-keys 進(jìn)去后創(chuàng)建一個(gè)新 Key復(fù)制出來(lái)填到上面的環(huán)境變量里。注意不要把 Key 硬編碼進(jìn)提交到 Git 的腳本里用 .env 或者系統(tǒng)環(huán)境變量管理。模型對(duì)話的調(diào)試入口在 https://taotoken.net/models 你可以在這里先測(cè)試一下模型能不能正常返回確認(rèn) Key 和 Base URL 沒(méi)問(wèn)題再去寫自動(dòng)化腳本。對(duì)于檢測(cè)項(xiàng)目來(lái)說(shuō)這個(gè)環(huán)節(jié)主要用在兩個(gè)地方一是生成數(shù)據(jù)轉(zhuǎn)換腳本的模板代碼二是根據(jù)訓(xùn)練日志里的報(bào)錯(cuò)定位問(wèn)題。比如你遇到DataLossError: Unable to open table file可以把完整報(bào)錯(cuò)貼給模型讓它給出排查方向比翻 issue 快很多。如果你后面要做長(zhǎng)期的編碼和 Agent 任務(wù)比如自動(dòng)生成標(biāo)注轉(zhuǎn)換腳本、批量跑推理可以考慮 Coding Plan入口在 https://taotoken.net/coding-plan 。它的定位是給需要持續(xù)調(diào)用模型的開(kāi)發(fā)場(chǎng)景用的不是一次性問(wèn)答。3. 可復(fù)制配置pipeline.config 與數(shù)據(jù)轉(zhuǎn)換腳本這一節(jié)是整篇的核心所有內(nèi)容都可以直接復(fù)制到你的項(xiàng)目里改路徑就能用。先看數(shù)據(jù)存放結(jié)構(gòu)我按 VOC 格式來(lái)組織data/ ├── label_map.pbtxt └── VOC2007/ ├── Annotations/ │ ├── 000001.xml │ └── ... ├── ImageSets/ │ └── Main/ │ ├── train.txt │ └── val.txt └── JPEGImages/ ├── 000001.jpg └── ...label_map.pbtxt 的內(nèi)容按類別寫id 從 1 開(kāi)始0 留給背景item { id: 1 name: cat } item { id: 2 name: dog }數(shù)據(jù)轉(zhuǎn)換腳本基于官方的 create_pascal_tf_record.py 改核心改動(dòng)是路徑和字段。把腳本放到 data 同級(jí)目錄命名為 create_xx_tf_record.py然后改這幾處# 主函數(shù)里的路徑 data_dir ./data label_map_path ./data/label_map.pbtxt output_path ./data/xx_train.tfrecord examples_path os.path.join(data_dir, VOC2007, ImageSets, Main, train.txt) # 如果 XML 里沒(méi)有 width/height改成從圖片讀 width, height image.size # 注釋掉不需要的字段 # difficult_obj.append(int(difficult)) # truncated.append(int(obj[truncated])) # poses.append(obj[pose].encode(utf8))運(yùn)行轉(zhuǎn)換命令python create_xx_tf_record.py \ --data_dir./data \ --yearVOC2007 \ --settrain \ --label_map_path./data/label_map.pbtxt \ --output_path./data/xx_train.tfrecord驗(yàn)證 tfrecord 是否生成成功可以用一段小腳本讀一條出來(lái)看import tensorflow as tf raw_dataset tf.data.TFRecordDataset(./data/xx_train.tfrecord) for raw_record in raw_dataset.take(1): example tf.train.Example() example.ParseFromString(raw_record.numpy()) print(example.features.feature[image/object/class/text].bytes_list.value)接下來(lái)是 pipeline.config。從 detection_model_zoo 下載 faster_rcnn_resnet101_coco 的 config復(fù)制一份改名為 faster_rcnn_resnet101_xx.config然后改這幾個(gè)字段model { faster_rcnn { num_classes: 2 image_resizer { keep_aspect_ratio_resizer { min_dimension: 600 max_dimension: 1024 } } } } train_config { batch_size: 1 fine_tune_checkpoint: ./demo_xx/model.ckpt fine_tune_checkpoint_type: detection num_steps: 50000 data_augmentation_options { random_horizontal_flip { } } } train_input_reader { label_map_path: ./data/label_map.pbtxt tf_record_input_reader { input_path: ./data/xx_train.tfrecord } } eval_config { num_examples: 100 metrics_set: pascal_voc_detection_metrics } eval_input_reader { label_map_path: ./data/label_map.pbtxt shuffle: false num_readers: 1 tf_record_input_reader { input_path: ./data/xx_val.tfrecord } }注意 num_classes 必須和 label_map 里的類別數(shù)一致fine_tune_checkpoint 指向預(yù)訓(xùn)練模型的 ckpt 前綴不帶 .index 或 .data 后綴。如果你下載的預(yù)訓(xùn)練模型是三個(gè)文件 model.ckpt.data-00000-of-00001、model.ckpt.index、model.ckpt.meta那 fine_tune_checkpoint 就寫./demo_xx/model.ckpt。4. 訓(xùn)練啟動(dòng)與推理驗(yàn)證從 train.py 到 frozen_inference_graph.pb配置寫好后訓(xùn)練命令本身很簡(jiǎn)單但路徑和參數(shù)要對(duì)齊python object_detection/train.py \ --logtostderr \ --pipeline_config_path./demo_xx/faster_rcnn_resnet101_xx.config \ --train_dir./demo_xx/train訓(xùn)練啟動(dòng)后終端會(huì)打印每一步的 loss。正常情況下 loss 會(huì)從幾降到零點(diǎn)幾如果一直不動(dòng)或者報(bào) NaN先檢查學(xué)習(xí)率和 batch_size。faster_rcnn 在單卡上 batch_size 設(shè) 1 是常態(tài)顯存不夠就再降 image_resizer 的 min_dimension。評(píng)估命令python object_detection/eval.py \ --logtostderr \ --pipeline_config_path./demo_xx/faster_rcnn_resnet101_xx.config \ --checkpoint_dir./demo_xx/train \ --eval_dir./demo_xx/eval評(píng)估結(jié)果會(huì)寫進(jìn) eval_dir用 tensorboard 看tensorboard --logdir./demo_xx/eval重點(diǎn)看 Pascal VOC mAP 這個(gè)指標(biāo)如果 mAP 在 0.5 以上說(shuō)明模型基本可用。如果低于 0.2大概率是 label_map 的 id 和 XML 里的類別對(duì)不上或者 tfrecord 里的圖片和標(biāo)注錯(cuò)位了。訓(xùn)練到滿意程度后導(dǎo)出推理圖python object_detection/export_inference_graph.py \ --input_type image_tensor \ --pipeline_config_path ./demo_xx/faster_rcnn_resnet101_xx.config \ --trained_checkpoint_prefix ./demo_xx/train/model.ckpt-50000 \ --output_directory ./demo_xx/output這里的 model.ckpt-50000 要換成你 train_dir 里實(shí)際存在的最大步數(shù)檢查點(diǎn)。導(dǎo)出成功后output 目錄下會(huì)有 frozen_inference_graph.pb。預(yù)測(cè)腳本基于官方 notebook 改寫核心是加載 pb 和 label_mapimport numpy as np import tensorflow as tf from PIL import Image from object_detection.utils import label_map_util from object_detection.utils import visualization_utils as vis_util PATH_TO_CKPT ./demo_xx/output/frozen_inference_graph.pb PATH_TO_LABELS ./data/label_map.pbtxt NUM_CLASSES 2 detection_graph tf.Graph() with detection_graph.as_default(): od_graph_def tf.GraphDef() with tf.gfile.GFile(PATH_TO_CKPT, rb) as fid: serialized_graph fid.read() od_graph_def.ParseFromString(serialized_graph) tf.import_graph_def(od_graph_def, name) label_map label_map_util.load_labelmap(PATH_TO_LABELS) categories label_map_util.convert_label_map_to_categories( label_map, max_num_classesNUM_CLASSES, use_display_nameTrue) category_index label_map_util.create_category_index(categories) with detection_graph.as_default(): with tf.Session(graphdetection_graph) as sess: image_tensor detection_graph.get_tensor_by_name(image_tensor:0) boxes detection_graph.get_tensor_by_name(detection_boxes:0) scores detection_graph.get_tensor_by_name(detection_scores:0) classes detection_graph.get_tensor_by_name(detection_classes:0) num_detections detection_graph.get_tensor_by_name(num_detections:0) image Image.open(./test_images/000001.jpg) image_np np.array(image) image_np_expanded np.expand_dims(image_np, axis0) (boxes_out, scores_out, classes_out, num_out) sess.run( [boxes, scores, classes, num_detections], feed_dict{image_tensor: image_np_expanded}) vis_util.visualize_boxes_and_labels_on_image_array( image_np, np.squeeze(boxes_out), np.squeeze(classes_out).astype(np.int32), np.squeeze(scores_out), category_index, use_normalized_coordinatesTrue, line_thickness4) Image.fromarray(image_np).save(./test_images/result_000001.jpg)跑完后打開(kāi) result_000001.jpg看框的位置和類別是否正確。如果框全在左上角或者類別全錯(cuò)先檢查 label_map 的 id 順序和訓(xùn)練時(shí)是否一致再檢查輸入圖片的通道順序。5. 常見(jiàn)報(bào)錯(cuò)排查401、DataLossError、pycocotools 與 OAuth 問(wèn)題這一節(jié)按真實(shí)報(bào)錯(cuò)來(lái)對(duì)照每個(gè)都給出定位方法和修復(fù)動(dòng)作。401 Unauthorized / invalid api key如果你在調(diào)用 TaoToken API 時(shí)遇到 401先確認(rèn)環(huán)境變量里的 Key 沒(méi)有多余空格再確認(rèn) Base URL 是 https://taotoken.net/api 而不是帶 UTM 的官網(wǎng)地址。API 端點(diǎn)和官網(wǎng)是兩個(gè)不同的入口混用會(huì)報(bào) 401。修復(fù)方式echo $TAOTOKEN_API_KEY curl -H Authorization: Bearer $TAOTOKEN_API_KEY https://taotoken.net/api/v1/models如果 curl 返回 200說(shuō)明 Key 沒(méi)問(wèn)題問(wèn)題在代碼里的讀取邏輯。DataLossError: Unable to open table file這個(gè)報(bào)錯(cuò)通常出現(xiàn)在 fine_tune_checkpoint 路徑寫錯(cuò)的時(shí)候。新版 TensorFlow 的 ckpt 是三個(gè)文件fine_tune_checkpoint 要寫前綴比如./demo_xx/model.ckpt而不是./demo_xx/model.ckpt.index。另外確認(rèn) ckpt 文件和 config 在同一臺(tái)機(jī)器上路徑不要用 Windows 的反斜杠。No module named pycocotoolsCOCO API 在 Windows 上安裝容易失敗。從源碼編譯git clone https://github.com/philferriere/cocoapi.git cd cocoapi/PythonAPI python setup.py build_ext --inplace python setup.py build_ext install如果 build_ext 報(bào)缺少 Visual C 編譯環(huán)境裝一個(gè) Build Tools 再重試。裝完后import pycocotools不報(bào)錯(cuò)即可。local proxy failed / connection refused這類報(bào)錯(cuò)一般出現(xiàn)在調(diào)用外部 API 時(shí)。先確認(rèn)網(wǎng)絡(luò)能通再檢查代碼里有沒(méi)有硬編碼的代理地址。如果你在 config 或腳本里寫了 proxy 相關(guān)配置刪掉直接用系統(tǒng)默認(rèn)網(wǎng)絡(luò)。OAuth token expired如果你用 OAuth 方式接入模型服務(wù)token 過(guò)期后會(huì)報(bào)這個(gè)。重新走一遍授權(quán)流程或者改用 API Key 方式。TaoToken 的 API Key 方式不需要 OAuth直接在 https://taotoken.net/api-keys 生成即可。reading choices / shape mismatch這個(gè)報(bào)錯(cuò)出現(xiàn)在預(yù)測(cè)階段通常是 num_classes 和 label_map 的類別數(shù)不一致。比如 label_map 里有 3 個(gè)類別config 里 num_classes 寫了 2導(dǎo)出 pb 后預(yù)測(cè)就會(huì) shape mismatch。修復(fù)方式是統(tǒng)一改成實(shí)際類別數(shù)重新導(dǎo)出。CC Switch / Cline MCP / Codex auth.json 三件套如果你在檢測(cè)項(xiàng)目里用這些工具做輔助編碼配置時(shí)要寫全三件套——Base URL、Key、Model ID。以 auth.json 為例{ base_url: https://taotoken.net/api, api_key: 你的Key, model: claude-3-5-sonnet }缺任何一個(gè)都會(huì)導(dǎo)致調(diào)用失敗。Model ID 要和 https://taotoken.net/models 里列出的名稱一致不要自己拼。6. 語(yǔ)義一致 CTA把訓(xùn)練流程接到你的實(shí)際項(xiàng)目里走到這一步你已經(jīng)有了一個(gè)能跑通訓(xùn)練和預(yù)測(cè)的檢測(cè)模型。接下來(lái)最實(shí)際的動(dòng)作是把這套流程接到你自己的數(shù)據(jù)上。先換 label_map再換 tfrecord然后改 config 里的 num_classes 和路徑重新跑一遍 train.py。如果 mAP 不達(dá)標(biāo)優(yōu)先加數(shù)據(jù)而不是調(diào)模型檢測(cè)任務(wù)里數(shù)據(jù)質(zhì)量和標(biāo)注一致性比網(wǎng)絡(luò)結(jié)構(gòu)影響大得多。如果你在調(diào)試過(guò)程中需要快速定位報(bào)錯(cuò)或者想讓模型幫你生成數(shù)據(jù)轉(zhuǎn)換腳本的變體可以用 TaoToken 的模型對(duì)話入口 https://taotoken.net/models 先測(cè)一下調(diào)用是否正常。API Key 在 https://taotoken.net/api-keys 生成接入文檔在 https://taotoken.net/doc 有完整的請(qǐng)求示例。對(duì)于需要長(zhǎng)期跑編碼和 Agent 任務(wù)的場(chǎng)景Coding Plan 的入口在 https://taotoken.net/coding-plan 適合把模型調(diào)用固化到你的開(kāi)發(fā)流程里。最后給一個(gè)實(shí)用技巧訓(xùn)練前先用 100 張圖跑 500 步確認(rèn) loss 能降、eval 能出 mAP再上全量數(shù)據(jù)。這樣能把數(shù)據(jù)格式問(wèn)題在早期暴露出來(lái)比跑了一天發(fā)現(xiàn) tfrecord 錯(cuò)位要省時(shí)間。導(dǎo)出 pb 后先用訓(xùn)練集里的圖做預(yù)測(cè)確認(rèn)框的位置和訓(xùn)練時(shí)可視化的一致再去測(cè)新圖。這一步能幫你區(qū)分是模型沒(méi)學(xué)好還是導(dǎo)出環(huán)節(jié)出了問(wèn)題。