博主简介:擅长数据搜集与处理、建模仿真、程序设计、仿真代码、论文写作与指导,毕业论文、期刊论文经验交流。

 ✅成品或者定制,扫描文章底部微信二维码。


基于深度学习的太阳花马面部特征提取探究

核心内容

(1)马匹面部图像数据集构建与神经网络模型选择

马产业作为内蒙古自治区的重要支柱产业,其健康发展离不开高效精准的马匹个体识别和品种鉴定技术。传统的马匹识别方法如烙印法和芯片植入法属于侵入性操作,不仅会对马匹造成一定程度的身体伤害,还存在引发应激反应的潜在风险。分子生物学鉴定虽然准确性较高,但检测成本昂贵且周期较长,难以在大规模养殖场景中推广应用。人工经验判断则受限于从业人员的专业水平,识别准确率参差不齐。因此,开发一种非接触式、快速准确的马匹面部识别系统具有重要的现实意义和应用价值。

本研究首先在自然养殖环境下采集马匹面部图像数据,构建了专门用于深度学习训练的图像数据集。针对马个体识别任务,选取了十三匹温血马作为研究对象,在不同光照条件、拍摄角度和表情状态下采集其正面和侧面面部照片,形成了覆盖多种变化因素的个体面部图像库。针对马品种分类任务,分别采集了乌珠穆沁蒙古马、太阳花马和纯血马三个品种的面部图像,总计超过两万张,经过去重、筛选和标注处理后,构建了品种分类数据集。图像预处理阶段对原始照片进行尺寸统一、直方图均衡化和数据增强等操作,增强数据多样性的同时保证了图像质量的一致性。

神经网络模型的选择直接影响识别系统的性能表现。本研究选取了当前计算机视觉领域应用广泛的五种经典卷积神经网络模型进行对比实验,包括轻量级网络MobileNetv2、密集连接网络DenseNet121、高效网络EfficientNet以及残差网络ResNet50和ResNet101。这些模型在网络深度、参数量和特征提取能力上各有特点。MobileNetv2采用深度可分离卷积设计,计算效率高但特征表达能力相对有限;DenseNet121通过密集连接增强了特征复用;EfficientNet采用复合缩放策略平衡了网络各维度;ResNet系列通过残差学习解决了深层网络训练困难的问题。实验结果表明,在马个体面部识别任务中,ResNet101取得了最优性能,识别准确率达到百分之九十三点五,显著优于其他对比模型,证明了深层残差网络在捕捉马面部细微差异特征方面的优越性。

(2)融合ECA注意力机制的改进ResNet马品种分类模型

马品种分类任务相比个体识别更具挑战性,不同品种之间既存在显著的外观差异,同时同一品种内部个体之间的差异也较为明显。太阳花马作为纯血马与蒙古马的杂交后代,其面部特征兼具两个亲本品种的特点,与亲本品种之间存在一定的形态相似性,这对分类模型的特征判别能力提出了更高要求。为了提升品种分类的准确率,本研究提出将高效通道注意力机制ECA-Net融入ResNet网络架构,构建改进的ResNet-ECA分类模型。

ECA-Net注意力机制是一种轻量级的通道注意力模块,其核心思想是通过一维卷积操作自适应地学习通道间的依赖关系,在几乎不增加模型参数量的前提下显著提升特征表达能力。与传统的SE注意力模块相比,ECA-Net避免了全连接层带来的参数膨胀问题,同时通过局部跨通道交互捕获了更有效的通道相关性。具体实现中,ECA模块首先对输入特征图进行全局平均池化,压缩空间维度得到通道描述向量,然后通过自适应确定卷积核大小的一维卷积生成通道权重,最后将权重与原始特征图相乘实现通道维度的特征重标定。

改进模型的构建过程中,首先对标准ResNet网络进行适度精简,在保留核心残差块结构的基础上减少冗余层数,降低模型复杂度以适应马品种分类这一相对简单的分类任务。随后在精简网络的关键位置嵌入ECA-Net注意力模块,使网络能够自动聚焦于马面部图像中具有品种判别意义的关键区域特征。训练策略采用迁移学习方法,利用在ImageNet数据集上预训练的模型权重作为初始化参数,在马品种数据集上进行微调训练。实验结果表明,改进的ResNet-ECA模型在品种分类任务上达到了百分之九十八点六九的准确率,相比原始ResNet模型提升了六点三九个百分点,相比DenseNet模型提升了三点二七个百分点,充分验证了注意力机制对于增强网络特征判别能力的有效性。

(3)基于类激活热力图的太阳花马面部特征可视化分析

深度学习模型虽然能够取得优异的分类性能,但其决策过程往往缺乏可解释性,难以直观理解模型究竟学习到了哪些区分不同品种的关键特征。类激活热力图技术为解决这一问题提供了有效手段,通过可视化网络对输入图像各区域的关注程度,揭示模型进行分类决策时所依赖的特征区域。本研究采用梯度加权类激活映射方法,对训练好的品种分类模型进行可视化分析,深入探究太阳花马与其亲本品种在面部特征上的差异表现。

热力图生成的基本原理是计算目标类别得分对网络最后一个卷积层特征图的梯度,将梯度作为权重对特征图进行加权求和,得到反映各空间位置对分类结果贡献程度的热力图。热力图中颜色越亮的区域表示该区域对分类决策的影响越大,即模型认为该区域包含的特征信息对于区分当前类别最为关键。通过对大量样本的热力图进行统计分析,可以总结出不同品种马面部的判别性特征分布规律。

可视化分析结果揭示了多项重要发现。首先,神经网络用于品种识别的马面部特征信息主要集中在颌面部区域,该区域的骨骼结构和肌肉纹理在不同品种间存在稳定的形态差异。其次,太阳花马与纯血马的区别特征主要体现在眼部、额部和鼻部的局部区域,这些区域在热力图中呈现出较为明显的激活差异,表明两个品种在这些部位的形态特征具有可辨识性。相比之下,乌珠穆沁蒙古马与太阳花马之间的差异更多表现在面部表皮颜色上,由于蒙古马毛色普遍较深而太阳花马继承了纯血马的浅色毛皮特征,这种颜色差异过于显著以至于网络无需关注精细的形态特征即可做出判断,因此热力图激活相对不明显。这些可视化分析结论不仅验证了深度学习模型的合理性,还为后续开展马面部表型相关基因筛选研究提供了重要的形态学参考依据。

import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
from torchvision import models
import rasterio
from scipy.interpolate import griddata
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, roc_auc_score

class GeologicalDataset(Dataset):
    def __init__(self, features, labels):
        self.features = torch.FloatTensor(features)
        self.labels = torch.LongTensor(labels)
    
    def __len__(self):
        return len(self.labels)
    
    def __getitem__(self, idx):
        return self.features[idx], self.labels[idx]

class GeochemistryProcessor:
    def __init__(self, grid_resolution=100):
        self.grid_resolution = grid_resolution
    
    def inverse_distance_interpolation(self, points, values, grid_bounds, power=2):
        x_min, x_max, y_min, y_max = grid_bounds
        grid_x = np.linspace(x_min, x_max, self.grid_resolution)
        grid_y = np.linspace(y_min, y_max, self.grid_resolution)
        grid_xx, grid_yy = np.meshgrid(grid_x, grid_y)
        grid_points = np.column_stack([grid_xx.ravel(), grid_yy.ravel()])
        interpolated = griddata(points, values, grid_points, method='linear')
        interpolated = interpolated.reshape(self.grid_resolution, self.grid_resolution)
        return interpolated
    
    def buffer_distance_density(self, features, grid_bounds, buffer_radius=500):
        x_min, x_max, y_min, y_max = grid_bounds
        grid_x = np.linspace(x_min, x_max, self.grid_resolution)
        grid_y = np.linspace(y_min, y_max, self.grid_resolution)
        density = np.zeros((self.grid_resolution, self.grid_resolution))
        for i, x in enumerate(grid_x):
            for j, y in enumerate(grid_y):
                distances = np.sqrt((features[:, 0] - x)**2 + (features[:, 1] - y)**2)
                density[j, i] = np.sum(distances < buffer_radius)
        return density / density.max()

class MineralPredictionCNN(nn.Module):
    def __init__(self, in_channels, num_classes=2):
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(in_channels, 32, 3, padding=1), nn.BatchNorm2d(32), nn.ReLU(),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 3, padding=1), nn.BatchNorm2d(64), nn.ReLU(),
            nn.MaxPool2d(2),
            nn.Conv2d(64, 128, 3, padding=1), nn.BatchNorm2d(128), nn.ReLU(),
            nn.AdaptiveAvgPool2d((4, 4)))
        self.classifier = nn.Sequential(
            nn.Linear(128 * 16, 256), nn.ReLU(), nn.Dropout(0.5),
            nn.Linear(256, num_classes))
    
    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), -1)
        return self.classifier(x)

class ResNetPredictor(nn.Module):
    def __init__(self, in_channels, num_classes=2):
        super().__init__()
        self.input_conv = nn.Conv2d(in_channels, 3, 1)
        resnet = models.resnet18(pretrained=True)
        self.backbone = nn.Sequential(*list(resnet.children())[:-1])
        self.fc = nn.Linear(512, num_classes)
    
    def forward(self, x):
        x = self.input_conv(x)
        x = self.backbone(x)
        x = x.view(x.size(0), -1)
        return self.fc(x)

class DenseNetPredictor(nn.Module):
    def __init__(self, in_channels, num_classes=2):
        super().__init__()
        self.input_conv = nn.Conv2d(in_channels, 3, 1)
        densenet = models.densenet121(pretrained=True)
        self.backbone = densenet.features
        self.classifier = nn.Linear(1024, num_classes)
    
    def forward(self, x):
        x = self.input_conv(x)
        x = self.backbone(x)
        x = nn.functional.adaptive_avg_pool2d(x, (1, 1))
        x = x.view(x.size(0), -1)
        return self.classifier(x)

class EnsemblePredictor:
    def __init__(self, models, weights=None):
        self.models = models
        self.weights = weights if weights else [1.0 / len(models)] * len(models)
    
    def predict_proba(self, x):
        predictions = []
        for model in self.models:
            model.eval()
            with torch.no_grad():
                logits = model(x)
                probs = torch.softmax(logits, dim=1)
                predictions.append(probs)
        weighted_sum = sum(w * p for w, p in zip(self.weights, predictions))
        return weighted_sum
    
    def predict(self, x):
        probs = self.predict_proba(x)
        return probs.argmax(dim=1)

class MineralProspectivityMapper:
    def __init__(self, ensemble_predictor, grid_shape):
        self.predictor = ensemble_predictor
        self.grid_shape = grid_shape
    
    def generate_favorability_map(self, geological_data):
        device = next(self.predictor.models[0].parameters()).device
        n_samples = geological_data.shape[0]
        favorability = np.zeros(n_samples)
        batch_size = 32
        for i in range(0, n_samples, batch_size):
            batch = torch.FloatTensor(geological_data[i:i+batch_size]).to(device)
            with torch.no_grad():
                probs = self.predictor.predict_proba(batch)
                favorability[i:i+batch_size] = probs[:, 1].cpu().numpy()
        return favorability.reshape(self.grid_shape)
    
    def delineate_prospect_areas(self, favorability_map, threshold=0.7):
        prospect_mask = favorability_map > threshold
        return prospect_mask

def train_single_model(model, train_loader, val_loader, epochs=50, lr=0.001):
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    model.to(device)
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=lr, weight_decay=1e-4)
    scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience=5)
    best_auc = 0.0
    for epoch in range(epochs):
        model.train()
        for features, labels in train_loader:
            features, labels = features.to(device), labels.to(device)
            optimizer.zero_grad()
            outputs = model(features)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()
        model.eval()
        val_probs, val_labels = [], []
        with torch.no_grad():
            for features, labels in val_loader:
                features = features.to(device)
                outputs = model(features)
                probs = torch.softmax(outputs, dim=1)[:, 1]
                val_probs.extend(probs.cpu().numpy())
                val_labels.extend(labels.numpy())
        auc = roc_auc_score(val_labels, val_probs)
        scheduler.step(1 - auc)
        if auc > best_auc:
            best_auc = auc
            torch.save(model.state_dict(), f'{model.__class__.__name__}_best.pth')
    return model, best_auc

def create_sample_patches(raster_data, mineral_points, patch_size=32):
    height, width = raster_data.shape[1], raster_data.shape[2]
    positive_patches, negative_patches = [], []
    half_size = patch_size // 2
    for x, y in mineral_points:
        if half_size <= x < width - half_size and half_size <= y < height - half_size:
            patch = raster_data[:, y-half_size:y+half_size, x-half_size:x+half_size]
            positive_patches.append(patch)
    n_negative = len(positive_patches) * 3
    for _ in range(n_negative):
        x = np.random.randint(half_size, width - half_size)
        y = np.random.randint(half_size, height - half_size)
        min_dist = min(np.sqrt((x - px)**2 + (y - py)**2) for px, py in mineral_points)
        if min_dist > patch_size * 2:
            patch = raster_data[:, y-half_size:y+half_size, x-half_size:x+half_size]
            negative_patches.append(patch)
    return np.array(positive_patches), np.array(negative_patches)

def main():
    grid_size = 100
    n_channels = 6
    synthetic_data = np.random.rand(n_channels, grid_size, grid_size).astype(np.float32)
    mineral_points = [(30, 40), (50, 60), (70, 30), (45, 80), (25, 55)]
    positive, negative = create_sample_patches(synthetic_data, mineral_points)
    if len(positive) > 0 and len(negative) > 0:
        X = np.concatenate([positive, negative[:len(positive)*2]])
        y = np.concatenate([np.ones(len(positive)), np.zeros(len(positive)*2)])
    else:
        X = np.random.rand(100, n_channels, 32, 32).astype(np.float32)
        y = np.random.randint(0, 2, 100)
    X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2)
    train_dataset = GeologicalDataset(X_train, y_train)
    val_dataset = GeologicalDataset(X_val, y_val)
    train_loader = DataLoader(train_dataset, batch_size=16, shuffle=True)
    val_loader = DataLoader(val_dataset, batch_size=16)
    models_list = [
        MineralPredictionCNN(n_channels),
        ResNetPredictor(n_channels),
        DenseNetPredictor(n_channels)]
    trained_models = []
    model_weights = []
    for model in models_list:
        trained, auc = train_single_model(model, train_loader, val_loader, epochs=10)
        trained_models.append(trained)
        model_weights.append(auc)
    weight_sum = sum(model_weights)
    model_weights = [w / weight_sum for w in model_weights]
    ensemble = EnsemblePredictor(trained_models, model_weights)
    mapper = MineralProspectivityMapper(ensemble, (grid_size, grid_size))
    print("Ensemble model created with weights:", model_weights)

if __name__ == "__main__":
    main()


如有问题,可以直接沟通

👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇

更多推荐