昇思25天学习打卡营第17天|应用实践之SSD目标检测

基本介绍

        今天要学习的内容是计算机视觉领域中的目标检测任务。与图像分类相比,目标检测更难,因为目标检测不仅要检测出图片中的物体的类别,还要检测出该物体的位置。现主流的目标检测算法大致可分为两种,一种是基于CNN的,另一种是基于Transformer的。基于CNN的还可以细分为三种,以Faster R-CNN为代表的一阶段目标检测,以Yolo为代表的二阶段目标检测,以及Anchor-free算法。本文会先简单介绍一下SSD模型,然后在MindSpore框架下,使用COCO2017数据集训练SSD模型,并进行模型评估。

SSD模型简介

        SSD与Yolo一样,都是一阶段目标检测算法,是直接通过主干网络给出类别位置信息,不需要区域生成。此外,SSD通过卷积神经网络进行特征提取,取不同的特征层进行检测输出,所以SSD是一种多尺度的检测方法,在需要检测的特征层,直接使用一个3 ×× 3卷积,进行通道的变换。SSD的框架图如下图所示:

        SSD采用VGG16作为基础模型,然后在VGG16的基础上新增了卷积层来获得更多的特征图以用于检测。如果VGG16,也可以使用ResNet19,ResNet50等作为基础模型。SSD的网络结构如下图所示

可大致分为四个模块:VGG base Layer,Extra Feature Layer,Detection Layer、NMS

  • VGG base Layer

        如下图所示,VGG base Layer也就是backbone layer

输入图像经过预处理后大小固定为300×300,首先经过backbone,本案例中使用的是VGG16网络的前13个卷积层,然后分别将VGG16的全连接层fc6和fc7转换成3 ×× 3卷积层block6和1 ×× 1卷积层block7,进一步提取特征。 在block6中,使用了空洞数为6的空洞卷积,其padding也为6,这样做同样也是为了增加感受野的同时保持参数量与特征图尺寸的不变

  • Extra Feature Layer

        Extra Feature Layer是在VGG16的基础增加的特征提取层,用于提取更高层的语义信息,具体结构如下:

block8-11,用于更高语义信息的提取。block8的通道数为512,而block9、block10与block11的通道数都为256。从block7到block11,这5个卷积后输出特征图的尺寸依次为19×19、10×10、5×5、3×3和1×1。为了降低参数量,使用了1×1卷积先降低通道数为该层输出通道数的一半,再利用3×3卷积进行特征提取

  • Detection Layer

        Detection Layer负责类别和位置预测,这需要借助anchor实现。SSD模型一共有6个预测特征图,对于其中一个尺寸为m*n,通道为p的预测特征图,假设其每个像素点会产生k个anchor,每个anchor会对应c个类别和4个回归偏移量,使用(4+c)k个尺寸为3x3,通道为p的卷积核对该预测特征图进行卷积操作,得到尺寸为m*n,通道为(4+c)m*k的输出特征图,它包含了预测特征图上所产生的每个anchor的回归偏移量和各类别概率分数。

  • NMS

        NMS即非极大抑制法,训练过程不用,只用在推理过程。其算法流程如下

SSD代码实践

数据集准备

        我们将会使用COCO2017数据集进行训练,COCO2017数据集很容易下载,加载到内存的操作也很简单,我们将重点放在SSD模型的数据增强方法。为了使模型对于各种输入对象大小和形状更加鲁棒,SSD算法每个训练图像通过以下选项之一随机采样:

  • 使用整个原始输入图像

  • 采样一个区域,使采样区域和原始图片最小的交并比重叠为0.1,0.3,0.5,0.7或0.9

  • 随机采样一个区域

每个采样区域的大小为原始图像大小的[0.3,1],长宽比在1/2和2之间。如果真实标签框中心在采样区域内,则保留两者重叠部分作为新图片的真实标注框。在上述采样步骤之后,将每个采样区域大小调整为固定大小,并以0.5的概率水平翻转。其代码实现如下:

import cv2
import numpy as np

def _rand(a=0., b=1.):
    return np.random.rand() * (b - a) + a

def intersect(box_a, box_b):
    """Compute the intersect of two sets of boxes."""
    max_yx = np.minimum(box_a[:, 2:4], box_b[2:4])
    min_yx = np.maximum(box_a[:, :2], box_b[:2])
    inter = np.clip((max_yx - min_yx), a_min=0, a_max=np.inf)
    return inter[:, 0] * inter[:, 1]

def jaccard_numpy(box_a, box_b):
    """Compute the jaccard overlap of two sets of boxes."""
    inter = intersect(box_a, box_b)
    area_a = ((box_a[:, 2] - box_a[:, 0]) *
              (box_a[:, 3] - box_a[:, 1]))
    area_b = ((box_b[2] - box_b[0]) *
              (box_b[3] - box_b[1]))
    union = area_a + area_b - inter
    return inter / union

def random_sample_crop(image, boxes):
    """Crop images and boxes randomly."""
    height, width, _ = image.shape
    min_iou = np.random.choice([None, 0.1, 0.3, 0.5, 0.7, 0.9])

    if min_iou is None:
        return image, boxes

    for _ in range(50):
        image_t = image
        w = _rand(0.3, 1.0) * width
        h = _rand(0.3, 1.0) * height
        # aspect ratio constraint b/t .5 & 2
        if h / w < 0.5 or h / w > 2:
            continue

        left = _rand() * (width - w)
        top = _rand() * (height - h)
        rect = np.array([int(top), int(left), int(top + h), int(left + w)])
        overlap = jaccard_numpy(boxes, rect)

        # dropout some boxes
        drop_mask = overlap > 0
        if not drop_mask.any():
            continue

        if overlap[drop_mask].min() < min_iou and overlap[drop_mask].max() > (min_iou + 0.2):
            continue

        image_t = image_t[rect[0]:rect[2], rect[1]:rect[3], :]
        centers = (boxes[:, :2] + boxes[:, 2:4]) / 2.0
        m1 = (rect[0] < centers[:, 0]) * (rect[1] < centers[:, 1])
        m2 = (rect[2] > centers[:, 0]) * (rect[3] > centers[:, 1])

        # mask in that both m1 and m2 are true
        mask = m1 * m2 * drop_mask

        # have any valid boxes? try again if not
        if not mask.any():
            continue

        # take only matching gt boxes
        boxes_t = boxes[mask, :].copy()
        boxes_t[:, :2] = np.maximum(boxes_t[:, :2], rect[:2])
        boxes_t[:, :2] -= rect[:2]
        boxes_t[:, 2:4] = np.minimum(boxes_t[:, 2:4], rect[2:4])
        boxes_t[:, 2:4] -= rect[:2]

        return image_t, boxes_t
    return image, boxes

def ssd_bboxes_encode(boxes):
    """Labels anchors with ground truth inputs."""

    def jaccard_with_anchors(bbox):
        """Compute jaccard score a box and the anchors."""
        # Intersection bbox and volume.
        ymin = np.maximum(y1, bbox[0])
        xmin = np.maximum(x1, bbox[1])
        ymax = np.minimum(y2, bbox[2])
        xmax = np.minimum(x2, bbox[3])
        w = np.maximum(xmax - xmin, 0.)
        h = np.maximum(ymax - ymin, 0.)

        # Volumes.
        inter_vol = h * w
        union_vol = vol_anchors + (bbox[2] - bbox[0]) * (bbox[3] - bbox[1]) - inter_vol
        jaccard = inter_vol / union_vol
        return np.squeeze(jaccard)

    pre_scores = np.zeros((8732), dtype=np.float32)
    t_boxes = np.zeros((8732, 4), dtype=np.float32)
    t_label = np.zeros((8732), dtype=np.int64)
    for bbox in boxes:
        label = int(bbox[4])
        scores = jaccard_with_anchors(bbox)
        idx = np.argmax(scores)
        scores[idx] = 2.0
        mask = (scores > matching_threshold)
        mask = mask & (scores > pre_scores)
        pre_scores = np.maximum(pre_scores, scores * mask)
        t_label = mask * label + (1 - mask) * t_label
        for i in range(4):
            t_boxes[:, i] = mask * bbox[i] + (1 - mask) * t_boxes[:, i]

    index = np.nonzero(t_label)

    # Transform to tlbr.
    bboxes = np.zeros((8732, 4), dtype=np.float32)
    bboxes[:, [0, 1]] = (t_boxes[:, [0, 1]] + t_boxes[:, [2, 3]]) / 2
    bboxes[:, [2, 3]] = t_boxes[:, [2, 3]] - t_boxes[:, [0, 1]]

    # Encode features.
    bboxes_t = bboxes[index]
    default_boxes_t = default_boxes[index]
    bboxes_t[:, :2] = (bboxes_t[:, :2] - default_boxes_t[:, :2]) / (default_boxes_t[:, 2:] * 0.1)
    tmp = np.maximum(bboxes_t[:, 2:4] / default_boxes_t[:, 2:4], 0.000001)
    bboxes_t[:, 2:4] = np.log(tmp) / 0.2
    bboxes[index] = bboxes_t

    num_match = np.array([len(np.nonzero(t_label)[0])], dtype=np.int32)
    return bboxes, t_label.astype(np.int32), num_match

def preprocess_fn(img_id, image, box, is_training):
    """Preprocess function for dataset."""
    cv2.setNumThreads(2)

    def _infer_data(image, input_shape):
        img_h, img_w, _ = image.shape
        input_h, input_w = input_shape

        image = cv2.resize(image, (input_w, input_h))

        # When the channels of image is 1
        if len(image.shape) == 2:
            image = np.expand_dims(image, axis=-1)
            image = np.concatenate([image, image, image], axis=-1)

        return img_id, image, np.array((img_h, img_w), np.float32)

    def _data_aug(image, box, is_training, image_size=(300, 300)):
        ih, iw, _ = image.shape
        h, w = image_size
        if not is_training:
            return _infer_data(image, image_size)
        # Random crop
        box = box.astype(np.float32)
        image, box = random_sample_crop(image, box)
        ih, iw, _ = image.shape
        # Resize image
        image = cv2.resize(image, (w, h))
        # Flip image or not
        flip = _rand() < .5
        if flip:
            image = cv2.flip(image, 1, dst=None)
        # When the channels of image is 1
        if len(image.shape) == 2:
            image = np.expand_dims(image, axis=-1)
            image = np.concatenate([image, image, image], axis=-1)
        box[:, [0, 2]] = box[:, [0, 2]] / ih
        box[:, [1, 3]] = box[:, [1, 3]] / iw
        if flip:
            box[:, [1, 3]] = 1 - box[:, [3, 1]]
        box, label, num_match = ssd_bboxes_encode(box)
        return image, box, label, num_match

    return _data_aug(image, box, is_training, image_size=[300, 300])
模型搭建

        借助MindSpore可以很快搭建出模型,模型代码如下:

class SSD300Vgg16(nn.Cell):
    """SSD300Vgg16 module."""

    def __init__(self):
        super(SSD300Vgg16, self).__init__()

        # VGG16 backbone: block1~5
        self.backbone = Vgg16()

        # SSD blocks: block6~7
        self.b6_1 = nn.Conv2d(in_channels=512, out_channels=1024, kernel_size=3, padding=6, dilation=6, pad_mode='pad')
        self.b6_2 = nn.Dropout(p=0.5)

        self.b7_1 = nn.Conv2d(in_channels=1024, out_channels=1024, kernel_size=1)
        self.b7_2 = nn.Dropout(p=0.5)

        # Extra Feature Layers: block8~11
        self.b8_1 = nn.Conv2d(in_channels=1024, out_channels=256, kernel_size=1, padding=1, pad_mode='pad')
        self.b8_2 = nn.Conv2d(in_channels=256, out_channels=512, kernel_size=3, stride=2, pad_mode='valid')

        self.b9_1 = nn.Conv2d(in_channels=512, out_channels=128, kernel_size=1, padding=1, pad_mode='pad')
        self.b9_2 = nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, stride=2, pad_mode='valid')

        self.b10_1 = nn.Conv2d(in_channels=256, out_channels=128, kernel_size=1)
        self.b10_2 = nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, pad_mode='valid')

        self.b11_1 = nn.Conv2d(in_channels=256, out_channels=128, kernel_size=1)
        self.b11_2 = nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, pad_mode='valid')

        # boxes
        self.multi_box = MultiBox()

    def construct(self, x):
        # VGG16 backbone: block1~5
        block4, x = self.backbone(x)

        # SSD blocks: block6~7
        x = self.b6_1(x)  # 1024
        x = self.b6_2(x)

        x = self.b7_1(x)  # 1024
        x = self.b7_2(x)
        block7 = x

        # Extra Feature Layers: block8~11
        x = self.b8_1(x)  # 256
        x = self.b8_2(x)  # 512
        block8 = x

        x = self.b9_1(x)  # 128
        x = self.b9_2(x)  # 256
        block9 = x

        x = self.b10_1(x)  # 128
        x = self.b10_2(x)  # 256
        block10 = x

        x = self.b11_1(x)  # 128
        x = self.b11_2(x)  # 256
        block11 = x

        # boxes
        multi_feature = (block4, block7, block8, block9, block10, block11)
        pred_loc, pred_label = self.multi_box(multi_feature)
        if not self.training:
            pred_label = ops.sigmoid(pred_label)
        pred_loc = pred_loc.astype(ms.float32)
        pred_label = pred_label.astype(ms.float32)
        return pred_loc, pred_label
模型训练

        模型训练时,使用上述所说的数据增强方式,损失韩式是类别损失函数和位置损失函数的加权和,设置模型训练的epoch次数为60,然后通过create_ssd_dataset类创建了训练集和验证集。batch_size大小为5,图像尺寸统一调整为300×300。损失函数使用位置损失函数和置信度损失函数的加权和,优化器使用Momentum,并设置初始学习率为0.001。回调函数方面使用了LossMonitor和TimeMonitor来监控训练过程中每个epoch结束后,损失值Loss的变化情况以及每个epoch、每个step的运行时间。设置每训练10个epoch保存一次模型。具体代码如下:

dataset = create_ssd_dataset(mindrecord_file, batch_size=5, rank=0, use_multiprocessing=True)
dataset_size = dataset.get_dataset_size()

image, get_loc, gt_label, num_matched_boxes = next(dataset.create_tuple_iterator())

# Network definition and initialization
network = SSD300Vgg16()
init_net_param(network)

# Define the learning rate
lr = Tensor(get_lr(global_step=0 * dataset_size,
                   lr_init=0.001, lr_end=0.001 * 0.05, lr_max=0.05,
                   warmup_epochs=2, total_epochs=60, steps_per_epoch=dataset_size))

# Define the optimizer
opt = nn.Momentum(filter(lambda x: x.requires_grad, network.get_parameters()), lr,
                  0.9, 0.00015, float(1024))

# Define the forward procedure
def forward_fn(x, gt_loc, gt_label, num_matched_boxes):
    pred_loc, pred_label = network(x)
    mask = ops.less(0, gt_label).astype(ms.float32)
    num_matched_boxes = ops.sum(num_matched_boxes.astype(ms.float32))

    # Positioning loss
    mask_loc = ops.tile(ops.expand_dims(mask, -1), (1, 1, 4))
    smooth_l1 = nn.SmoothL1Loss()(pred_loc, gt_loc) * mask_loc
    loss_loc = ops.sum(ops.sum(smooth_l1, -1), -1)

    # Category loss
    loss_cls = class_loss(pred_label, gt_label)
    loss_cls = ops.sum(loss_cls, (1, 2))

    return ops.sum((loss_cls + loss_loc) / num_matched_boxes)

grad_fn = ms.value_and_grad(forward_fn, None, opt.parameters, has_aux=False)
loss_scaler = DynamicLossScaler(1024, 2, 1000)

# Gradient updates
def train_step(x, gt_loc, gt_label, num_matched_boxes):
    loss, grads = grad_fn(x, gt_loc, gt_label, num_matched_boxes)
    opt(grads)
    return loss

print("=================== Starting Training =====================")
for epoch in range(60):
    network.set_train(True)
    begin_time = time.time()
    for step, (image, get_loc, gt_label, num_matched_boxes) in enumerate(dataset.create_tuple_iterator()):
        loss = train_step(image, get_loc, gt_label, num_matched_boxes)
    end_time = time.time()
    times = end_time - begin_time
    print(f"Epoch:[{int(epoch + 1)}/{int(60)}], "
          f"loss:{loss} , "
          f"time:{times}s ")
ms.save_checkpoint(network, "ssd-60_9.ckpt")
print("=================== Training Success =====================")
模型评估

        训练好自然就要进行模型评估,本次使用的评估指标是目标检测领域的经典指标Average Precision、Average Recall和mAP。评估结果如下:

可以看出:好像各个评价指标的表现都很一般,我个人认为有两个原因,一个是测试数据集太少了,只有9张图片好像;另一个是,SSD模型对中小物体的检测能力本来就比较弱,所以差一些。

总结

        今天所学习的SSD是有些难度的,虽然我之前接触目标检测算法比较多,但还是第一接触SSD算法。不过得益于之前积累的经验,今天的很多东西能比较快速理解。今天运行的SSD模型的效果比最新的Yolo差很多,但有其优点。此外,本人在这里只是回顾一些官方文档中的一些重要部分,SSD更详细的讲解和代码讲解还是要看官方文档的。

Jupyter在线运行情况

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/791776.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MQTT是什么,物联网

写文思路&#xff1a; 以下从几个方面介绍MQTT&#xff0c;包括&#xff1a;MQTT是什么&#xff0c;MQTT和webSocket的结合&#xff0c;以及使用场景&#xff0c; 一、MQTT是什么 MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级的发布/订阅消息…

Java技术栈总结:JVM虚拟机篇

一、Java的四种引用类型 1、强引用 最常见的引用&#xff0c;类似Object obj new Object()、String str “hello”。 如果一个对象具有强引用&#xff0c;垃圾回收器绝对不会回收它。即使抛出“OutOfMemoryError”错误&#xff0c;程序终止&#xff0c;也不会随意回收具有强…

市场趋势的智能预测:Kompas.ai如何洞察未来市场动向

在商业领域&#xff0c;市场趋势预测是企业制定战略规划和做出明智决策的关键。准确把握市场动向能够帮助企业及时调整战略&#xff0c;抓住机遇&#xff0c;规避风险。Kompas.ai&#xff0c;一款先进的人工智能市场分析工具&#xff0c;正通过其深度学习和数据分析能力&#x…

重塑肌肤DNA!华贝甄选解锁生命活力密码

在探索生命奥秘与健康的征途中&#xff0c;华贝甄选携手前沿干细胞科技&#xff0c;为您开启一场前所未有的健康革命。我们深知&#xff0c;生命的活力源自细胞的不懈更新与修复&#xff0c;而干细胞&#xff0c;正是这场生命奇迹的钥匙。 【重塑内分泌平衡&#xff0c;焕发自…

Sentieon Arm版本:进一步降低基因组计算成本

前不久&#xff0c;Arm在其社区的HPC blog上发布了一篇Sentieon在低通量全基因组&#xff08;LP-WGS&#xff09;的应用案例。 图1 伴随着大规模基因组学的需求持续增长&#xff0c;基因测序成本的降低使得研究和分析更加广泛。而在基因组学的每一个应用背后,都有一系列计算密…

入驻长沙!全球数据资产理事会长江中游中心挂牌成立

在全球数据资产化浪潮的推动下&#xff0c;长江中游地区迎来了其在数字经济领域的重要里程碑。 近日&#xff0c;《数据资产长江中游生态论坛暨数据资产入表和价值转化研讨会》于长沙圆满落幕&#xff0c;会上各产业专家、企业高管&#xff0c;围绕数据产品开发、数据资产融资…

ICC2:如何检查input floating

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 input floating分为两种情况&#xff1a; input没接net的情况: get_flat_pins -f "direction &#xff1d;&#xff1d;in && undefined(net)" input接net…

数据结构:链表相关题目

链表反转 LeetCode地址&#xff1a;LCR 024. 反转链表 - 力扣&#xff08;LeetCode&#xff09; 头插法&#xff1a; class Solution {public ListNode reverseList(ListNode head) {ListNode h1 new ListNode(-1);while(head!null){ListNode index new ListNode(head.val…

SQL职场必备:掌握数据库技能提升职场竞争力

&#x1f482; 个人网站:【 摸鱼游戏】【网址导航】【神级代码资源网站】&#x1f91f; 一站式轻松构建小程序、Web网站、移动应用&#xff1a;&#x1f449;注册地址&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交…

顺序结构 ( 五 ) —— 数据输入输出 【互三互三】

文章目录 &#x1f341;序 &#x1f341;一、字符输入函数getchar &#x1f341;二、字符输出函数putchar &#x1f341;三、通过cout流输出数据 &#x1f341;四、通过cin流读入数据 &#x1f341;五、格式化输入函数scanf &#x1f341;六、格式化输出函数printf &…

【每日一练】python面对对象的基本概念和用法(附实例)

面向对象编程&#xff08;OOP&#xff09;是一种程序设计方法&#xff0c;其基本概念包括对象、类、继承和封装。 对象&#xff1a;对象是系统中的基本单位&#xff0c;用于描述客观事物。每个对象包含一组属性和对这些属性进行操作的方法。对象是类的一个实例&#xff0c;具有…

高通开发系列 - LCD屏调试过程中的一些技巧

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 返回:专栏总目录 目录 打开开发者模式wifi adb shellmodetest测试使用异显CPU和GPU加速开性能模式进程绑定大核CPU设置和查询进程状态视频…

Java 中的阻塞 IO 和非阻塞 IO

Java 中的阻塞 IO 和非阻塞 IO 1、阻塞 IO&#xff08;Blocking IO&#xff09;2、非阻塞 IO&#xff08;Non-blocking IO&#xff09;3、区别与应用场景4、总结 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; IO&#xff08;输入输出&…

文华财经盘立方博易大师boll布林带指标公式源码

TT:TIME>850&&TIME<1150; MID:MA(CLOSE,26);//求N个周期的收盘价均线&#xff0c;称为布林通道中轨 TMP2:STD(CLOSE,26);//求M个周期内的收盘价的标准差 TOP:MID2*TMP2;//布林通道上轨 BOTTOM:MID-2*TMP2;//布林通道下轨 A:EVERY(ISDOWN,2)&&TT&&…

Python机器学习推理工程化落地步骤指南

目录 一、引言 二、数据准备 2.1 数据收集 2.2 数据清洗 2.3 特征工程 2.4 数据分割 三、模型训练 3.1 选择算法 3.2 训练模型 3.3 模型评估 3.4 模型调优 四、模型部署 4.1 模型序列化 4.2 构建推理服务 4.3 部署与监控 五、总结 在当今科技飞速发展的时代…

text prompt如何超过77个词

【深度学习】sdwebui的token_counter,update_token_counter,如何超出77个token的限制?对提示词加权的底层实现_prompt中token权重-CSDN博客文章浏览阅读1.6k次,点赞26次,收藏36次。文章探讨了如何在StableDiffusionProcessing中处理超过77个token的提示,涉及token_counte…

【面试题】防火墙的部署模式有哪些?

防火墙的部署模式多种多样&#xff0c;每种模式都有其特定的应用场景和优缺点。以下是防火墙的主要部署模式&#xff1a; 一、按工作模式分类 路由模式 定义&#xff1a;当防火墙位于内部网络和外部网络之间时&#xff0c;需要将防火墙与内部网络、外部网络以及DMZ&#xff0…

mindspore打卡20天之Shufflenet图像分类

ShuffleNet图像分类 当前案例不支持在GPU设备上静态图模式运行&#xff0c;其他模式运行皆支持。 ShuffleNet网络介绍 ShuffleNetV1是旷视科技提出的一种计算高效的CNN模型&#xff0c;和MobileNet, SqueezeNet等一样主要应用在移动端&#xff0c;所以模型的设计目标就是利用有…

对于人机结合+人工智能的一点思考

开题失败后看了不少论文&#xff0c;人机结合这个方向查了一下……作为毕业论文的题目还真没有&#xff0c;无论是知网公开的还是中科院自建库学生毕业论文都没有这个题目……这实验怎么设计啊……主观的&#xff0c;还要让模型像人&#xff0c;还要让模型更容易被人调教&#…

Android初学者书籍推荐

书单 1.《Android应用开发项目式教程》&#xff0c;机械工业出版社&#xff0c;2024年出版2.《第一行代码Android》第二版3.《第一行代码Android》第三版4.《疯狂Android讲义》第四版5.《Android移动应用基础教程&#xff08;Android Studio 第2版&#xff09;》 从学安卓到用安…