yetrun's picture
ver3: 将源码迁入 src/deep_learning 包,重塑训练流水线,规范 data/model 契约,并补齐文档与测试
2c90129
Raw
History Blame Contribute Delete
8.06 kB
"""
猫狗图片分类 Gradio 交互界面。
这个文件把图片分类 Pipeline 包装成可体验的页面,方便上传图片或选择内置示例后查看模型预测的
最大概率类别。
"""
from typing import cast
import gradio as gr
import numpy as np
from PIL import Image
from deep_learning.env.resolve import display_path, resolve_path, resolve_saved
from deep_learning.pipeline.services.checkpoint import describe_checkpoint_lookup, resolve_checkpoint
from deep_learning.pipeline.services.model_loader import load_inference_artifact
from tasks.image_classification.runner import pipeline
IMAGE_SIZE = (180, 180)
CLASS_NAMES = ["Cat", "Dog"]
class ImageClassificationTool:
def __init__(
self,
pipeline,
inference_artifact,
resource,
image_size: tuple[int, int],
class_names: list[str]
):
self.pipeline = pipeline
self.inference_artifact = inference_artifact
self.resource = resource
self.image_size = image_size
self.class_names = class_names
def classify(self, image) -> str:
input_image = Image.fromarray(np.asarray(image, dtype="uint8")).convert("RGB")
resized_image = input_image.resize(
(self.image_size[1], self.image_size[0])
)
image_array = np.asarray(resized_image, dtype="float32")
model_input = np.expand_dims(image_array, axis=0)
prediction = self.inference_artifact.model(model_input, training=False)
positive_probability = float(np.asarray(prediction).reshape(-1)[0])
class_index = int(positive_probability >= 0.5)
probability = positive_probability
if class_index == 0:
probability = 1.0 - positive_probability
return f"预测类别:{self.class_names[class_index]},概率:{probability:.2%}"
class ImageClassificationPageAdapter:
def __init__(self, pipeline):
self.pipeline = pipeline
self.sample_dir = resolve_path("data/dev/cats_vs_dogs/images")
def list_sample_images(self) -> list[str]:
return sorted(
path.name
for path in self.sample_dir.iterdir()
if path.is_file() and path.suffix.lower() in [".jpg", ".jpeg", ".png"]
)
def load_sample_image(self, sample_name: str):
image_path = self.sample_dir / sample_name
return np.asarray(Image.open(image_path).convert("RGB"))
def extra_model_info(self) -> str:
return f"**示例图片目录**: {display_path(self.sample_dir)}"
def create_tool(self, inference_artifact, resource) -> ImageClassificationTool:
return ImageClassificationTool(
self.pipeline,
inference_artifact,
resource,
IMAGE_SIZE,
CLASS_NAMES
)
class ImageClassificationAppBuilder:
def __init__(
self,
pipeline,
tool_factory,
sample_images: list[str],
load_sample_image,
title: str = "猫狗图片分类",
input_label: str = "输入图片",
sample_label: str = "示例图片",
output_label: str = "预测结果",
extra_model_info=None
):
self.pipeline = pipeline
self.tool_factory = tool_factory
self.sample_images = sample_images
self.load_sample_image = load_sample_image
self.title = title
self.input_label = input_label
self.sample_label = sample_label
self.output_label = output_label
self.extra_model_info = extra_model_info
self._tool = None
def _resolve_checkpoint_rule(self) -> dict:
checkpoint_rule = self.pipeline.checkpoint_load_rules.resolve_test_rule(
default_dirs=[resolve_saved(f"models/{self.pipeline.name}")]
)
if checkpoint_rule["suffix"] is None:
checkpoint_rule["suffix"] = ".keras"
return checkpoint_rule
def _load_inference_artifact(self) -> tuple:
checkpoint_rule = self._resolve_checkpoint_rule()
return load_inference_artifact(
checkpoint_rule,
self.pipeline.model_builder.load_inference_artifact
)
def get_model_info(self) -> str:
parts = []
checkpoint_rule = self._resolve_checkpoint_rule()
checkpoint_path, _ = resolve_checkpoint(**checkpoint_rule)
if checkpoint_path is None:
lookup_info = describe_checkpoint_lookup(
dirs=checkpoint_rule.get("dirs"),
path=checkpoint_rule.get("path"),
suffix=checkpoint_rule.get("suffix")
)
raise FileNotFoundError(f"未找到模型检查点文件。查找信息: {lookup_info}")
file_name = checkpoint_path.name
file_size = checkpoint_path.stat().st_size
parts.append(f"**模型文件**: {file_name}{file_size / (1024 * 1024):.2f} MB)")
if self.extra_model_info is not None:
extra_info = self.extra_model_info()
if extra_info:
parts.append(extra_info)
return ",".join(parts)
def _init_tool(self):
print("正在加载图片分类模型...")
inference_artifact, resource = self._load_inference_artifact()
print("模型加载完成!")
return self.tool_factory(inference_artifact, resource)
def _ensure_tool_initialized(self) -> None:
if self._tool is None:
self._tool = self._init_tool()
def select_sample_image(self, sample_name: str):
return self.load_sample_image(sample_name)
def classify_image(self, image):
self._ensure_tool_initialized()
return self._tool.classify(image)
def create_ui(self):
with gr.Blocks(title=self.title) as demo:
gr.Markdown(f"# {self.title}")
gr.Markdown("请选择示例图片或直接上传图片,分类时只会读取当前输入框中的图片。")
try:
gr.Markdown(self.get_model_info())
except Exception as e:
gr.Markdown(f"**模型信息加载失败**: {str(e)}")
with gr.Row():
with gr.Column():
input_image = gr.Image(
label=self.input_label,
type="numpy"
)
sample_name = gr.Dropdown(
choices=self.sample_images,
value=cast(str | None, None),
label=self.sample_label
)
classify_btn = gr.Button("开始分类", variant="primary")
with gr.Column():
output_text = gr.Textbox(
label=self.output_label,
interactive=False
)
sample_name.change(
fn=self.select_sample_image,
inputs=[sample_name],
outputs=[input_image]
)
classify_btn.click(
fn=self.classify_image,
inputs=[input_image],
outputs=[output_text]
)
original_get_config_file = demo.get_config_file
def get_config_file():
config = original_get_config_file()
for component in config["components"]:
if component["type"] == "dropdown" and component["props"].get("label") == self.sample_label:
component["props"]["value"] = None
return config
demo.get_config_file = get_config_file
return demo
adapter = ImageClassificationPageAdapter(pipeline)
app = ImageClassificationAppBuilder(
pipeline=pipeline,
tool_factory=adapter.create_tool,
sample_images=adapter.list_sample_images(),
load_sample_image=adapter.load_sample_image,
extra_model_info=adapter.extra_model_info
)
demo = app.create_ui()
if __name__ == "__main__":
from deep_learning.env.keras import enable_mixed_precision
enable_mixed_precision()
demo.launch()