常见数据的维度与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论文的核心思想 一个单词表达一个意思, 选择最能表达整段文本意思的 相靠近的两个单词,转换为长度为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
原论文复现 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 |
|
|
|
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也做超参数优化,其精度还能再上一个台阶
酒店评论索引数据集下载