数学理论 线性指线性变换,y = w1*x1 + w2*x2 +...+wn*xn + b - xi是变量,n元一次变量,因变量,y因为它发生变化 - wi是权重 - b是偏置,也叫截距 这是个万能公式,如果你的变量足够多,可以近似表示任何事物之间的关系 线性变换是n元一次方程, 变量y 与 x(x1,x2,...,xn,1) 的一个 线性组合(w1,w2,...,wn,b) 近似成 正比关系/线性关系 - 而不与某个变量成线性关系 - 回归有几重含义: 1. 特指AI/统计学 中 连续/分类两大问题中的 连续问题,回归代表着连续,毕竟世界本质是连续的 2. 映射到本质,回到更底层,本源层面; 用1维数据构建一个线性组合/结构,描述一个事物, 反过来就是,将事物投影/映射到1维结构中, 不管一个事物多么复杂,都可以通过构建一个1维线性结构近似描述 复杂问题简单化,由高级/高维 回归 本质/低维 3. 空间有一个正交直角坐标系,每个维度都是线性的, 将各个维度的数据排成一列,就得到一个维线性结构, 这个结构可表示万物 4. 微分思想,任何高阶函数都可以通过微分化阶为一,用1维数据的积分表示 5. 量变达到质变,单个1维变量是简单的,三个臭皮匠却能抵上一个诸葛亮,当N多个变量出现时,可模拟万物 - 单个人的力量弱 - 团队的力量大,尤其是形成一定 组织/组合 之后力量更强 |
深度学习的线性回归 AI中使用向量表示一个样本x(x1,x2,...,xn)(行向量),w(w1,w2,...,wn)(列向量) xw + b = y 深度学习的数据都是以批量形式存在的,一个批次X中有多个x X@w + b = y w与X每一行运算,然后映射到y, 等价于 批量数据X经过参数矩阵w转换,映射到向量y(y1,y2,...,yn) 每个样本x对应一个yi,所以X在前,W在后 相乘再相加 深度学习之中都是 相乘再相加 在[0,1]的尺度上,相乘相加,以这种方式不断地 融合变换 这有一种大道至简的感觉,不像机器学习那样,整合了高大上的理论或公式... - 如此简洁的方式,却可以 模拟/拟合 万物 谁是变量,认是常量 x1,x2,...,xn 这组数形成一个向量,代表一个样本,是常量 w1,w2,...,wn 是权重,是算法自身要调整的值,这些值来自数据,是变量 算法的学习就是要找到一组合适的wi,与样本向量线性组合,映射到 某个类别 以上是训练过程,训练中需要学习参数 预测时,参数固定,参数与数据皆是常量 |
深度学习基本步骤 数据预处理:空值处理,归一化 模型定义:参数网络初始化, 计算方法 (不包含数据,数据只是流过参数网络) 损失函数设计:求极值,DL都是批次操作,实际是批次损失 极值的均值 模型参数优化方法设计:优化模型参数的方法 训练:调用优化方法优化参数 预测:利用训练时相关参数处理数据,然后输入模型得到模型输出 模型保存与交付:数据预数据过程及参数,模型(模型参数,模型网络结构) |
class Model(object): """ 机器学习模型骨架 """ def __init__(self, hyper_parameters): """ 定义超参数 """ self.hyper_parameters = hyper_parameters def fit(self, X, y): """ 训练过程 """ pass def predict(self, X): """ 定义预测过程 """ pass |
|
![]() ![]() tensor设置为requires_grad=True后,将被看作一个变量 zero_这种下划线写在单词末尾的方式,表示replace, 表示原地转换变量的值,而不是返回一个新变量 |
在 PyTorch 的自动微分系统中, 只有叶子张量(即那些直接由用户创建的张量,而不是通过其他张量计算得到的)的 .grad 属性会被填充梯度信息。 如果一个张量是通过其他张量的操作得到的,那么它就不是叶子张量,其 .grad 属性默认不会被填充。 import torch w1 = torch.tensor(3.0,requires_grad=True) b = torch.tensor(2.0,requires_grad=True) y = w1*2 + b*1 y.backward() if y.is_leaf: #用户直接创建的 print(y.grad) else: print("y is not a leaf tensor") 此时的y是由其他张量计算得到的,y.grad不会被填充梯度 但要注意,深度学习也并不是要对y求偏导,y对应损失函数, y.backward() 是梯度反向传播,最终求导的是参数变量 w1.grad # tensor(2.) |
import torch w1 = torch.tensor([1.0,1.0],requires_grad=True) b = torch.tensor(1.0,requires_grad=True) y = w1 + b # tensor([2., 2.], grad_fn= w1.grad # tensor([1., 1.]) b.grad # tensor(2.) w1有多个维度,每个维度都需要求一个偏导,皆被当作一个变量, 因为他们的系数永远一样,所以其梯度也一样 由于广播机制,变量b与w1的每个变量 进行了一次运算,相当于每个元素都有一个b,sum时相加了n次 |
import torch # tensor([0.1000, 0.2000], requires_grad=True) w1 = torch.arange(0.1, 0.3, 0.1,requires_grad=True) w2 = torch.arange(0.01, 0.03, 0.01,requires_grad=True) # tensor([0.1000, 0.2000], requires_grad=True) tensor([0.0100, 0.0200], requires_grad=True) print(w1,w2) b = torch.tensor(0.1,requires_grad=True) y = w1*3 + w2*2 + b print(y.shape) # torch.Size([2]) print(y.sum()) # tensor(1.1600, grad_fn=SumBackward0) print(y.sum().shape) # torch.Size([]) # torch只能对标量[](单个数字的tensor)求导,y是shape为[3]的tensor loss = y.sum() # 对变量x求导,然后将求导结果赋值给tensor x的grad属性 # 每求一次导,就执行一次 x.grad = x.grad + grad loss.backward() print(w1.grad.data) # tensor([3., 3.]) print(w2.grad.data) # tensor([2., 2.]) """ 对每一个变量,即w1,w2分别求导 针对某个变量,有多个值,在每一个值处有一个导数 """ print(b.grad.data) # tensor(2.) """ 常数的导数是0,但是b参数,是变量 所以b的导数是它的系数1 由于w1与b的shape并不一致,b向量只有一个数,在计算时会将b加到w1的每一个数上 即w1有多少个数,b就会被计算多少次,也就是广播机制 每一次计算,都会执行b.grad = b.grad + 1(b的导数) w1有两个数,所以这里结果为2 """ # 为防止累加,每计算完一次求导运算,就清空一次属性值 w1.grad.data.zero_() w2.grad.data.zero_() b.grad.data.zero_() print(w1.grad.data) # tensor([0., 0.]) print(b.grad.data) 将参数的长度改为3,验证如下,结果与前面结论一致 import torch w1 = torch.tensor(data=[0.1,0.2,0.3],requires_grad=True) w2 = torch.tensor(data=[0.01,0.02,0.03],requires_grad=True) b = torch.tensor(0.1,requires_grad=True) y = w1*3 + w2*2 + b loss = y.sum() loss.backward() print(b.grad.data) # tensor(3.) |
|
import numpy as np from matplotlib import pyplot as plt from sklearn.model_selection import train_test_split import torch from sklearn.linear_model import LinearRegression from sklearn.datasets import make_regression # 随机生成的回归测试数据 X, y = make_regression(n_samples=2000, n_features=13) X.shape,y.shape #((2000, 13), (2000,)) X_train, X_test, y_train ,y_test = train_test_split(X, y, test_size=0.15, random_state=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_ lr1 = LinearRegression() lr1.fit(X=X_train, y=y_train) #参数w lr1.coef_[:3] array([95.26287774, 15.38262642, 74.11799156]) #偏置b lr1.intercept_ -5.381843234898983 lr1.predict(X=X_test)[:3],y_test[:3] (array([ -97.28120707, -95.63686668, -147.64895265]), array([ -97.28120707, -95.63686668, -147.64895265])) 机器学习中标签为向量,shape=(n,) 尝试学习中标签为矩阵,shape=(n,1) y_test.shape #(300,) |
import numpy as np from matplotlib import pyplot as plt from sklearn.model_selection import train_test_split import torch from sklearn.linear_model import LinearRegression from sklearn.datasets import make_regression # 随机生成的回归测试数据 X, y = make_regression(n_samples=2000, n_features=13) X.shape,y.shape #((2000, 13), (2000,)) X_train, X_test, y_train ,y_test = train_test_split(X, y, test_size=0.15, random_state=1) """ 数据转tensor """ # 检测是否有GPU device = "cuda:0" if torch.cuda.is_available() else "cpu" X_train1 = torch.tensor(data=X_train).to(dtype=torch.float64).to(device=device) y_train1 = torch.tensor(data=y_train).to(dtype=torch.float64).to(device=device) X_test1 = torch.tensor(data=X_test).to(dtype=torch.float64).to(device=device) y_test1 = torch.tensor(data=y_test).to(dtype=torch.float64).to(device=device) y_train1=y_train1.reshape(-1,1) y_test1=y_test1.reshape(-1,1) 模型及参数,损失函数 def linear_regression(X, w, b): """线性回归(计算逻辑) """ return X @ w + b def loss_fn(y_pred, y_true): """mse损失函数 """ return ((y_pred - y_true) ** 2).mean() #随机初始化模型的参数 w = torch.randn(13, 1, requires_grad=True, dtype=torch.float64, device=device) b = torch.randn(1, requires_grad=True, dtype=torch.float64, device=device) w.shape,b (torch.Size([13, 1]), tensor([1.9541e-14], dtype=torch.float64, requires_grad=True)) 模型参数不在于大小,随机初始化即可 在于shape,要与数据X能进行矩阵运算 参数要将一个样本向量的n个维度[n1,n2,...,nn]变换到一个维度上[类别] def predict(X, w, b): with torch.no_grad(): y_pred = linear_regression(X=X, w=w, b=b) return y_pred 训练 lr = 1e-2 for epoch in range(10000): # 正向传播 --- 模型输出,预测结果 y_pred = linear_regression(X=X_train1, w=w, b=b) # 计算损失 -- loss = loss_fn(y_pred=y_pred, y_true=y_train1) # 反向传播 -- 求w和b的偏导数 loss.backward() # 更新参数 w.data -= lr * w.grad b.data -= lr * b.grad # 清空梯度 w.grad.data.zero_() b.grad.data.zero_() # 过程监控 if epoch % 1000 == 0: print(loss.item()) 32887.253607779305 9.500932702751085e-13 5.664800872938198e-25 5.666899667315686e-25 5.666979629969325e-25 5.66737335508748e-25 5.6672913877990685e-25 5.6672913877990685e-25 5.666897662680915e-25 5.666897662680915e-25 predict(X=X_test1, w=w, b=b)[:3] tensor([[ 209.9008], [ -86.7581], [-154.0384]], dtype=torch.float64) y_test1[:3] tensor([[ 209.9008], [ -86.7581], [-154.0384]], dtype=torch.float64) w[:3] tensor([[1.2952e+01], [2.0628e-14], [1.1275e+01]], dtype=torch.float64, grad_fn= |
import torch from ai.datasets import load_boston # ------------------------------------------------- # 数据预处理 # ------------------------------------------------- def get_data(): X_train,y_train, X_test, y_test = load_boston() # 从训练集中提取参数 mean_ = X_train.mean(axis=0) # 按列方向取均值, std_ = X_train.std(axis=0) # 相同特征的一列数据归一 # 预处理训练集和测试集 X_train1 = (X_train - mean_) / std_ X_test1 = (X_test - mean_) / std_ # 共享内存 X_train = torch.from_numpy(X_train1).float() X_test = torch.from_numpy(X_test1).to(dtype=torch.float32) # 标签转列向量 y_train = torch.from_numpy(y_train.reshape((-1, 1))).float() y_test = torch.from_numpy(y_test.reshape((-1, 1))).float() return X_train,y_train, X_test, y_test # ------------------------------------------------- # 模型构造 # ------------------------------------------------- class LinearRegression(object): """模型构造:线性回归 """ loss = None def __init__(self, device=None) -> None: if device: self.device = device else: # 若存在GPU则使用GPU self.device = "cuda:0" if torch.cuda.is_available() else "cpu" # 定义模型的参数 self.w = torch.randn(13, 1, requires_grad=True, dtype=torch.float32, device=device) self.b = torch.randn(1, requires_grad=True, dtype=torch.float32, device=device) # print(b) # tensor([0.1403], requires_grad=True) # stp(b,"b") # shape:torch.Size([1]), type:class 'torch.Tensor', b def forward(self, X): return X@self.w + self.b # ------------------------------------------------- # 损失函数设计 # ------------------------------------------------- @classmethod def loss_fn(cls, y_pred, y_true): cls.loss = ((y_pred - y_true) ** 2).mean() return cls.loss def grad_reduce(self, learning_rate): """梯度下降法 一种优化方法,让模型输出不断接近真实标签的方法 梯度指导数 """ # 使用导数优化更新参数 self.w.data -= learning_rate * self.w.grad.data self.b.data -= learning_rate * self.b.grad.data # 清空梯度,以防止不断累加 self.w.grad.data.zero_() self.b.grad.data.zero_() def print_loss(self): print(self.loss.item()) def train(self, X,y, learning_rate, loss_fn=None): """训练 每训练一次,就优化一次参数 """ # 正向传播 y_pred = self.forward(X=X) # print("y pred",y_pred[1],"y label",y_train[1]) # 模型预测与真实标签间的差异 if loss_fn: loss = loss_fn(y_pred, y) else: loss = self.loss_fn(y_pred=y_pred, y_true=y_train) # 反向传播,逆向求偏导grad loss.backward() model.grad_reduce(learning_rate=learning_rate) def predict(self, X): """ 模型预测 """ with torch.no_grad(): y_pred = self.forward(X=X) return y_pred def batch_train(model, X, y, learning_rate=1e-2, epochs=300): """训练 梯度下降法求函数极值 """ for epoch in range(epochs): model.train(X=X,y=y, learning_rate=learning_rate) # 过程监控 model.print_loss() if __name__=="__main__": X_train,y_train, X_test, y_test = get_data() # from ai.box.base import stp # stp(y_test,"y_test") # shape:torch.Size([76, 1]), type:class 'torch.Tensor', y_test # print(y_test.dtype) # torch.float32 # 若存在GPU则使用GPU device = "cuda:0" if torch.cuda.is_available() else "cpu" X_train.to(device=device) y_train.to(device=device) X_test.to(device=device) y_test.to(device=device) model = LinearRegression() batch_train(model=model,X=X_train,y=y_train) """ 587.739013671875 545.857666015625 510.1678161621094 ...... 23.185871124267578 23.183822631835938 23.181793212890625 """ y_pred = model.predict(X=X_test) print(y_pred) """ tensor([[17.6774], [25.3967], [36.5577], ... ... ... [28.7579], [29.5272], [32.7000]]) """ 重点分析: 1. 模型定义:X@self.w + self.b 2. 损失函数设计: MSE,((y_pred - y_true) ** 2).mean() |
|
|
import numpy as np import torch from torch import nn import torch.nn.functional as F # torch 批次处理 from torch.utils.data import Dataset from torch.utils.data import DataLoader from sklearn.datasets import make_regression from sklearn.model_selection import train_test_split # 因为加入了1%的噪声,所以,不管怎么训练,模型的精度上限是 99%,会有稍许浮动,但不会达到100% # 这也说明影响精度的两个重要因素:一是模型算法不够好,二是数据噪声太多(这个决定了上限) X,y = make_regression(n_samples=10000,n_features=100,noise=0.01,random_state=73) X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.1,random_state=73) class MyDataSet(Dataset): def __init__(self,X,y): """ 构建数据集 """ self.X = X self.y = y.reshape(-1,1) print(f"seq_len={len(X[0])}") def __len__(self): return len(self.X) def __getitem__(self, idx): x = self.X[idx] y = self.y[idx] return torch.tensor(data=x).float(), torch.tensor(data=y).float() # 自定义数据加载器 train_dataset = MyDataSet(X=X_train,y=y_train) test_dataset = MyDataSet(X=X_test,y=y_test) # ----------------------------- # 模型定义 # ----------------------------- class LinearModel(nn.Module): def __init__(self, in_features=100, out_features=1): """初始化网络参数""" super().__init__() self.linear = nn.Linear(in_features=in_features, out_features=out_features) def forward(self, X): """让数据流过参数网络""" x = self.linear(X) return x #模型 model = LinearModel(in_features=100, out_features=1) #损失函数 loss_fn = nn.MSELoss() #优化器 optim = torch.optim.Adam(params=model.parameters(),lr=1e-3) # 从数据集中批次取数据 train_dataloader = DataLoader(dataset=train_dataset, shuffle=True, batch_size = 128) # 训练一行数据,测试代码是否有误 for X,y in train_dataloader: print(X.shape,X.ndim,y.shape,y.ndim) # torch.Size([128, 100]) 2 torch.Size([128, 1]) 2 y_out = model(X) if y_out.ndim != y.ndim: print('模型输出数据维度与标签维度不一致,无法进行损失计算...') print('y_out:',y_out.shape,y_out.ndim) # torch.Size([128, 1]) 2 break loss = loss_fn(y_out,y) optim.zero_grad() loss.backward() optim.step() break # ------------------ # 重要:参数网络定义(模型定义),如何让参数逼近标签(损失函数设计及优化方法) # ------------------ |
数据集 from tpf.dl import MyDataSet from tpf.dl import log from tpf.dl import DMEval import numpy as np import torch from torch import nn # torch 批次处理 from torch.utils.data import Dataset from torch.utils.data import DataLoader from sklearn.datasets import make_regression from sklearn.model_selection import train_test_split # 因为加入了1%的噪声,所以,不管怎么训练,模型的精度上限是 99%,会有稍许浮动,但不会达到100% # 这也说明影响精度的两个重要因素:一是模型算法不够好,二是数据噪声太多(这个决定了上限) X,y = make_regression(n_samples=10000,n_features=100,noise=0.01,random_state=73) X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.1,random_state=73) device = "cuda:0" if torch.cuda.is_available() else "cpu" # 自定义数据加载器 train_dataset = MyDataSet(X=X_train,y=y_train) test_dataset = MyDataSet(X=X_test,y=y_test) 模型,损失函数,优化器 # ----------------------------- # 模型定义 # ----------------------------- from tpf.dl import LinearModel #模型 model = LinearModel(in_features=100, out_features=1) #损失函数 loss_fn = nn.MSELoss() #优化器 optim = torch.optim.Adam(params=model.parameters(),lr=1e-3) # 从数据集中批次取数据 # train_dataloader = DataLoader(dataset=train_dataset, shuffle=True, batch_size = 128) 训练 from tpf.dl import T T.train(model=model, loss_fn=loss_fn, optimizer="adam", train_dataset=train_dataset, test_dataset=test_dataset, epochs=3, batch_size=128, learning_rate=1e-3, model_param_path="ml_model1_params.h5", auto_save=True, continuation=True, #是否在之前训练的基础上进行训练 is_regression=True, log_file="/tmp/train.log") |
|
|
X@w + b 代码虽然只有一行,需要注意的地方有很多: 1 X是多个样本形成的批次,行代表样本的个数, 2 列代表不同的特征 3 w为列向量,b为标量 4 X@w表示X通过w进行映射,即X中的每一行通过w进行映射,或者说w对X的特征进行变换 5 数据X在左侧,参数矩阵w在矩阵乘法的右侧, 6 本质是每一行都与列向量w进行向量内积运算:相乘再相加,化为一个标量(一个数), 由于有N行,所以形成一个列向量,实际每个样本被转化成了标量,降了一维 表示从列向量w的角度从样本中提取出一个维度的数据 7 广播进制,b是标量,标量与矩阵一起运算存在广播机制,但这里每行的x与w运算后也化成了一个标量,所以每行b被运算了一次 但X是有多行的,所以整个表达式b被加到了X的每一行上,b被运算的次数为X的行数 8 AI中永远一个样本对应一个预测结果, X批次的维度,这里是行,这个维度永远不会变, 即不管怎么预测,有多少个样本就会出多少个结果 9 这一步的X,就是能与参数做矩阵乘法的X,必定是经过预处理的X 10 AI中与X成对出现的标签y,没有在这个表达式中,也就是说标签不参与模型计算,哪标签在哪算? 11 标签参与损失函数的计算,损失函数衡量模型输出与标签之间的差异,即标签比较的对象是这个表达式本身 12 不知你是否质疑过b为什么是一个标量,为什么不定义成列向量, 如果是列向量,即为每个样本定义一个不同的b,这样有违AI的核心, AI求的是那个参数矩阵,对所有样本都适用的参数矩阵,求的是共性 只属于某个样本,而不是一类样本的特征,AI有专门的名词来形容:过拟合,这是我们要避免的 在y=ax+b这样的一元一次线性方程中,从解析几何的角度看,ax决定的是函数的倾斜度,b表示对函数图形整体做上下移动 即b的作用是在不改变函数结构的情况下,使之更接近标签数据 13 表达式整体是线性回归,矩阵相乘,是向量内积,是线性代数那本书的核心,是深度学习中全连接/神经网络层的别名 14 回归是连续的数据,但X可是离散型数据,这是怎么预测出连续的结果的,原理何在? 这得用大片文字解释,也是AI的一个重要环节,并且还比较抽象,此处暂时忽略,不然这文章都写不完了.... 15 看到矩阵相乘,要立即确定两个点:1 每个矩阵的shape, 2 是否存在广播机制 16 X@w+b是模型算法,它的shape决定了标签要转化的shape,因为它将来要与标签求损失, 最好让标签的shape与模型输出的shape一致,否则广播机制就会生效, 17 模型算法可以有多个变量形成一个参数矩阵,比如w,但将来定义的损失函数的结果loss_fn=f(w1,w2,...,wn)是一个标量 由于参与运算的都是矩阵,所以自定义损失函数矩阵运算之后,其结果通常不是一个标量, 需要确认这一点,然后将之转化为标量,变量可以多个维度,损失结果我们只要一个值就可以了 18 终极目的:就是我们在干什么,为了什么?我们为了将业务数据从一个维度转换到另外一个业务维度, 从纯数据的角度看,就是对数据完成了一次线性变换,将数据从一个维度变换到另外一个维度 怎么实现的?就是矩阵相乘, 将业务映射为数据,通过矩阵相乘提取某个维度的数据, 后续再通过一些优化方法,使这样的变换更加准确 19 X@w+b线性回归模型的表达能力是有上限的,并且不高,只能处理一些简单的问题; 所以复杂问题就不要用这个,但简单问题用简单方法解决也并无问题 20 批次处理。本质就是个向量内积,多个向量放一起,形成多行就有了批次, 批次这个维度这里是行(X的第1维)还可以是列(X的第2维),具体要看处理什么样的数据, 这里,你的脑海中应该有一行行的数据与w进行相乘再相加的操作,转化为一个个数字, 这些数字按批次的维度排列...数据从一个维度转换到了另外一个维度,这么一个画面 而不是像没接触过矩阵那样,潜意识认为X只是一个数字, 21 计算图。深度学习框架解决的主要问题是自动求导,解决方式就是计算图; 以pytorch为例,从import torch这一行开始,计算图就开始构建了 如果你想要一段不进行自动求导的代码,比如预测的时候,需要加上 with torch.no_grad(): y_pred = X@w+b 由于是框架自动实现的,这一点极容易被忽略; 计算图自动求导,广播机制 是深度学习矩阵运算容易被忽略的两个地方
x = x - h h 是y = model(x)在x处的导数再乘一个比较小的正数 导数可以反应函数的变化趋势,可以从大量数据中快速找到区域极值,即导数为0的地方 x - h, 如果函数曲线是下降趋势,那么h为负,表示当前位置未过极值,x-h就是加上一个正数,表示向前一小步 如果函数曲线是上升趋势,那么h为正,表示当前位置过了极值,x-h就是加上一个负数,表示退后一小步 这意味着随机从函数上取一点,一步步走下去,总会逼近一个极值,这就是我们需要的 深度学习的核心问题,是快速 快速 快速求出极值,数学上转化为求导问题 数学上的求导,可以一次对N个变量求导,此时又叫求偏导 核心是导数可以反应函数的变化趋势,在数字上体现为有正负 它是多大不是重点,要确保的是乘上一个较小的数,其结果h不要太大,即一次跳过的长度不要太大 重点是它有正负 这个正负让 x-h 这个表达式可以从函数的任意一点逼近它所在局部区域的极值 如果求导问题搞不定,深度学习就进行不下去,好在现在有很多框架实现了自动求导 难点在于x是向量,由n个变量组成,对x进行一次求导,就是求所有变量的导数(偏导数) x - h ,是x所有变量同时向各自区域附近的极值走一步
维度/特征 变换/转换
全连接,亦 线性变换, 将 数据/向量 的维数 变换到 另外一个维数 将数据的特征 从一个空间 映射到 另外一个空间