算法选择 - 机器学习 - DL深度学习 - CV - NLP 选择一个算法是为了解决一类问题,而DL算法通常包括以下三部分 或者说,将解决某个问题的算法,拆分为三部分 - 模型:网络参数结构定义 - 损失函数:衡量模型输出与数据标签的分布差异,值越小越好 - 优化器:调整模型的参数的方法 模型只是定义了参数/参数结构 怎么调整,尤其是如何自动化地调整模型的参数, 使得模型的输出与数据标签的分布越来越接近才是关键 损失函数与优化器配合,完成了参数的自动化调整 - 梯度下降法,偏导的正负就是下降的方向 - 没错,正负这个方向,比数值本身更重要 - 它为每个参数指明了下降的方向 - w = w - a*w.grad - a是一个小数,小于1, - w.grad是“损失函数”在参数w处的偏导 - 损失函数关于某个参数的偏导函数,如果有极小值 - 那么 w = w - a*w.grad 会慢慢使损失函数逼近这个极小值 - w.grad为正,w减小,小到一定程度, - 比如,在函数曲线上越过了极小值点,变大了,那么w.grad会变为负值 - 此时的w = w - a*w.grad就会变大 - 损失函数取得极小值时的位置,w就在这个位置附近振荡 - 极小值,是最低位置,w.grad为0,w并不一定为0 - 损失函数极小值,也不一定只有一个,可能会有多个 - 极小,是相对一定范围的w来说的,可能其他范围也有极小 - 就比如水坑,湖泊,河流,都有一个低极点,其偏导为0,但极点则有很多
从整个DL算法的解决来看,损失函数是入口, 输入的是模型与标签, 输出的是分布差异, 优化器是辅助工具,它负责更新损失函数中模型的参数
损失函数的结果是一个数值,随着训练,它的值可能是1000,800,500,300,100,80,... 下降是我们期望的,即逐渐降低,变小, 自变量x数轴,右为负无穷,左为正无穷,从右到左,x逐渐变大 即随着x的变大,损失函数变小 损失函数值降低的过程,即对应x处的导数为负数, 损失函数值上升的过程,即对应x处的导数为正数, x = x - a*x.grad 会向一个极小值附近靠拢 当导数为正,即x越过极小值时,x会变小,还会折回来, 然后就是在极小值附近来回移动... 但这个极小值不一定就是0值, 即模型所能表述的规律,很难与真实的数据标签的分布 完全相符 最好的结果就是:表象在规律附近振荡...
数据加载,预处理 import torch import os from tpf import stp from tpf import pkl_save,pkl_load from tpf.params import TPF_DATADIR def load_boston(split=True,test_size=0.15, reload=False): """房价(回归问题),后续默认加载首次生成的文件 X,y = load_boston(split=False) X_train, y_train, X_test, y_test = load_boston(split=True,test_size=0.15) print(type(X)) # class 'numpy.ndarray' print(X.shape) # (506, 13) print(y.shape) # (506,) """ if split and (not reload): tmp_path = os.path.join(TPF_DATADIR,"fangjia_boston_split.pkl") else: tmp_path = os.path.join(TPF_DATADIR,"fangjia_boston.pkl") if os.path.exists(tmp_path): return pkl_load(tmp_path) data_url = "http://lib.stat.cmu.edu/datasets/boston" raw_df = pd.read_csv(data_url, sep="\s+", skiprows=22, header=None) # print(raw_df.info()) data = np.hstack([raw_df.values[::2, :], raw_df.values[1::2, :2]]) target = raw_df.values[1::2, 2] if split: X_train, X_test, y_train, y_test = train_test_split(data, target, test_size=test_size, random_state=73) stp(X_train,"X_train") stp(y_train,"y_train") pkl_save((X_train,y_train, X_test, y_test),file_path=tmp_path) return X_train,y_train, X_test, y_test else: stp(data, "X") stp(target,"y") pkl_save((data,target),file_path=tmp_path) return data,target X_train,y_train, X_test, y_test = load_boston() stp(X_test) #shape:(76, 13), type:class 'numpy.ndarray' # ------------------------------------------------- # 数据预处理 # ------------------------------------------------- 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 X_train,y_train, X_test, y_test = get_data() # from tpf import stp # stp(y_test,"y_test") # shape:torch.Size([76, 1]), type:class 'torch.Tensor', y_test # print(y_test.dtype) # torch.float32 模型 # ------------------------------------------------- # 模型构造 # ------------------------------------------------- class LinearRegression(object): """模型构造:线性回归 """ loss = None def __init__(self, device=None,in_feature=13) -> None: if device: self.device = device else: # 若存在GPU则使用GPU self.device = "cuda:0" if torch.cuda.is_available() else "cpu" # 定义模型的参数 self.w = torch.randn(in_feature, 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() 训练 # 若存在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) 预测 y_pred = model.predict(X=X_test) print(y_pred[:5]) 损失 model.print_loss() 23.729873657226562 |
数据处理,正向传播,损失函数,优化器,训练,预测... 这些步骤中,理解难度大在的地方在于两点:正向传播, 优化器 正向传播 def forward(self, X): return X@self.w + self.b 这涉及线性代数,也有微分的思想, 大概意思就是,众人拾柴火焰高,人多力量大,勤能补拙... 以众多局部的直,去模拟实际的曲, 提一句微分, 把一件复杂的事,拆分成多个简单的成分,这就是微分, 从多个简单的角度去描述一件复杂的事物,这就是积分! 就是这么简单...只是有人把这种思想以数学的形式,精准地描述出来了! 难理解就是难理解,实现中,不只是普通人, 神经网络刚出来的时候,许多的学者,教授都不理解,不看好,认为不可能... 本人在学到这个时,连连惊奇并研究了好几个月... 没错,本人并不聪明,一个简单的神经网络研究了好几个月!!! X是常量,虽然每次可以输入不同的X,即不同的样本,但它还是常量 说它是常量,是因为X在整个计算过程中不变,就是一个常量“矩阵” X是矩阵,是矩阵,是矩阵,w也是矩阵,也是矩阵,也是矩阵 但w是变量,是变化的, 对于不同的X,w是相同的,代表了数据集的共性提取方法 任务的目的,就在于,对于某个数据集, 找到一个参数矩阵/参数网络, 它能够将常量X(即样本),映射到标签的维度上 优化器 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_() 如果说前面的Ax=b是复杂问题简单化/线性化,是简单粗暴的微分描述, 那么 w = w - learning_rate*w.grad 则是用规则/公式 去直接描述变化/变化趋势 也有微分的思想,因为这里的w指的是众多参数的中每一个 这个公式的关键在于, 通过“损失函数”梯度的正负特性,建立参数/变量w与函数极小值之间的关系 效果是 我们想找到模型输出与标签分布之间的最小差值,即损失函数的极小值, 那么可以通过公式w = w - learning_rate*w.grad 来调整参数w实现 w = w - learning_rate*w.grad 这公式赋予了神经网络自动化的能力,给予了神经网络超越机器学习的底气, 在深度学习发展,具有里程碑的意义! |
|
|
|
|
反向传播算法推导过程(看一篇就够了)