|
```
import torch
from torch import nn
import torch.nn.functional as F
from sklearn.datasets import make_regression
X,y = make_regression(n_samples=10000,n_features=20,noise=0.01,random_state=73)
X = torch.tensor(X).float()
y = torch.tensor(y).reshape(-1,1).float()
y.shape #torch.Size([10000, 1])
```
```
class Modle(nn.Module):
def __init__(self,in_feature_dim,out_feature_dim):
super().__init__()
self.linear = nn.Linear(in_features=in_feature_dim,out_features=out_feature_dim)
def forward(self,X):
x = self.linear(X)
return x
model = Modle(in_feature_dim=20,out_feature_dim=1)
model(X).shape #torch.Size([10000, 1])
```
```
def predict(X, model=model, threshold=0.5):
model.eval()
with torch.no_grad():
# 模型输出
y_out = model(X)
# 类别转换
y_pred = (y_out >= threshold).float()
return y_pred
```
```
y_pred = predict(X,model)
print(y_pred.shape) #torch.Size([10000, 1])
y_pred[:3]
tensor([[0.],
[0.],
[1.]])
```
|
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()
def predict(X, model=model, threshold=0.5):
if not isinstance(X_test,torch.Tensor):
x = torch.tensor(X_test).float()
model.eval()
with torch.no_grad():
# 模型输出
y_out = model(x)
# 类别转换
y_pred = (y_out >= threshold).float()
return y_pred
y_pred = predict(X_test,model)
print(y_pred.shape) #torch.Size([114, 2])
y_pred[:3]
tensor([[0., 1.],
[1., 0.],
[0., 1.]])
|
|
|
|
|
|
|
|
|
回归是连续值
分类是离散值
只需要设计一个方法,将连续数值映射到离散值上就可以了
|
回归问题模型输出的是一个连续的值,这个值是模型试图靠近标签的结果
- 因此这个值大概率在标签的附近,
- 当然了,数据量大后必有意外,就是总有一些数据离标签很远
对于,二分类,标签通常是0与1,
因此会在回归模型,也就是模型,因为模型本身输出的也是数值,
会在模型的后面加一个函数,比如sigmoid,softmax等,将数值映射到[0,1]
如此,可以标签的值对应
模型输出范围映射到[0,1],这只是个映射,哪以什么为阈值确定输出结果是0还是1呢?
不考虑业务场景的情况下,以0.5为阈值进行划分
|
使用one hot向量表示标签, 只有类型所在位置上的元素为1,其他位置上的元素为 0 |
import torch from torch import nn import torch.nn.functional as F from sklearn.datasets import make_regression X,y = make_regression(n_samples=10000,n_features=20,noise=0.01,random_state=73) X = torch.tensor(X).float() y = torch.tensor(y).reshape(-1,1).float() y.shape #torch.Size([10000, 1])
class Modle(nn.Module):
def __init__(self,in_feature_dim,out_feature_dim):
super().__init__()
self.linear = nn.Linear(in_features=in_feature_dim,out_features=out_feature_dim)
def forward(self,X):
x = self.linear(X)
return x
model = Modle(in_feature_dim=20,out_feature_dim=1)
model(X).shape #torch.Size([10000, 1])
def predict(X, model=model, threshold=0.5):
model.eval()
with torch.no_grad():
# 模型输出
y_out = model(X)
# 类别转换
y_pred = (y_out >= threshold).float()
return y_pred
y_pred = predict(X,model)
print(y_pred.shape) #torch.Size([10000, 1])
y_pred[:3]
tensor([[0.],
[0.],
[1.]])
|
|
|
|
从数据到标签概述 从数据到标签,可以一次到位,也可以嵌套,并且可以无限嵌套 - 对应模型可以有一层,也可以有多层 - 从一层流转到下一层,就是一次嵌套 - 线性嵌套不会改变参数变量的冥,嵌套后还是线性函数 固定的是数据与标签,变化是参数 - 从数据到标签的路径有N条 - 目的并不在于哪一条,而是可用的任一条 - 是大数定律与个体随机的综合体现, - 只要找到其中一条满足业务条件的路径即可 - 局部的随机性是允许的,并且还可以体现其健壮性 其中无用的特征,其对应的参数会不断变小,趋于0 - 会在变换中消失 全局视角 标签是目标,网络是路径,这中间有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()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(y_pred == label).float().mean()
label是索引编码,[0,n_class-1]
不管是二分类还是多分类,(y_pred == label)都是01矩阵/向量
它等价于
1. all_count = len(y_pred)
2. right_count = len(y_pred[y_pred == label])
3. acc = float(right_count)/float(all_count)
|
|
二分类 y_pred[label.reshape(-1)==1].float().mean() 多分类
lablen = y_pred[label.reshape(-1)==class_index] #对应某个真实类型
lablen_count = float(lablen.shape[0]) #真实个数
n_pre = float(lablen[lablen == class_index].shape[0]) #真实中模型预测为真实的个数
single_class_mean = n_pre/lablen_count
多分类举例 import numpy as np import torch class_index=0 y4 = np.array([2,2,0,0]) y_pred = np.array([[1,2,3],[1,2,3],[0,2,3],[10,2,3]]) y_pred = torch.tensor(y_pred) y_pred = torch.argmax(y_pred,dim=1) lablen = y_pred[y4.reshape(-1)==class_index] #对应某个真实类型 lablen_count = float(lablen.shape[0]) #真实个数 n_pre = float(lablen[lablen == class_index].shape[0]) #真实中模型预测为真实的个数 single_class_mean = n_pre/lablen_count single_class_mean #0.5 标签为0这个类别,共有两个,预测对了一个,TPF为50% |
|
|
|
|
|
|