算法选择 - 机器学习 - 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
这公式赋予了神经网络自动化的能力,给予了神经网络超越机器学习的底气,
在深度学习发展,具有里程碑的意义!
|
|
|
|
|
|
|
|
反向传播算法推导过程(看一篇就够了)