从数据到标签概述 从数据到标签,可以一次到位,也可以嵌套,并且可以无限嵌套 嵌套与否在数学上 与不嵌套完全一致,在实际应用中,可以舍弃一部分,让一部分特征在变换中消失 固定的是数据与标签,变化是参数 - 从数据到标签的路径有N条 - 目的并不在于哪一条,而是可用的任一条 - 是大数定律与个体随机的综合体现, - 只要找到其中一条满足业务条件的路径即可 - 局部的随机性是允许的,并且还可以体现其健壮性 全局视角 标签是目标,数据是起点,这中间有N条路可达,但 你 是不知道的 训练参数就是以暴力破解的方式,找到一条 ... 嗯 ... 最好是最短的那条路 每条路上有M个节点,每次线性变换 或 添加激活函数或某个组件 都可看作添加一个节点 , 但你不知道很客观真理那一方有哪些节点, 看看自己知道哪些 根据行业经验和自己的知识储备,开始试吧 ... 这张由节点与路径构成的图,就是全局视觉,但在我们眼里,那是啥也看不见... 只能慢慢搜索... 我们就是盲人摸象中的盲人,我们去摸索了,比信口开河强多了... 去模拟,去近似, 去探索... |
数据决定上限 模型从历史数据中学习规律,数据有大量的噪声,干扰信息, 此时模型可能无论怎么训练都精度不高 ,或者说达不到要求 ------------------------------------------------------------------------ |
绝对优势 从数据变换到标签, 对于每个样本,都配有一组参数,参数变换的目标在于 - 让得到的结果投放到一个标签中,即最接近其中的某个标签,可以将之想象成一个篮子,一个结果只能放一个框中 - 并且是仅有的一个标签中 - 要求是 多选一 - 得到的是一个概率,选概率最大的那个 - 每个结果都有一个概率 - 每个结果都是一个长度/维数为标签个数n的向量 - 这个n维向量做softmax,值最大的元素位置对应的索引所对应的标签 - 元素 -- 概率 -- 最大概率 -- 元素索引 -- 业务标签 - 最好的情况是,其中一个90%,剩下之和是10% - 这就是绝对优势 但实际情况往往不是这样,可能是这样的 - 其中一个是20%, - 剩下有一个0,其余是10% - 这也算绝对优势 - 即被选择的标签相对其他标签有明显的差异 若是下面的情况 - 其中两个是51%,48%,其余所有之和为1% - 这就导致没有任何一个结果具有 绝对优势 - 算法取最大值51%, - 但要知道差异的2%相对51%,48%来说,并没有什么压倒性优势 - 并且算法每次都有波动,可能下次就是49%,49% - 能出现这个结果的,通常是数据的原因 - 即数据属于这两个类型的哪个都可以 - 数据本身对于这两个类型来说,是模糊的,不清晰的,不容易区分的 - 比如人脸识别遇到了某些双胞胎,太像了,或者说,这世界上本身就存在某个与你高度相似的事物 - 这时通常有以下几种做法 - 进一步细分,添加一些变量,这些新增变量要对区分这两个类型有帮助 - 增加模型复杂度,添加更多的参数,再次训练 - 网上找找其他清洗数据的方法,或者请教一下他人,就是向外求助 - 接受这个现实,但给个提示,说也可能是另外一个类型, - 就是在这里标注一下,告诉你这个结果,让人心里有个准备,多个选择 - 也是放一放的意思,或者后续就有解决办法了,至少现在点出了可优化的一点就在这里 |
|
|
线性变换 产生的是值是一个连续的值 对于2分类问题,比如0,1二分类 大于0.5转换为1 小于0.5转换为0 这样就将连续问题,转换为 二分类问题 多分类问题 将模型的维度变换到1维上,经过上面的转换就是2分类 将模型的维度变换到n维上,经过softmax变换转换为n维上的概率后,就是多分类问题 自然概率 对于2分类问题,自然概率,即随机起步的概率,也不会太低... 经过训练的模型输出,要高于自然概率 |
MSE是针对的是连续型数据,线性Model到损失的计算依然是连续型问题 只是在数据输出之后,后处理上,转为分类 因此整个前身传播及反导求导,都是连续型数据的计算 所以损失函数“可以”使用MSE import numpy as np from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split import torch from torch import nn from torch.nn import functional as F from torch.utils.data import Dataset from torch.utils.data import DataLoader # 加载数据集 X, y = load_breast_cancer(return_X_y=True) # 切分数据集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1) # 标签改为列向量 y_train = y_train.reshape((-1, 1)) y_test = y_test.reshape((-1, 1)) # 数据预处理 mean_ = X_train.mean(axis=0) std_ = X_train.std(axis=0) X_train = (X_train - mean_) / std_ X_test = (X_test - mean_) / std_ class MyDataset(Dataset): def __init__(self, X, y): self.X = X self.y = y def __getitem__(self, idx): x = self.X[idx] y = self.y[idx] return torch.tensor(data=x).float(), torch.tensor(data=y).float() def __len__(self): return len(self.X) # 批量化打包数据 train_dataset = MyDataset(X=X_train, y=y_train) train_dataloader = DataLoader(dataset=train_dataset,batch_size=32 ,shuffle=True) test_dataset = MyDataset(X=X_test, y=y_test) test_dataloader = DataLoader(dataset=test_dataset,batch_size=32 ,shuffle=False) # 定义模型 class Model(nn.Module): def __init__(self, in_features=30, out_features=1): super(Model, self).__init__() self.linear1 = nn.Linear(in_features=in_features, out_features=out_features) def forward(self, x): out = self.linear1(x) return out # 生成模型对象 model = Model(in_features=30, out_features=1) # 损失函数 loss_fn = nn.MSELoss() # 优化器 optimizer = torch.optim.SGD(params=model.parameters(), lr=1e-2) # 过程监控 def get_loss(dataloader, model=model, loss_fn=loss_fn): losses = [] with torch.no_grad(): for X, y in dataloader: y_pred = model(X) loss = loss_fn(y_pred, y) losses.append(loss.item()) return np.array(losses).mean() # 计算准确率 def get_acc(dataloader=test_dataloader, model=model): accs = [] with torch.no_grad(): for X, y in dataloader: # 原始输出 y_pred = model(X) # 类别转换 y_pred = (y_pred >= 0.5).float() # 准确率的计算 acc = (y == y_pred).float().mean() accs.append(acc.item()) return round(np.array(accs).mean(), ndigits=3) # 训练函数 def train(dataloader=train_dataloader, model=model, loss_fn=loss_fn, optimizer=optimizer, epochs=100): for epoch in range(1, epochs+1): for X, y in dataloader: # 正向传播 y_pred = model(X) # 计算损失 loss = loss_fn(y_pred, y) # 梯度清空 optimizer.zero_grad() # 反向传播 loss.backward() # 更新参数 optimizer.step() print(f"Epoch: {epoch}, Train Acc: {get_acc(dataloader=train_dataloader)}, Test Acc: {get_acc(dataloader=test_dataloader)}") train() |
|
|
|
回归问题 模型输出是数值 分类问题 模型输出是类别 类别与类别之间应该地位相当, 如果使用 index 编码,即0,1,2,...,n-1区分n个类别,那么类别与类别之间的差异过大 这里使用one hot编码 去标识一个类别 - 每个One Hot向量的长度皆为1,对应类别与类别之间地位相同 - 每个类别对应One Hot向量的1个维度,相同之间又有区别 - 类别所在维度为1,其他维度为0,这又与概率对应上了 实际上就是用一个向量去代表了标签,这个向量有以下特点 - 长度皆为1 - 所在类别为1,其他维度为0 - 向量的维数就是类别的个数 这意味着模型输出的维度就是类别的个数 接下来就是设计损失函数,计算模型输出的向量与表示类别的one hot向量之间的差异 代码示例 import torch torch.eye(2) tensor([[1., 0.], [0., 1.]]) def one_hot_coding(class_num,index): E = torch.eye(class_num) return E[index] one_hot_coding(class_num=2,index=0) tensor([1., 0.]) |
模型输出本是连续的数值,是后续处理将之转化为了概率 严格来说,转化概率这一步,并不归模型管, 模型负责线性维度变换+激活函数 转概率这一步,完全可以在模型之外做 向量转概率 模型输出转概率,通常用softmax import torch p = torch.tensor([0.9,0.1]) torch.exp(p)/torch.exp(p).sum() + 1e-6 tensor([0.6900, 0.3100]) 对于[0.9,0.1],softmax明显不合理的地方 - 自然的思路,期望转化为概率后应该是90%,10% - 而softmax转化为的概率为[0.6900, 0.3100] - 这合理吗? 这是因为没考虑负数... softmax实际上是将数据拉到指数e的层面做了概率, 不管正负,经过指数运算后,皆为正,这符合了概率皆大于0的特点 其他概率 def v2p(p): p_min = p.min() p_max = p.max() p = (p - p_min+1e-6)/(p_max-p_min+1e-6) return p p = v2p(p) p tensor([1.0000e+00, 1.2500e-06]) 通常的就是sotmax,这是个人感觉也还可以的概率计算方式 - 但要注意这并不是严格意义上的概率 - 和并不为1 - 但模型通常要的是那个最大概率的值,其他的不为1好像也没关系 - 具体如何还需要验证... |
交叉熵可以求两个向量之前的差异,它来自于KL离散的概念 简单过一下概率的概念 - 这里标签已全部转化为概率 - 模型输出本是随机的连续值,这里也已转化为概率 - 两个都已是概率,那么就可以套KL离散的概念了 标签 y=[1,0] 模型 p=[0.9,0.1] 求这两个向量之间的差异/损失,实际上分两步 - 求向量每个维度上两个元素之间的差异 - 求和,各个维度上差异的和才是两个向量,也叫两个分布之间的损失 ![]() 交叉熵公式 = -sum(y*log(x)) - y已转化为one hot向量 - x已转化为概率 -torch.log(p[0]) tensor(0.1054) 在标签已经化为One Hot向量的前提下 - 损失函数可以简化为 -torch.log(x_max) - 即只计算 one hot向量1所在索引位置上样本x对应值的 log 即可 |
|
|
使用one hot标签 标识类别,那么模型就设计为将样本的维度变换到one hot向量的维度 损失函数 计算两个向量之间的差异 优化器使用梯度下降法 更新参数 这么强调,意在说明,如果标签不是one hot向量,比如其他维度都也有数据, 那么该流程依然适用, 因为标签向量如何映射回标签这个问题,并不归模型所管 只要在模型之外定义一个字典, 然后计算模型输出的结果向量离字典中哪个向量的距离最近,就能找到是哪个标签 |
import numpy as np from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split import torch from torch import nn from torch.nn import functional as F from torch.utils.data import Dataset from torch.utils.data import DataLoader # 加载数据集 X, y = load_breast_cancer(return_X_y=True) # 切分数据集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1) print(X.shape,y.shape) # (569, 30) (569,) # 数据预处理 mean_ = X_train.mean(axis=0) std_ = X_train.std(axis=0) X_train = (X_train - mean_) / std_ X_test = (X_test - mean_) / std_ class MyDataset(Dataset): def __init__(self, X, y): self.X = X self.y = y def __getitem__(self, idx): x = self.X[idx] y = self.y[idx] return torch.tensor(data=x).float(), torch.tensor(data=y).long() def __len__(self): return len(self.X) # 批量化打包数据 train_dataset = MyDataset(X=X_train, y=y_train) train_dataloader = DataLoader(dataset=train_dataset,batch_size=32 ,shuffle=True) test_dataset = MyDataset(X=X_test, y=y_test) test_dataloader = DataLoader(dataset=test_dataset,batch_size=32 ,shuffle=False) # 定义模型 class Model(nn.Module): def __init__(self, in_features=30, out_features=2): super(Model, self).__init__() self.linear1 = nn.Linear(in_features=in_features, out_features=256) self.linear2 = nn.Linear(in_features=256, out_features=out_features) def forward(self, x): h = self.linear1(x) h = torch.relu(h) out = self.linear2(h) return out # 构建模型 model = Model(in_features=30, out_features=2) # 损失函数 loss_fn = nn.CrossEntropyLoss() # 优化器 optimizer = torch.optim.Adam(params=model.parameters(), lr=1e-2) # 过程监控 def get_loss(dataloader, model=model, loss_fn=loss_fn): losses = [] with torch.no_grad(): for X, y in dataloader: y_pred = model(X) loss = loss_fn(y_pred, y) losses.append(loss.item()) return np.array(losses).mean() # 计算准确率 def get_acc(dataloader=test_dataloader, model=model): accs = [] with torch.no_grad(): for X, y in dataloader: # 原始输出,这是连续的数值,并没有转概率 y_pred = model(X) # 类别转换,求数值最大的索引位置,对应标签类别 y_pred = y_pred.argmax(dim=1) # 准确率的计算 acc = (y == y_pred).float().mean() accs.append(acc.item()) return round(np.array(accs).mean(), ndigits=3) # 训练函数 def train(dataloader=train_dataloader, model=model, loss_fn=loss_fn, optimizer=optimizer, epochs=100): for epoch in range(1, epochs+1): for X, y in dataloader: # 正向传播 y_pred = model(X) # 计算损失 loss = loss_fn(y_pred, y) # 梯度清空 optimizer.zero_grad() # 反向传播 loss.backward() # 更新参数 optimizer.step() print(f"Epoch: {epoch}, Train Acc: {get_acc(dataloader=train_dataloader)}, Test Acc: {get_acc(dataloader=test_dataloader)}") train() |
|
|
|