动手学深度学习(Pytorch版)代码实践 -卷积神经网络-30Kaggle竞赛:图片分类

30Kaggle竞赛:图片分类

比赛链接: https://www.kaggle.com/c/classify-leaves

导入包
import torch
import torchvision
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
import numpy as np
import pandas as pd
from torch import nn
import matplotlib.pyplot as plt
from PIL import Image
import os
from torch.nn import functional as F
import torch.optim as optim
import liliPytorch as lp
import torchvision.models as models
预处理:数据集分析
train_path = '../data/classify-leaves/train.csv'
test_path = '../data/classify-leaves/test.csv'
file_path = '../data/classify-leaves/'

# # 读取训练和测试数据
train_data = pd.read_csv(train_path)
test_data = pd.read_csv(test_path)

# 打印数据形状
print(train_data.shape) # (18353, 2)
print(test_data.shape) # (8800, 1)

#生成描述性统计数据
print(train_data.describe())
"""
               image             label
count          18353             18353
unique         18353               176
top     images/0.jpg  maclura_pomifera
freq               1               353
"""

# 查看不同树叶的数量
print(train_data['label'].value_counts())
"""
label
maclura_pomifera            353
ulmus_rubra                 235
prunus_virginiana           223
acer_rubrum                 217
broussonettia_papyrifera    214
                           ... 
cedrus_deodara               58
ailanthus_altissima          58
crataegus_crus-galli         54
evodia_daniellii             53
juniperus_virginiana         51
Name: count, Length: 176, dtype: int64
"""
1.数据处理与加载
train_path = '../data/classify-leaves/train.csv'
test_path = '../data/classify-leaves/test.csv'
file_path = '../data/classify-leaves/'

# 树叶的名字统计
labels_unique = train_data['label'].unique()
# print(labels_unique)

# 树叶标签的数量
labels_num = len(labels_unique)

# 提取出树叶标签,并排序
leaves_labels = sorted(list(set(train_data['label'])))
# print(leaves_labels)

# 将树叶标签对应数字
labels_to_num = dict(zip(leaves_labels, range(labels_num )))
# print(labels_to_num)

# 将数字对应树叶标签(用于后续预测)
num_to_labels = {value : key for key, value in labels_to_num.items()}
# print(num_to_labels)

class LeavesDataset(Dataset):
    def __init__(self, csv_path, file_path, mode='train', valid_ratio=0.2, resize_height=224, resize_width=224):
        """
        初始化 LeavesDataset 对象。
        参数:
            csv_path (str): 包含图像路径和标签的 CSV 文件路径。
            file_path (str): 图像文件所在目录的路径。
            mode (str, optional): 数据集的模式。可以是 'train', 'valid' 或 'test'。默认值为 'train'。
            valid_ratio (float, optional): 用于验证的数据比例。默认值为 0.2。
            resize_height (int, optional): 调整图像高度的大小。默认值为 224。
            resize_width (int, optional): 调整图像宽度的大小。默认值为 224。
        """
        # 存储图像调整大小的高度和宽度
        self.resize_height = resize_height
        self.resize_width = resize_width
        
        # 存储图像文件路径和模式(train/valid/test)
        self.file_path = file_path
        self.mode = mode
        
        # 读取包含图像路径和标签的 CSV 文件
        self.data_info = pd.read_csv(csv_path, header=0)
        
        # 获取样本总数
        self.data_len = len(self.data_info.index)
        
        # 计算训练集样本数
        self.train_len = int(self.data_len * (1 - valid_ratio))

        # 根据模式处理数据
        if self.mode == 'train':
            # 训练模式下的图像和标签
            self.train_img = np.asarray(self.data_info.iloc[0:self.train_len, 0])
            self.train_label = np.asarray(self.data_info.iloc[0:self.train_len, 1])
            self.image_arr = self.train_img
            self.label_arr = self.train_label
        elif self.mode == 'valid':
            # 验证模式下的图像和标签
            self.valid_img = np.asarray(self.data_info.iloc[self.train_len:, 0])
            self.valid_label = np.asarray(self.data_info.iloc[self.train_len:, 1])
            self.image_arr = self.valid_img
            self.label_arr = self.valid_label
        elif self.mode == 'test':
            # 测试模式下的图像
            self.test_img = np.asarray(self.data_info.iloc[:, 0])
            self.image_arr = self.test_img

        # 获取图像数组的长度
        self.len_image = len(self.image_arr)
        print(f'扫描所有 {mode} 数据,共 {self.len_image} 张图像')

    def __getitem__(self, idx):
        """
        获取指定索引的图像和标签。
        参数: idx (int): 标签文本对应编号的索引
        返回:如果是测试模式,返回图像张量;否则返回图像张量和标签。
        """
        # 打开图像文件
        self.img = Image.open(self.file_path + self.image_arr[idx])
        
        if self.mode == 'train':
            # 训练模式下的数据增强
            trans = transforms.Compose([
                transforms.Resize((self.resize_height, self.resize_width)),
                transforms.RandomHorizontalFlip(p=0.5),
                transforms.RandomVerticalFlip(p=0.5),
                transforms.RandomRotation(degrees=30),
                # transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1),
                # transforms.RandomResizedCrop(size=self.resize_height, scale=(0.8, 1.0)),
                transforms.ToTensor(),
                # transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
            ])
            self.img = trans(self.img)
        else:
            # 验证和测试模式下的简单处理
            trans = transforms.Compose([
                transforms.Resize((self.resize_height, self.resize_width)),
                transforms.ToTensor(),
                # transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
            ])
            self.img = trans(self.img)
        
        if self.mode == 'test':
            return self.img
        else:
            # 获取标签文本对应的编号
            self.label = labels_to_num[self.label_arr[idx]]
            return self.img, self.label

    def __call__(self, idx):
        """
        使对象可以像函数一样被调用。
        参数:idx (int):标签文本对应编号的索引 
        返回: 调用 __getitem__ 方法并返回结果。
        """
        return self.__getitem__(idx)

    def __len__(self):
        """
        获取数据集的长度。
        返回: 数据集中图像的数量。
        """
        return self.len_image
 
train_dataset = LeavesDataset(train_path, file_path)
valid_dataset = LeavesDataset(train_path, file_path, mode='valid')
test_dataset = LeavesDataset(test_path, file_path, mode='test')
2.模型构建Resnet
class Residual(nn.Module):
    def __init__(self, input_channels, num_channels, use_1x1conv=False, strides=1):
        super().__init__()
        self.conv1 = nn.Conv2d(input_channels, num_channels, kernel_size=3, padding=1, stride=strides)
        self.conv2 = nn.Conv2d(num_channels, num_channels, kernel_size=3, padding=1)
        if use_1x1conv:
            self.conv3 = nn.Conv2d(input_channels, num_channels, kernel_size=1, stride=strides)
        else:
            self.conv3 = None
        self.bn1 = nn.BatchNorm2d(num_channels)
        self.bn2 = nn.BatchNorm2d(num_channels)
    
    def forward(self, X):
        Y = F.relu(self.bn1(self.conv1(X)))
        Y = self.bn2(self.conv2(Y))
        if self.conv3:
            X = self.conv3(X)
        Y += X

        return F.relu(Y)

b1 = nn.Sequential(
    nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),
    nn.BatchNorm2d(64),
    nn.ReLU(),
    nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)

def resnet_block(input_channels, num_channels, num_residuals, first_block=False):
    blk = []
    for i in range(num_residuals):
        if i == 0 and not first_block:
            blk.append(Residual(input_channels, num_channels, use_1x1conv=True, strides=2))
        else:
            blk.append(Residual(num_channels, num_channels))
    return blk

#ResNet34
# b2 = nn.Sequential(*resnet_block(64, 64, 3, first_block=True))
# b3 = nn.Sequential(*resnet_block(64, 128, 4))
# b4 = nn.Sequential(*resnet_block(128, 256, 6))
# b5 = nn.Sequential(*resnet_block(256, 512, 3))

#ResNet18
b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
b3 = nn.Sequential(*resnet_block(64, 128, 2))
b4 = nn.Sequential(*resnet_block(128, 256, 2))
b5 = nn.Sequential(*resnet_block(256, 512, 2))

net = nn.Sequential(
    b1, 
    b2, 
    b3, 
    b4, 
    b5,
    nn.AdaptiveAvgPool2d((1, 1)),
    nn.Flatten(),
    nn.Linear(512, labels_num)
)
3.模型训练
def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):
    """
    用GPU训练模型
   	参数:
        net (torch.nn.Module): 要训练的神经网络模型
        train_iter (torch.utils.data.DataLoader): 训练数据加载器
        test_iter (torch.utils.data.DataLoader): 测试数据加载器
        num_epochs (int): 训练的轮数
        lr (float): 学习率
        device (torch.device): 计算设备(CPU或GPU)
    """
    # 初始化模型权重
    def init_weights(m):
        if(type(m) == nn.Linear or type(m) == nn.Conv2d):
            nn.init.xavier_uniform_(m.weight)
    net.apply(init_weights)
    print('training on', device)
    net.to(device)
    
    # 应用初始化权重函数
    # optimizer = torch.optim.SGD(net.parameters(), lr = lr)
    optimizer = torch.optim.Adam(net.parameters(), lr=lr, weight_decay = 0.001)
    # 每5个epoch学习率减少到原来的0.1倍
    # scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)  
    loss = nn.CrossEntropyLoss()   # 损失函数,使用交叉熵损失
    animator = lp.Animator(xlabel='epoch', xlim=[1, num_epochs],
                            legend=['train loss', 'train acc', 'test acc'])
    timer, num_batches = lp.Timer(), len(train_iter)

    for epoch in range(num_epochs):
        # 训练损失之和,训练准确率之和,样本数
        metric = lp.Accumulator(3)
        net.train() #训练模式
        for i, (X, y) in enumerate(train_iter):
            timer.start()
            optimizer.zero_grad() # 梯度清零
            X, y = X.to(device), y.to(device)
            y_hat = net(X)# 前向传播
            l = loss(y_hat, y) # 计算损失
            l.backward()# 反向传播
            optimizer.step() # 更新参数
            with torch.no_grad():
                metric.add(l * X.shape[0], lp.accuracy(y_hat, y), X.shape[0]) # 更新指标
            timer.stop()
            train_l = metric[0] / metric[2] # 计算训练损失
            train_acc = metric[1] / metric[2] # 计算训练准确率
            # 每训练完一个批次或每5个批次更新动画
            if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
                animator.add(epoch + (i + 1) / num_batches,
                             (train_l, train_acc, None))
        # 在验证集上计算准确率
        test_acc = lp.evaluate_accuracy_gpu(net, test_iter, device)
        animator.add(epoch + 1, (None, None, test_acc))
        # 打印当前epoch的训练损失,训练准确率和测试准确率
        print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, '
              f'test acc {test_acc:.3f}')
        # scheduler.step()
    animator.show()
    print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, '
          f'test acc {test_acc:.3f}')
    print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec '
          f'on {str(device)}')# 打印每秒处理的样本数

# 超参数设置
lr, num_epochs, batch_size = 1e-5, 120, 128

# 数据加载器
train_iter = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=4)
valid_iter = DataLoader(valid_dataset, batch_size=batch_size, shuffle=False, num_workers=4)

train_ch6(net, train_iter, valid_iter, num_epochs, lr, lp.try_gpu())
plt.show()

# 保存模型参数
file_path_module = '../limuPytorch/module/'
torch.save(net.state_dict(), file_path_module + 'classify_leaves.params')
4.训练调参
resNet-18,num_epochs = 10,lr=1e-4,
loss 2.239, train acc 0.429, test acc 0.149
444.5 examples/sec on cuda:0

resNet-34, num_epochs = 10,lr=1e-4
loss 1.991, train acc 0.443, test acc 0.147
270.7 examples/sec on cuda:0

resNet-34,num_epochs = 50,lr=1e-4,train数据增强,使用Adam
loss 0.281, train acc 0.914, test acc 0.378
244.6 examples/sec on cuda:0

resNet-34,num_epochs = 50,lr=1e-5,train数据增强,使用Adam
loss 0.189, train acc 0.925, test acc 0.398
258.0 examples/sec on cuda:0

resNet-18,num_epochs = 50,lr=1e-4,train数据增强,使用Adam
loss 0.199, train acc 0.955, test acc 0.338
458.0 examples/sec on cuda:0

resNet-18,num_epochs = 50,lr=1e-4,train数据增强,调整数据集比例82
数据增强过度导致测试准确率(test accuracy)曲线上下震荡

resNet-18,num_epochs = 50,lr=1e-4,train数据增强,调整数据集比例为82
数据增强过度导致测试准确率(test accuracy)曲线上下震荡

resNet-18,num_epochs = 50,lr=1e-4,train数据增强(仅旋转),调整数据集比例为82
loss 0.129, train acc 0.966, test acc 0.838
350.7 examples/sec on cuda:0

resNet-18,num_epochs = 50,lr=1e-5,train数据增强,调整数据集比例为82
loss 0.808, train acc 0.788, test acc 0.701
420.6 examples/sec on cuda:0

resNet-18,num_epochs = 100,lr=1e-5,train数据增强,调整数据集比例为82
loss 0.285, train acc 0.927, test acc 0.825
409.2 examples/sec on cuda:0
5.模型预测
 def predict(model, data_loader, device):
    """
    使用模型进行预测
    参数:
        model (torch.nn.Module): 要进行预测的模型
        data_loader (torch.utils.data.DataLoader): 数据加载器,用于提供待预测的数据
        device (torch.device): 计算设备(CPU或GPU)

    返回: all_preds (list): 包含所有预测结果的列表
    """
    all_preds = []  # 存储所有预测结果
    model.to(device)  # 将模型移动到指定设备
    model.eval()  # 设置模型为评估模式
    with torch.no_grad():  # 在不需要计算梯度的上下文中进行
        for X in data_loader:  # 遍历数据加载器
            X = X.to(device)  # 将数据移动到指定设备
            outputs = model(X)  # 前向传播,计算模型输出
            _, preds = torch.max(outputs, 1)  # 获取预测结果
            all_preds.extend(preds.cpu().numpy())  # 将预测结果添加到列表中
    return all_preds  # 返回所有预测结果
  
 # 克隆模型
 clone_net = net
 # 加载预训练模型参数
 clone_net.load_state_dict(torch.load(file_path_module + 'classify_leaves.params'))
 # 创建验证集的数据加载器
 valid_iter = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, num_workers=2)
 # 进行预测
 predictions = predict(clone_net, valid_iter, lp.try_gpu())
 # 将预测结果映射到标签
 for i in predictions:
     predictions.append(num_to_labels[int(i)])
 # 读取测试数据
 test_data = pd.read_csv(test_path)
 # 将预测结果添加到测试数据中
 test_data['label'] = pd.Series(predictions)
 # 创建提交文件
 submission = pd.concat([test_data['image'], test_data['label']], axis=2)
 # 保存提交文件
 submission.to_csv(file_path + 'submission.csv', index=False)
7.扩展学习
# 模型构建
# 加载预训练的ResNet-18模型
#加载一个预训练的ResNet-18模型,这个模型已经在ImageNet数据集上进行了预训练。
#可以利用其提取特征的能力。
pretrained_net = models.resnet18(pretrained=True)

# 克隆预训练的ResNet-18模型,用于分类叶子数据集
classify_leaves_net = pretrained_net

# 修改最后的全连接层,将其输出特征数改为176(有176个类别)
# classify_leaves_net.fc.in_features 获取原始全连接层的输入特征数。
classify_leaves_net.fc = nn.Linear(classify_leaves_net.fc.in_features, 176)

# 使用Xavier均匀分布初始化新的全连接层的权重
nn.init.xavier_uniform_(classify_leaves_net.fc.weight)

# 模型训练部分更改优化器
def train_ch6(net, train_iter, test_iter, num_epochs, lr, device,param_group=True):
    """
    param_group (bool, optional): 是否对参数进行分组设置不同的学习率。默认值为True
    """
    # optimizer = torch.optim.SGD(net.parameters(), lr = lr)
    # optimizer = torch.optim.Adam(net.parameters(), lr=lr, weight_decay = 0.001)
 	if param_group:
        # 如果参数分组设置为True,分离出最后一层全连接层的参数
        # 列表params_1x,包含除最后一层全连接层外的所有参数。
        params_1x = [param for name, param in net.named_parameters()
                     if name not in ["fc.weight", "fc.bias"]]
        optimizer = torch.optim.Adam([
            {'params': params_1x},  # 其他层的参数使用默认学习率
            {'params': net.fc.parameters(), 'lr': lr * 10}  # 全连接层的参数使用更高的学习率
        ], lr=lr, weight_decay=0.001)
   	else:
        # 如果参数分组设置为False,所有参数使用相同的学习率
        optimizer = torch.optim.Adam(net.parameters(), lr=lr, weight_decay=0.001)

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

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

相关文章

某同盾验证码

⚠️前言⚠️ 本文仅用于学术交流。 学习探讨逆向知识,欢迎私信共享学习心得。 如有侵权,联系博主删除。 请勿商用,否则后果自负。 网址 aHR0cHM6Ly9zZWMueGlhb2R1bi5jb20vb25saW5lRXhwZXJpZW5jZS9zbGlkaW5nUHV6emxl 1. 先整体分析一下接…

计算机组成原理 | 数据的表示、运算和校验(4)基本运算方法

补码加减(运算与控制) (-Y)补 [Y补]变补,这个要好好理解 (-Y)补:先将Y的符号位置反,在求-Y的补码(数字为变反加1) [Y补]变补:先求Y的补码(数字为变反加1)&…

shell的正则表达------awk

一、awk:按行取列 1.awk原理:根据指令信息,逐行的读取文本内容,然后按照条件进行格式化输出。 2.awk默认分隔符:空格、tab键,把多个空格自动压缩成一个。 3.awk的选项: awk ‘操作符 {动作}’…

微服务、多租户、单点登录、国产化形成的开源Java框架!

一、项目简介 JVS是软开企服构建的一站式数字化的开源框架,支持对接多种账户体系,支持多租户、支持Auth2、统一登录、单点登录等,支持原生开发、低代码/零代码开发应用。 二、框架核心功能 控制台(首页):采用配置化的方式 用户…

Redis数据库(一):Redis数据库介绍与安装

Redis是一种高性能的开源内存数据库,支持多种数据结构(如字符串、列表、集合等),具有快速的读写速度。它提供持久化、主从复制、高可用性和分布式部署等功能,适用于缓存、实时分析、消息队列等应用场景。Redis使用简单…

A股羊群效应CSSD CSAD数据与Stata代码数据(2000-2023)

数据来源 参考马丽老师(2016)的做法,股价数据来源于东方财富网,采用上证180指数及构成上证180指数样本股日收盘价数据作为样本。上证180指数自2002年7月1日起正式发布,其样本股是在所有 A 股股票中抽取最具市场代表性…

什么是绩效评价?绩效考核的五个标准包括哪些?

什么是绩效评价?绩效评价是指运用一定的评价方法、量化指标及评价标准,对中央部门为实现其职能所确定的绩效目标的实现程度,及为实现这一目标所安排预算的执行结果所进行的综合性评价。   绩效考核的五个标准有: 1、考核标准设置…

记一下 Stream 流操作

Java Stream流 创建流 Collection.stream() / Collection.parallelStream()&#xff1a;从集合生成流&#xff0c;后者为并行流。 List<String> list new ArrayList<>(); Stream<String> stream list.stream(); //获取一个顺序流 Stream<String> …

深度学习 --- stanford cs231学习笔记五(训练神经网络的几个重要组成部分之三,权重矩阵的初始化)

权重矩阵的初始化 3&#xff0c;权重矩阵的初始化 深度学习所学习的重点就是要根据损失函数训练权重矩阵中的系数。即便如此&#xff0c;权重函数也不能为空&#xff0c;总是需要初始化为某个值。 3&#xff0c;1 全都初始化为同一个常数可以吗&#xff1f; 首先要简单回顾一下…

【总线】AXI4第五课时:信号描述

大家好,欢迎来到今天的总线学习时间!如果你对电子设计、特别是FPGA和SoC设计感兴趣&#xff0c;那你绝对不能错过我们今天的主角——AXI4总线。作为ARM公司AMBA总线家族中的佼佼者&#xff0c;AXI4以其高性能和高度可扩展性&#xff0c;成为了现代电子系统中不可或缺的通信桥梁…

保姆级 | Windows 复古风格终端样式设置

0x00 前言 前段时间有朋友询问我 Windows 终端的样式是如何设置的&#xff0c;我也进行了一些简单的回复。在之前的 Windows 11 版本中&#xff0c;系统提供了一个界面按钮&#xff0c;可以直接将终端样式设置为复古风格。然而&#xff0c;系统更新之后&#xff0c;这个按钮好像…

【UML用户指南】-22-对高级行为建模-事件和信号

目录 1、概述 2、事件分类 2.1、信号 2.2、调用事件 2.3、时间事件和变化事件 2.4、发送和接收事件 3、常用建模技术 3.1、对信号族建模 3.1.1、建立过程 3.2、对异常建模 在状态机语境中&#xff0c;使用事件对能够触发状态转移的激励建模。事件包括信号、调用、时间…

go语言day03

目录 一、 go语言的数据类型&#xff1a; 二、声明赋值的简写形式&#xff1a; ":" 1&#xff09;重复使用的编译错误 2&#xff09;在全局变量中使用 : 会报编译错误 三、变量规则&#xff1a; 0&#xff09;变量的命名规则&#xff1a; 1&#xff09;创建的局部…

Excel 宏录制与VBA编程 —— 12、文本字符串类型相关(附示例)

字符串分割&#xff0c;文末示例&#xff08;文末代码3附有源码&#xff09; 代码1 - 基础字符串 代码2 - 字符串拆分 代码3 - 字符串分割 Option ExplicitSub WorkbooksClear()Dim DataRange As RangeSet DataRange Range("C2:E12")DataRange.Clear End SubSub Wo…

PS添加物体阴影

一、选择背景&#xff0c;确保物体和北京分割出图层 二、右键单击物体图层&#xff0c;点击混合选项&#xff0c;点击投影 三、调整参数&#xff0c;可以看效果决定(距离是高度&#xff0c;扩展是浓度&#xff0c;大小是模糊程度)&#xff0c;保存即可

PhotoShop自动生成号码牌文件

1、说明 设计卡牌的时候&#xff0c;遇到自动生成编号&#xff0c;从01500到-02500&#xff0c;一个一个的手写&#xff0c;在存储保存成psd格式的文件&#xff0c;会很耗时。 下面将介绍如何使用ps自动生成psd格式的文件 2、使用excle生成数字 从01500到-02500 第一步&…

数据挖掘常见算法(关联)

Apriori算法 Apriori算法基于频繁项集性质的先验知识&#xff0c;使用由下至上逐层搜索的迭代方法&#xff0c;即从频繁1项集开始&#xff0c;采用频繁k项集搜索频繁k1项集&#xff0c;直到不能找到包含更多项的频繁项集为止。 Apriori算法由以下步骤组成&#xff0c;其中的核…

【Python/Pytorch 】-- K-means聚类算法

文章目录 文章目录 00 写在前面01 基于Python版本的K-means代码02 X-means方法03 最小二乘法简单理解04 贝叶斯信息准则 00 写在前面 时间演变聚类算法&#xff1a;将时间演变聚类算法用在去噪上&#xff0c;基本思想是&#xff0c;具有相似信号演化的体素具有相似的模型参数…

连续9年618全渠道第一,入选最受用户关注TOP10,德施曼做对了什么?

近日&#xff0c;智能锁赛道领军品牌德施曼闯入大消费赛道&#xff0c;与宝洁、美的等品牌一起入选今年京东618最受用户关注TOP10品牌。 2024年的618大促已正式落下帷幕&#xff0c;大品牌纷纷交出了这场“年中大考”的答卷&#xff1b;其中高端智能锁领军品牌德施曼也交出了一…

一个故事,告诉你为什么要做目标管理

在一个小镇上&#xff0c;镇上的领导找到一群建筑工人&#xff0c;要求他们建造一堵墙&#xff0c;以保护镇上的居民免受外界侵扰。 然而&#xff0c;领导只是简单地描述了墙的作用和位置&#xff0c;却没有给出具体的尺寸和高度要求。工人们认为这是一个简单而直接的任务&am…