数据的维度

常见数据的维度与shape

 
图像
有高与宽,其数字存储的shape通常为[高,宽],
这个shape在CV中叫特征图,就是特征形成的图形/shape 
这个维度只是表示有多少个像素点,这些像素点形成了什么样的图形,
然而每个像素点还有三色,表示着这个像素点的特征,这个维度在CV中就叫特征,
特征组成事物的元素,比如
图像由什么组成?答,像素,那像素就是特征
句子由什么组成?答,单词,那单词就是特征
在torch构架中,特征维度的个数,也叫通道数,channel / C 
多张图像输入模型的shape为,[B,C,H,W]

文本 
一个又一个的句子,句子由单词组成,输入torch模型的shape通常为[B,C,L]
B是批次,C是单词的维度,L是一个句子中单词的个数 

 

    

 
在深度学习中,特征图(Feature Map)的概念通常与卷积神经网络(CNN)相关,
它指的是卷积层、池化层等处理后的输出。
在这个上下文中,特征图既不是指特征向量的维度,
也不是简单地指某个形状的二维数组(比如[H,W]),而是指经过特定层处理后的多维数据。

以RGB三色图像为例,在PyTorch框架中,一个图像张量的形状通常是 [B, C, H, W],其中:

B 是批次大小(Batch Size),即一次处理的图像数量。
C 是通道数(Channel),对于RGB图像来说,C=3,分别对应红色、绿色和蓝色通道。
H 是图像的高度(Height)。
W 是图像的宽度(Width)。

当这样的图像数据通过卷积层或池化层等处理时,
每个通道上的数据会被独立处理(或有时跨通道组合处理,如深度可分离卷积),
并生成新的特征图。
这些新的特征图通常具有不同的通道数(由卷积核的数量决定)和
可能不同的空间维度(高度和宽度,取决于步长、填充和池化等因素)。

因此,在卷积神经网络中,特征图通常指的是经过某层处理后输出的多维数据,
其形状一般为 [B, C', H', W'],
其中 C' 是新的通道数,H' 和 W' 是新的高度和宽度。
    

 
具体到你的问题,以 [B, 3, H, W] 的图像为例,
特征图既不是指 3(这是输入图像的通道数),也不是指 [H, W](这是输入图像的空间维度)。
特征图是指经过某个卷积层或池化层等处理后得到的输出张量,
其形状可能是 [B, C'', H'', W''],其中 C''、H'' 和 W'' 取决于该层的参数和输入数据的形状。

例如,如果有一个卷积层具有 16 个 3x3 的卷积核,
步长为 1,填充为 1,那么对于输入形状为 [B, 3, H, W] 的图像,
该卷积层的输出特征图形状将是 [B, 16, H, W]
(假设高度和宽度没有因为步长或填充而改变,这在实际中可能不成立,因为边界效应可能会导致尺寸变化)。
    
    

 


欺诈交易

 
一个交易是否为欺诈是基于历史交易的
将列的个数作为C 
取最近n条连续的交易记录,作为一个周期的数据 

最近一条记录为欺诈,则确定整个周期的标签为欺诈;否则为正常

这只是个人的一个猜测.... 实际上交易数据常用的转化方法为下面的方法
- L对应列,C=1 

 


 


 

  

 


多尺度

盲人摸象

 
我们没有一个全局的视角,可以看见事物的全貌
事物的本质一直在我们面前,我们由于种种原因,只能“看到”它的一面(并且大脑总是“骗”我们说这就是全部...)
就像盲人摸象一样,大象像什么? 盲人摸到什么就说像什么什么... 
小时候就知道...哈哈哈...

对于一个从来没有见到大象的人来说,把盲人各自摸到的结果拼接起来,
就是一个对大象近似的描述 
而这种从多个角度提取信息,然后拼接起来描述一个事物的方法,就是AI中的多尺度

卷积处理文本分类

pytorch卷积网络中的数据shape

 
尽管其他地方特征图理解为整个图像,这里特征图单指特征的shape
[B,特征的维度,特征shape]

就文本而言,单词就是特征,其shape就是序列长度,
于是,卷积处理中文本类批次矩阵shape为 [B,C,L]

对于图像,像素是特征,其shape就是高与宽,
于是,卷积处理中图像类批次矩阵shape为 [B,C,H,W]

卷积网络主要结构

 
卷积提取特征,全连接分类

卷积在特征维度进行变换,通常是先升后降,不改变特征的shape

特征shape由maxpool或者全连接变换,通常是一直下降,最后降到标签类别个数

算法思想与业务场景的匹配

 
卷积提取的特征是区域特征,
是提取整体特征中某个/几个指定的区域
至于这几个区域在整体特征shape中的哪个位置并不重要
比如是否在中间,两边,左上还是右下,没有关系
卷积将特征分层,每层参数抑制非指定区域,强化指定区域,
最后每一层剩下的就是指定的特征
其他特征在这个过程中被淡化了
所以所要找的特征在整体中哪个位置不重要,
与其他特征什么顺序也关系不大

此处的业务场景是判断一个评论是正面还是负面,
实际上就是看一看评论中是否有负面单词
有则负,无则正 
这就是一个区域依赖问题,
你要骂人,是上来就骂,还是先说赞美一下再骂,
在这个只有正负二类的场景里,就是负面评论
有为1,无为0,那么负样本应该标记为1,正样本标记为0

而根据大方向判断,文本是一个序列,使用循环神经网络处理也应该没问题

RNN具有长度依赖的问题,就是序列越长,丢失信息越多
相对而言,卷积没有这方面的问题
至于效果如何,还得试过才知道

文本向量化

单词转索引

 
按数据集的格式,读取出一个个文本,进行分词

获取文本中所有的单词,形成一个单词集合,然后编码

将文本对应的单词列表转化为索引列表

文本转矩阵

 
神经网络训练的时候是批次训练,其shape为[B,C,L] 
B,batch_size,就是批次,代表一次要让神经网络学习多少个样本
C,通道数,特征个数,这里指单词的维数,即用多少个数表示一个单词
L,特征shape,这里指单词个数,一段评论的单词个数 

这里有个工程化落地的问题:
矩阵中所有向量元素个数一致,不可能有的向量长而有的短
但实际的评论单词个数并不一致,我们要使之统一

常见做法:
取整个数据集的评论单词个数,然后取平均值,
多的舍弃,少的补0 

更精细的做法:
当句子长时,直接舍弃,有点一刀切的意思
此时保留关键单词,舍弃非关键单词更合适一些
但增加了处理的复杂度,什么才是业务的关键单词还需要额外地处理

折中方法:
句子的长度可以取比平均值长一些,拼成矩阵
在输入模型时,再将补的0去掉,
但这要求模型算法支持长度不一致的数据
torch中的RNN就支持这一点

卷积处理文本细节

 
[B,Cin,Lin]  --> [B,Cout,Lout]  --> [B,2,1]

先将特征个数通过线性变换扩大,
然后再通过损失函数逼近其收缩,
使之趋近于one hot向量这样 数据集中于某个区域/索引位置,其他位置趋于0的这样一个向量
这样卷积就完成分类工作了,
中间就是一系列的线性变换,
大概就是这个数应该映射到那个位置可以让分类更清晰一点,
这个网络结构是人在暴力设计尝试,
其中的参数是计算机在暴力尝试,从一组随机参数开始,
让损失函数变大的参数舍弃,记录让损失函数变小的参数

让特征个数变大之后,整个矩阵包含的信息量暴增,
然后就减少特征shape,这里就是去除一些序列中的单词
二分类是一个比较粗糙的分类,
在这里就是找到那个负面的单词,只要找到一个,就满足条件了
计算机开始不知道哪个单词重要,随机为每个单词初始化一个参数,
使用梯度下降法,每次微调一下这个参数,
也不知道多少次后,量变引发质量,关键单词的系数变大,其他单词的系数趋于0
这样一个参数网络就是我们需要的

最后序列长度就留下一个,
即一个样本得到一个这样的向量:
它类似于one hot向量,数据集中某几个,最好是一个位置
然后我们使用全连接再将这样一个向量映射为一个与标签one hot向量同shape的向量
在层的维度看过去是某个特征消失,留下了指定特征,
在向量维度看过去就是,某些索引位置上的数据趋于0,数据集中于特定的索引位置
这就是神经网络分类的数据流向及变化

TextCNN核心思想

 
这里说的是那篇TextCNN论文的核心思想

一个单词表达一个意思,
选择最能表达整段文本意思的 相靠近的两个单词,转换为长度为2的向量
选择最能表达整段文本意思的 相靠近的三个单词,转换为长度为2的向量
选择最能表达整段文本意思的 相靠近的四个单词,转换为长度为2的向量
kernel size选多少个单词是ngram的思想

将这三个向量拼接成起来,再全连接分类 

多尺度:从多个尺度中的每个尺度提取出一个信息出来,然后拼接分类

序列长度从L直接降为1
TextCNN比较大胆的是,它 使用池化 从卷积的结果中选了一个特征,只选了一个
在常见的神经网络中,[B,Cin,Lin]  --> [B,Cout,Lout]  --> [B,2,1]
这个特征shape,也就是L,是一直在减小,可以由maxpool来实现,
但这个减少不是一下子从一个数降到了1,而是经历多个网络层层下降,
但TextCNN就一步,直接将它从L变成了1 

TextCNN论文实现

原论文复现

 
import torch
from torch import nn
from torch.nn import functional as F

class TextCNN1(nn.Module):
    """1维卷积实现文本分类
        TextCNN论文实现
    """
    def __init__(self,num_embeddings, embedding_dim,padding_idx,seq_len):
        super().__init__()
        
        self.embed = nn.Embedding(num_embeddings=num_embeddings,
                                    embedding_dim=embedding_dim,
                                    padding_idx=padding_idx)
        
        # [N, C, seq_len] -- [N, C, seq_len-1],[N, C, seq_len-kernel_size+1]
        self.gram_2 = nn.Sequential(
            nn.Conv1d(in_channels=embedding_dim, out_channels=2, kernel_size=2),
            # 因为kernel为2,又没有补,所以卷积后序列长度减1
            nn.MaxPool1d(kernel_size=seq_len-1)  # [N, C, 1]
        )

        # [N, C, seq_len, 1] -- [N, C, seq_len-2, 1]
        self.gram_3 = nn.Sequential(
            nn.Conv1d(in_channels=embedding_dim, out_channels=2, kernel_size=3),
            # 因为kernel为3,又没有补,所以卷积后序列长度减2
            nn.MaxPool1d(kernel_size=seq_len-2) # [N, C, 1]
        )
        
        # [N, C, seq_len, 1] -- [N, C, seq_len-3, 1]
        self.gram_4 = nn.Sequential(
            nn.Conv1d(in_channels=embedding_dim, out_channels=2, kernel_size=4),
            nn.MaxPool1d(kernel_size=seq_len-3) # [N, C, 1]
        )

        self.dropout1 = nn.Dropout(p=0.2)
        self.fc1 = nn.Linear(in_features=2*3, out_features=2)
        
    
    def forward(self,X):
        # [B,seq_len,embedding_dim]
        x = self.embed(X)

        # [B, seq_len, embedding_dim] -- [B, embedding_dim, seq_len] 
        x = torch.permute(input=x, dims=(0, 2, 1))
        print(x.shape)  # torch.Size([128, 256, 87])

        x1 = self.gram_2(x)
        print(f"x1.shape={x1.shape}")  #torch.Size([128, 2, 1])
        x2 = self.gram_3(x)
        x3 = self.gram_4(x)

        x = torch.concat(tensors=(x1,x2,x3),dim=1)
        print(x.shape)  # torch.Size([128, 6, 1])
        x = torch.squeeze(x)

        x = self.dropout1(x)
        x = self.fc1(x)

        return x 

 

    

原论文优化

 
import torch
from torch import nn
from torch.nn import functional as F
class TextCNN3(nn.Module):
    """
        TextCNN优化,
    """
    def __init__(self,num_embeddings, embedding_dim,padding_idx,seq_len):
        super().__init__()
        
        self.embed = nn.Embedding(num_embeddings=num_embeddings,
                                    embedding_dim=embedding_dim,
                                    padding_idx=padding_idx)
        
        # [N, C, seq_len] -- [N, C, seq_len-1],[N, C, seq_len-kernel_size+1]
        self.gram_2 = nn.Sequential(
            nn.Conv1d(in_channels=embedding_dim, out_channels=256, kernel_size=2),
            nn.BatchNorm1d(num_features=256),
            nn.ReLU(),
            nn.MaxPool1d(kernel_size=seq_len-1)  # [N, C, 1]
        )

        # [N, C, seq_len, 1] -- [N, C, seq_len-2, 1]
        self.gram_3 = nn.Sequential(
            nn.Conv1d(in_channels=embedding_dim, out_channels=256, kernel_size=3),
            nn.BatchNorm1d(num_features=256),
            nn.ReLU(),
            nn.MaxPool1d(kernel_size=seq_len-2) # [N, C, 1]
        )
        # [N, C, seq_len, 1] -- [N, C, seq_len-3, 1]
        self.gram_4 = nn.Sequential(
            nn.Conv1d(in_channels=embedding_dim, out_channels=256, kernel_size=4),
            nn.BatchNorm1d(num_features=256),
            nn.ReLU(),
            nn.MaxPool1d(kernel_size=seq_len-3) # [N, C, 1]
        )

        self.dropout1 = nn.Dropout(p=0.2)
        self.fc1 = nn.Linear(in_features=256*3, out_features=2)
        

    def forward(self,X):
        # [B,seq_len,embedding_dim]
        x = self.embed(X)
        # [B, seq_len, embedding_dim] -- [B, embedding_dim, seq_len] 
        x = torch.permute(input=x, dims=(0, 2, 1))
        # print(x.shape)  # torch.Size([128, 256, 87])

        x1 = self.gram_2(x)
        # print(f"x1.shape={x1.shape}")  #torch.Size([128, 256, 1])
        x2 = self.gram_3(x)
        x3 = self.gram_4(x)

        x = torch.concat(tensors=(x1,x2,x3),dim=1)
        # print(x.shape)  # torch.Size([128, 768, 1])
        x = torch.squeeze(x)

        x = self.dropout1(x)
        x = self.fc1(x)

        return x 

 

    

 


 

  

 


LogisticRegression

 
from ai.datasets import load_hotel 
X_train,y_train,X_test,y_test = load_hotel(return_dict=False, return_Xy=True)
X_train: (4800, 85)
y_train: (4800,)
X_test: (1200, 85)
y_test: (1200,)

from sklearn.linear_model import LogisticRegression
model = LogisticRegression(max_iter=10000)
model.fit(X=X_train,y=y_train)
model.score(X=X_test,y=y_test)
0.6008333333333333

y_pred = model.predict(X=X_test)
y_pred[:10]
array([0, 0, 1, 1, 0, 1, 1, 0, 0, 0])

各模型精度

 
针对本数据集(AI概述-数据集-酒店评论部分或文章最后的参考可下载),在未优化的前提下,
TextCNN 未优化时 精度85%
TextCNN 优化后 精度91%
LogisticRegression  未优化 精度60% 

备注:
如果对数据做进一步优化处理,LogisticRegression也做超参数优化,其精度还能再上一个台阶

参考
    酒店评论索引数据集下载