← 返回文章列表

图像特征提取,实现手机分类案例

手机价格分类 1.1需求分析 小明创办了一家手机公司,他不知道如何估算手机产品的价格。为了解决这个问题,他收集了多家公司的手机销售 …

手机价格分类

1.1需求分析

小明创办了一家手机公司,他不知道如何估算手机产品的价格。为了解决这个问题,他收集了多家公司的手机销售数据。该数据为二手手机的各个性能的数据,最后根据这些性能得到4个价格区间,作为这些二手手机售出的价格区间。主要包括:

我们需要帮助小明找出手机的功能(例如:RAM等)与其售价之间的某种关系。我们可以使用机器学习的方法来解决这个问题,也可以构建一个全连接的网络。

需要注意的是: 在这个问题中,我们不需要预测实际价格,而是一个价格范围,它的范围使用 0123 来表示,所以该问题也是一个分类问题。接下来我们还是按照四个步骤来完成这个任务:

**1)**准备训练集数据

**2)**构建要使用的模型

**3)**模型训练

**4)**模型预测评估

#导包
# 导入相关模块
import torch
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader
import torch.nn as nn
import torch.optim as optim
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import time
from torchsummary import summary
#todo:1.定义函数,构建数据集
def create_dataset():
    data = pd.read_csv('./data/手机价格预测.csv')
    # print(data.head())
    # print(data.shape)
    x,y = data.iloc[:,:-1],data.iloc[:,-1]
    # print(f'x:{x.head()},{x.shape}')
    # print(f'y:{y.head()},{y.shape}')

    x = x.astype(np.float32)
    # print(f'x:{x.head()},{x.shape}')

    x_train,x_test,y_train,y_test = train_test_split(x,y,test_size=0.2,random_state=3,stratify=y)

    train_dataset = TensorDataset(torch.tensor(x_train.values),torch.tensor(y_train.values))
    test_dataset = TensorDataset(torch.tensor(x_test.values),torch.tensor(y_test.values))

    return train_dataset,test_dataset,x_train.shape[1],len(np.unique(y))
#todo:2.搭建神经网络
class PhonePriceModel(nn.Module):
    def __init__(self,input_dim,output_dim):
        super().__init__()
        self.linear1 = nn.Linear(input_dim,128)

        self.linear2 = nn.Linear(128,256)

        self.output = nn.Linear(256,output_dim)
    def forward(self,x):
        x = torch.relu(self.linear1(x))

        x = torch.relu(self.linear2(x))
        #正常写法,但是没必要,后续的交叉熵损失函数 = softmax + 损失计算
       # x = torch.softmax(self.output(x),dim=1)
        x = self.output(x)
        return x
#todo:3.模型训练
def train(train_dataset,input_dim,output_dim):
    #1:创建数据集
    dataloader = DataLoader(train_dataset,batch_size=16,shuffle=True)
    #2.创建神经网络模型
    model = PhonePriceModel(input_dim,output_dim)
    #定义损失函数
    criterion = nn.CrossEntropyLoss()
    #定义优化器
    optimizer = optim.SGD(model.parameters(),lr=0.001)
    #模型训练
    #5.1定义变量,计算训练的总轮数
    epochs = 50
    #5.2开始训练
    for epoch in range(epochs):
        #5.2.1 定义变量,计记录每次训练的损失值,训练批次数
        total_loss,batch_num = 0.0,0
        #定义变量表示开始的时间
        start = time.time()
        #5.2.3开始本轮的 各个批次的训练
        for x,y in dataloader:
        #5.2.4切换模型(状态)
            model.train()
            #模型预测
            y_pred = model(x)
            #5.2.6计算损失
            loss = criterion(y_pred,y)
            #5.2.7梯度清零,反向传播,优化参数
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            total_loss+=loss.item()#把本轮的每批次的平均损失累加起来,第一批的平均损失+第二批的平均损失...
            batch_num+=1
        print(f'epoch:{epoch+1},loss:{total_loss/batch_num:.4f},time:{time.time()-start:.2f}')
    print(f'nn 模型的参数为:{model.state_dict()}nn')
    torch.save(model.state_dict(),'./model/phone.pth')


#todo:4.模型测试
def evaluate(test_dataset,input_dim,output_dim):
    #1:创建神经网络分类对象
    model = PhonePriceModel(input_dim,output_dim)
    #2.加载模型参数
    model.load_state_dict(torch.load('./model/phone.pth'))
    #3.创建测试集的数据加载器对象
    test_dataloader = DataLoader(test_dataset,batch_size=8,shuffle=False)
    #4.定义变量,预测正确的样本个数
    correct = 0
    #5.从数据加载器中,获取到每批次的数据
    for x,y in test_dataloader:
        #5.1切换模式
        model.eval()
        #5.2模型预测
        y_pred = model(x)
        # print(f'预测结果:{y_pred}')
        #5.3根据加权求和,得到类别,用argmax()获取最大值对应的下标
        y_pred=torch.argmax(y_pred,dim = 1)
        # print(f'预测结果:{y_pred}')
        #5.4统计
        correct+=(y == y_pred).sum()
    print(f'准确率(accuracy):{correct / len(test_dataset):.4f}')


#todo5:测试

if __name__ == '__main__':
    #1:准备数据集
    train_dataset,test_dataset,input_dim,output_dim = create_dataset()
    # print(f'训练集 数据集对象:{train_dataset}')
    # print(f'测试集 数据集对象:{test_dataset}')
    # print(f'输入标签数:{input_dim}')
    # print(f'输出标签数:{output_dim}')
    #2.构建神经网络
    # model = PhonePriceModel(input_dim,output_dim)
    # summary(model,input_size=(16,input_dim))
    #3.模型训练
    # train(train_dataset,input_dim,output_dim)
    evaluate(test_dataset,input_dim,output_dim)

注意:step2搭建神经网络的时候,没用softmax,因为crossEntryLoss = SoftMax()+损失计算,所以测试的时候用argmax()

1.2 调优

1.优化方法SGD—>Adam

2.学习率从0.001—>0.0001

3.对数据进行标准化

总结

卷积神经网络

图像基础知识

图像基本概念

图像是人类视觉的基础,是自然景物的客观反映,是人类认识世界和人类本身的重要源泉。“图”是物体反射或透射光的分布,“像“是人的视觉系统所接受的图在人脑中所形成的印象或认识照片、绘画、剪贴画、地图、书法作品、手写汉字、传真、卫星云图、影视画面、X光片、脑电图、心电图等都是图像。

在计算机中,按照颜色和灰度的多少可以将图像分为四种基本类型。

  • 二值图像

一幅二值图像的二维矩阵仅由0、1两个值构成,“0”代表黑色,“1”代白色。由于每一像素(矩阵中每一元素)取值仅有0、1两种可能,所以计算机中二值图像的数据类型通常为1个二进制位。二值图像通常用于文字、线条图的扫描识别(OCR)和掩膜图像的存储。

  • 灰度图像

灰度图像矩阵元素的取值范围通常为[0,255]。因此其数据类型一般为8位无符号整数的(int8),这就是人们经常提到的256灰度图像。**“0”表示纯黑色,“255”表示纯白色,中间的数字从小到大表示由黑到白的过渡色。**二值图像可以看成是灰度图像的一个特例。

  • 索引图像

索引图像的文件结构比较复杂,除了存放图像的二维矩阵外,还包括一个称之为颜色索引矩阵MAP的二维数组。MAP的大小由存放图像的矩阵元素值域决定,如矩阵元素值域为[0,255],则MAP矩阵的大小为256Ⅹ3,用MAP=[RGB]表示MAP中每一行的三个元素分别指定该行对应颜色的红、绿、蓝单色值,MAP中每一行对应图像矩阵像素的一个灰度值,如某一像素的灰度值为64,则该像素就与MAP中的第64行建立了映射关系,该像素在屏幕上的实际颜色由第64行的[RGB]组合决定。也就是说,图像在屏幕上显示时,每一像素的颜色由存放在矩阵中该像素的灰度值作为索引通过检索颜色索引矩阵MAP得到。

  • 真彩色RGB图像

RGB图像与索引图像一样都可以用来表示彩色图像。与索引图像一样,它分别用红(R)、绿(G)、蓝(B)三原色的组合来表示每个像素的颜色。但与索引图像不同的是,RGB图像每一个像素的颜色值(由RGB三原色表示)直接存放在图像矩阵中,由于每一像素的颜色需由R、G、B三个分量来表示,**M、N分别表示图像的行列数,三个M x N的二维矩阵分别表示各个像素的R、G、B三个颜色分量。**RGB图像的数据类型一般为8位无符号整形。注意:通道的顺序是 BGR 而不是 RGB。

CNN网络主要由三部分构成:卷积层、池化层和全连接层构成:

1.卷积层负责提取图像中的局部特征;

2.池化层用来大幅降低参数量级(降维);

3.全连接层用来输出想要的结果。

卷积层

卷积层的主要作用如下:

  • 特征提取:卷积层的主要作用是从输入图像中提取低级特征(如边缘、角点、纹理等)。通过多个卷积层的堆叠,网络能够逐渐从低级特征到高级特征(如物体的形状、区域等)进行学习。
  • 权重共享:在卷积层中,同一个卷积核在整个输入图像上共享权重,这使得卷积层的参数数量大大减少,减少了计算量并提高了训练效率。
  • 局部连接:卷积层中的每个神经元仅与输入图像的一个小局部区域相连,这称为局部感受野,这种局部连接方式更符合图像的空间结构,有助于捕捉图像中的局部特征。
  • 空间不变性:由于卷积操作是局部的并且采用权重共享,卷积层在处理图像时具有平移不变性。也就是说,不论物体出现在图像的哪个位置,卷积层都能有效地检测到这些物体的特征。

多通道卷积计算-先对应通道做点乘,然后做加法,最后整体做加法

实际中的图像都是多个通道组成的,我们怎么计算卷积呢?

计算方法如下:

  1. 当输入有多个通道(Channel), 例如 RGB 三个通道, 此时要求卷积核需要拥有相同的通道数(图像有多少通道,每个卷积核就有多少通道).
  2. 每个卷积核通道与对应的输入图像的各个通道进行卷积.
  3. 将每个通道的卷积结果按位相加得到最终的特征图.

如下图所示:

多卷积核卷积计算

上面的例子里我们只使用一个卷积核进行特征提取, 实际对图像进行特征提取时, 我们需要使用多个卷积核进行特征提取. 这个多个卷积核可以理解为从不同到的视角、不同的角度对图像特征进行提取.

那么, 当使用多个卷积核时, 应该怎么进行特征提取呢?

通过以下例子查看多卷积核卷积计算流程:

可以看到:

  • 两个神经元,意味着有两个滤波器
  • 数据窗口每次移动两个步长取3*3的局部数据,即stride=2
  • zero-padding=1。输入数据由5*5*3变为7*7*3
  • 左边是输入(7*7*3中,7*7代表图像的像素/长宽,3代表R、G、B 三个颜色通道)
  • 中间部分是两个不同的滤波器Filter w0、Filter w1
  • 最右边则是两个不同的输出

特征图大小

输出特征图的大小与以下参数息息相关:

  1. size: 卷积核/过滤器大小,一般会选择为奇数,比如有 1*1, 3*35*5
  2. Padding: 零填充的方式
  3. Stride: 步长

那计算方法如下图所示:

  1. 输入图像大小: W x W
  2. 卷积核大小: F x F
  3. Stride: S
  4. Padding: P
  5. 输出图像大小: N x N

如果原图不一致,就分别代进去算,得出x行y列

卷积层API介绍-一个卷积核 = 一个神经元

import torch
import torch.nn as nn
import matplotlib.pyplot as plt

def dm01():
    #1.加载RGB真彩图
    img = plt.imread('./data/img.jpg')
    #2.打印图片的信息
    # print(f'img:{img},shape:{img.shape}')
    #3.把图像的形状从HWC改为CHW
    img2 = torch.tensor(img,dtype=torch.float)
    img2 = img2.permute(2,0,1)
    #4.因为只有一张图,所以再增加一个维度,从CHW改为(1,C,H,W)意思是1张3通道的640*640的图
    img3 =img2.unsqueeze(dim=0)
    print(f'img3:{img3},shape:{img3.shape}')
    #5.创建卷积层对象,提取特征
    #参1:输入图像的通道数 参2:输出图像的通道数(几个卷积核)
    conv = nn.Conv2d(3,4,3,2,0)
    #6.具体的卷积计算
    conv_img = conv(img3)
    #7.打印
    # print(f'conv_img:{conv_img},shape:{conv_img.shape}')
    #8.查看提取到的特征图
    img4 =conv_img[0]
    # print(f'img4:{img4},shape:{img4.shape}')#Size([4, 319, 319])
    #9.将CHW->HWC
    img5 = img4.permute(1,2,0)
    # print(f'img5:{img5},shape:{img5.shape}')
    #10.可视化
    feature1 = img5[:,:,3].detach().numpy()
    plt.imshow(feature1)
    plt.show()











if __name__ == '__main__':
   dm01()

抽取的图片特征

池化层

池化层 (Pooling) 降低维度 从而减少计算量、减少内存消耗,并提高模型的鲁棒性。

池化层通常位于卷积层之后,它通过对卷积层输出的特征图进行下采样,保留最重要的特征信息,同时丢弃一些不重要的细节。

多通道池化计算

在处理多通道输入数据时,池化层对每个输入通道分别池化,而不是像卷积层那样将各个通道的输入相加。这意味着池化层的输出和输入的通道数是相等