说明:这篇文章来自 2020-2021 年的 WordPress 备份。原始图片附件未随 XML 一起保存,旧服务器图片地址也已失效,因此这里保留正文并移除了失效图片。
一 函数
-
tf.where(条件语句,真返回A,假返回B)
a = tf.constant([1, 2, 3, 1, 1]) b = tf.constant([0, 1, 3, 4, 5]) c = tf.where(tf.greater(a, b), a, b) # 若a>b,返回a对应位置的元素,否则返回b对应位置的元素 结果:tf.Tensor([1 2 3 4 5], shape=(5,), dtype=int32) -
np.random.RandomState.rand()
#返回一个[0,1)之间的随机数 rdm = np.random.RandomState(seed=1) a = rdm.rand() b = rdm.rand(2, 3) #维度 -
np.vstack((a, b)) 将两个数组按垂直方向叠加
a = np.array([1, 2, 3]) b = np.array([4, 5, 6]) c = np.vstack((a, b)) output:c: [[1 2 3] [4 5 6]] -
np.mgrid()/np.ravel()/np.c_() 一起使用生成网格坐标点 np.mgrid[起始值:结束值:步长,起始值:结束值:步长,~] 返回若干组维度相同的等差数组,[起始值:结束值) x.ravel()将x变为一维数组 np.c_()使返回的间隔数值点配对
# 生成等间隔数值点 x, y = np.mgrid[1:3:1, 2:4:0.5] # 将x, y拉直,并合并配对为二维张量,生成二维坐标点 grid = np.c_[x.ravel(), y.ravel()] print("x:\n", x) print("y:\n", y) print("x.ravel():\n", x.ravel()) print("y.ravel():\n", y.ravel()) print('grid:\n', grid) output:x: [[1. 1. 1. 1.] [2. 2. 2. 2.]] y: [[2. 2.5 3. 3.5] [2. 2.5 3. 3.5]] x.ravel(): [1. 1. 1. 1. 2. 2. 2. 2.] y.ravel(): [2. 2.5 3. 3.5 2. 2.5 3. 3.5] grid: [[1. 2. ] [1. 2.5] [1. 3. ] [1. 3.5] [2. 2. ] [2. 2.5] [2. 3. ] [2. 3.5]]
二 概念
1 神经网络复杂度
神经网络(NN)复杂度:多用NN层数和NN参数的个数表示
- 空间复杂度: 层数 = 隐藏层的层数 +1个输出层 上图为2层NN 总参数 = 总w + 总b 上图 3 4 + 4 + 4 2+2 = 26
- 时间复杂度 乘加运算次数 上图 3 4 + 4 2 = 20
2 指数衰减学习率
可以先用较大的学习率,快速得到较优解,然后逐步减小学习率,使模型在训练后期稳定 指数衰减学习率 = 初始学习率 * 学习率衰减率 ^(当前轮数/多少轮衰减一次) 粗体为超参数
w = tf.Variable(tf.constant(5, dtype=tf.float32)) epoch = 40 LR_BASE = 0.2 # 最初学习率 LR_DECAY = 0.99 # 学习率衰减率 LR_STEP = 1 # 喂入多少轮BATCH_SIZE后,更新一次学习率for epoch in range(epoch): # for epoch 定义顶层循环,表示对数据集循环epoch次,此例数据集数据仅有1个w,初始化时候constant赋值为5,循环40次迭代。 lr = LR_BASE * LR_DECAY ** (epoch / LR_STEP) #**代表次方 with tf.GradientTape() as tape: # with结构到grads框起了梯度的计算过程。 loss = tf.square(w + 1) grads = tape.gradient(loss, w) # .gradient函数告知谁对谁求导
w.assign_sub(lr * grads) # .assign_sub 对变量做自减 即:w -= lr*grads 即 w = w - lr*grads print("After %s epoch,w is %f,loss is %f,lr is %f" % (epoch, w.numpy(), loss, lr))
可以看出随着迭代次数的增加,lr在递减
3 激活函数
概念
MP模型
简化的MP模型是线性函数,多层神经网络还是线性函数,模型表达力不够。 上图中非线性函数叫做激活函数,激活函数的加入使得多层神经网络不再是输入x的线性组合,可以随层数的增加提升表达力。
优秀的损失函数应具有的特性和输出值的范围
常用的激活函数
1. sigmoid函数
tf.nn.sigmoid(x) ——> f(x) = 1 / (1 + e^(-x)) sigmoid函数及其导数图像为
特点
- 易造成梯度消失 深层神经网络更新参数时需要从输出层到输入层逐层进行链式求导,sigmoid函数的导数输出是0-0.25之间的小数,链式求导需要多层导数连续相乘,多个0-0.25连续相乘结果会趋于0,导致梯度消失,使得参数无法继续更新。
- 输出非0均值,收敛慢
- 幂运算复杂,训练时间长
2. tanh函数
tf.math.tanh(x) ——> f(x) = (1-e^(-2x))/(1+e^(-2x)) tanh函数及其导数图像为
特点
- 输出是0均值
- 易造成梯度消失 类似于sigmoid函数
- 幂运算复杂,训练时间长
3. relu函数(首选)
tf.nn.relu(x) ——> f(x) = max(x, 0) ——>
f(x) = 0 ,x < 0
f(x) = x ,x >= 0
relu函数及其导数图像为
优点
- 解决了梯度消失问题(正区间)
- 只需判断输入是否大于0,计算速度快
- 收敛速度快于sigmoid和tanh 缺点
- 输出非0均值,收敛慢
- Dead RelU 问题,某些神经元可能被激活,导致相关的参数永远不能被更新 输入特征是负数时,激活函数输出是0,反向传播得到的梯度是0,导致参数无法更新,造成神经元死亡。可以通过改进随机初始化,避免负数特征送入relu函数,设置更小的学习率,减少参数分布的巨大变化,避免训练中产生过多负数特征
4. Leaky relu函数
tf.nn.leaky_relu(x) ——> f(x) = max(ax, x) 图像
导数图像
理论上有relu所有优点且不会有DEAD REALU问题,实际上并没有比relu更好,使用relu更多。
总结:
- 首选relu激活函数
- 学习率设置较小值
- 输入特征标准化,即让输入特征满足以0为均值,1为标准差的正态分布
- 初始参数中心化,即让随机生成的参数满足以0为均值,(2/当前层输入特征个数)^(1/2)为标准差的正态分布
4 损失函数
损失函数就是预测值y与已知答案y_的差距,NN的优化目标就是计算出参数使得loss最小 主流loss有三种计算方法:
1.均方误差mse(mean squared error)
均方误差mse:MSE( y , y ) = (∑( y - y )^2)/n loss_mse = tf.nn.reducemean(tf.square( y - y ))
2.自定义损失函数
自定义损失函数: loss( y , y ) = ∑f( y , y )
3. 交叉熵损失函数CE(cross entropy)
表征两个概率分布之间的距离 H(y, y) = -∑y * lny 函数:tf.losses.categoricalcrossentropy(y, y)
loss_ce1 = tf.losses.categorical_crossentropy([1, 0], [0.6, 0.4])
loss_ce2 = tf.losses.categorical_crossentropy([1, 0], [0.8, 0.2])
print("loss_ce1:", loss_ce1) #输出loss_ce1: tf.Tensor(0.5108256, shape=(), dtype=float32)
print("loss_ce2:", loss_ce2) #输出loss_ce2: tf.Tensor(0.22314353, shape=(), dtype=float32)
#第二组损失函数更小
4. softmax与交叉熵结合
输出先通过softmax函数,再计算y与y_的交叉熵损失函数 函数:tf.nn.softmax_cross_entropy_withlogits(y, y)
5 欠拟合与过拟合
- 欠拟合的解决方法:
- 增加输入特征项
- 增加网络参数
- 减少正则化参数
- 过拟合的解决方法:
- 数据清洗
- 增大训练集
- 采用正则化
- 增大正则化参数
正则化缓解过拟合
正则化再损失函数中引入模型复杂度指标,利用给w加权值,弱化了训练数据的噪声(一般不正则化b) 正则化后 loss = loss(y与y_) + REGULARIZER * loss(w) REGULARIZER:超参数,参数 w 在总loss中的比例,即正则化的权重 loss(w):需要正则化的参数 loss(w)的计算有两种方法
-
L1正则化:L1_loss(w) = ∑|w| 大概率会使很多参数变为零,因此该方法可通过稀疏参数,即减少参数的数量,降低复杂度
-
L2正则化:L2_loss(w) = 1/2*∑|w^2| 会使参数很接近零但不等于零,因此该方法课通过减小参数值的大小降低复杂度
h1 = tf.matmul(x_train, w1) + b1 # 两层网络w1,b1 ;w2 b2 h1 = tf.nn.relu(h1) y = tf.matmul(h1, w2) + b2 # 采用均方误差损失函数mse = mean(sum(y-out)^2) loss_mse = tf.reduce_mean(tf.square(y_train - y)) # 添加L2正则化 loss_regularization = [] # tf.nn.l2_loss(w)=sum(w ** 2) / 2 loss_regularization.append(tf.nn.l2_loss(w1)) loss_regularization.append(tf.nn.l2_loss(w2)) #L2正则化求和 # loss_regularization = tf.reduce_sum(tf.stack(loss_regularization)) tf.stack增加一个维度 loss_regularization = tf.reduce_sum(loss_regularization) loss = loss_mse + 0.03 * loss_regularization #REGULARIZER = 0.03
正则化缓解过拟合实例
# 读入数据/标签 生成x_train y_train df = pd.read_csv('dot.csv') x_data = np.array(df[['x1', 'x2']]) y_data = np.array(df['y_c'])x_train = np.vstack(x_data).reshape(-1,2) #reshape成两列,行数自动生成 y_train = np.vstack(y_data).reshape(-1,1)
Y_c = [[‘red’ if y else ‘blue’] for y in y_train]
转换x的数据类型,否则后面矩阵相乘时会因数据类型问题报错
x_train = tf.cast(x_train, tf.float32) y_train = tf.cast(y_train, tf.float32)
from_tensor_slices函数切分传入的张量的第一个维度,生成相应的数据集,使输入特征和标签值一一对应
train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(32)
生成神经网络的参数,输入层为2个神经元,隐藏层为11个神经元,1层隐藏层,输出层为1个神经元
用tf.Variable()保证参数可训练
w1 = tf.Variable(tf.random.normal([2, 11]), dtype=tf.float32) b1 = tf.Variable(tf.constant(0.01, shape=[11]))
w2 = tf.Variable(tf.random.normal([11, 1]), dtype=tf.float32) b2 = tf.Variable(tf.constant(0.01, shape=[1]))
lr = 0.01 # 学习率 epoch = 400 # 循环轮数
训练部分
for epoch in range(epoch): for step, (x_train, y_train) in enumerate(train_db): with tf.GradientTape() as tape: # 记录梯度信息
h1 = tf.matmul(x_train, w1) + b1 # 记录神经网络乘加运算 h1 = tf.nn.relu(h1) y = tf.matmul(h1, w2) + b2 # 采用均方误差损失函数mse = mean(sum(y-out)^2) loss = tf.reduce_mean(tf.square(y_train - y)) # 计算loss对各个参数的梯度 variables = [w1, b1, w2, b2] grads = tape.gradient(loss, variables) # 实现梯度更新 # w1 = w1 - lr * w1_grad tape.gradient是自动求导结果与[w1, b1, w2, b2] 索引为0,1,2,3 w1.assign_sub(lr * grads[0]) b1.assign_sub(lr * grads[1]) w2.assign_sub(lr * grads[2]) b2.assign_sub(lr * grads[3]) # 每20个epoch,打印loss信息 if epoch % 20 == 0: print('epoch:', epoch, 'loss:', float(loss))预测部分
print(“predict”)
xx在-3到3之间以步长为0.01,yy在-3到3之间以步长0.01,生成间隔数值点
xx, yy = np.mgrid[-3:3:.1, -3:3:.1]
将xx , yy拉直,并合并配对为二维张量,生成二维坐标点
grid = np.c_[xx.ravel(), yy.ravel()] grid = tf.cast(grid, tf.float32)
将网格坐标点喂入神经网络,进行预测,probs为输出
probs = [] for x_test in grid: # 使用训练好的参数进行预测 h1 = tf.matmul([x_test], w1) + b1 h1 = tf.nn.relu(h1) y = tf.matmul(h1, w2) + b2 # y为预测结果 probs.append(y)
取第0列给x1,取第1列给x2
x1 = x_data[:, 0] x2 = x_data[:, 1]
probs的shape调整成xx的样子
probs = np.array(probs).reshape(xx.shape) plt.scatter(x1, x2, color=np.squeeze(Y_c)) #squeeze去掉纬度是1的纬度,相当于去掉[[‘red’],[”blue]],内层括号变为[‘red’,‘blue’]
把坐标xx yy和对应的值probs放入contour函数,给probs值为0.5的所有点上色 plt点show后 显示的是红蓝点的分界线
plt.contour(xx, yy, probs, levels=[.5]) plt.show()
读入红蓝点,画出分割线,不包含正则化
结果
在代码中加入对w的正则化后
# 添加l2正则化
loss_regularization = []
# tf.nn.l2_loss(w)=sum(w ** 2) / 2
loss_regularization.append(tf.nn.l2_loss(w1))
loss_regularization.append(tf.nn.l2_loss(w2))
# loss_regularization = tf.reduce_sum(tf.stack(loss_regularization))
loss_regularization = tf.reduce_sum(loss_regularization)
loss = loss_mse + 0.03 * loss_regularization #REGULARIZER = 0.03
结果变为:
可以看到正则化有效的缓解了过拟合
6 神经网络参数优化器
神经网络是基于连接的人工智能,当网络结构固定后,不同参数的选取对模型的表达力影响很大,优化器就是引导神经网络更新参数的工具。
更新参数的步骤
基本变量:待优化参数w, 损失函数loss, 学习率lr, 每次迭代一个batch, 他,表示当前迭代的总次数
- 计算t时刻损失函数对于当前参数的梯度:$g_t$ = $\frac{\partial loss}{\partial w_t}$
- 计算t时刻一阶栋梁$m_t$和二阶动量$V_t$
- 计算t时刻下降梯度:$\eta_t = l_r * m_t / \sqrt V_t$
- 计算t+1时刻参数:$w_{t+1} = w_t - \eta_t = w_t - l_r * m_t / \sqrt V_t$ 一阶动量:与梯度相关的函数 二阶动量:与梯度平方相关的函数 不同的优化器实质上只是定义了不同的一阶动量和二阶动量公式
常用的神经网络优化器
1.SGD(无momentum)随机梯度下降
也就是上一节的梯度更新公式
# 计算loss对各个参数的梯度
grads = tape.gradient(loss, [w1, b1])
# 实现梯度更新 w1 = w1 - lr * w1_grad b = b - lr * b_grad
w1.assign_sub(lr * grads[0]) # 参数w1自更新
b1.assign_sub(lr * grads[1]) # 参数b自更新
2.SGDM(含momentum的SGD)
在SGD基础上增加一阶动量
m_w, m_b = 0, 0 #w和b的一阶动量初始值都为0
beta = 0.9
# 计算loss对各个参数的梯度
grads = tape.gradient(loss, [w1, b1])
##########################################################################
# sgd-momentun
m_w = beta * m_w + (1 - beta) * grads[0]
m_b = beta * m_b + (1 - beta) * grads[1]
w1.assign_sub(lr * m_w)
b1.assign_sub(lr * m_b)
3.Adagrad
在SGD基础上增加二阶动量
v_w, v_b = 0, 0
# 计算loss对各个参数的梯度
grads = tape.gradient(loss, [w1, b1])
# adagrad
v_w += tf.square(grads[0])
v_b += tf.square(grads[1])
w1.assign_sub(lr * grads[0] / tf.sqrt(v_w))
b1.assign_sub(lr * grads[1] / tf.sqrt(v_b))
4.RMSprop
在SGD基础上增加二阶动量
V是各时刻梯度平方的指数滑动平均,使用指数滑动平均值计算,表征的是过去一段时间的平均值
v_w, v_b = 0, 0
beta = 0.9
grads = tape.gradient(loss, [w1, b1])
# rmsprop
v_w = beta * v_w + (1 - beta) * tf.square(grads[0])
v_b = beta * v_b + (1 - beta) * tf.square(grads[1])
w1.assign_sub(lr * grads[0] / tf.sqrt(v_w))
b1.assign_sub(lr * grads[1] / tf.sqrt(v_b))
5.Adam
同时结合SGDM一阶动量和RMSProp二阶动量
m_w, m_b = 0, 0 v_w, v_b = 0, 0 beta1, beta2 = 0.9, 0.999 delta_w, delta_b = 0, 0 global_step = 0 grads = tape.gradient(loss, [w1, b1]) m_w = beta1 * m_w + (1 - beta1) * grads[0] m_b = beta1 * m_b + (1 - beta1) * grads[1] v_w = beta2 * v_w + (1 - beta2) * tf.square(grads[0]) v_b = beta2 * v_b + (1 - beta2) * tf.square(grads[1])m_w_correction = m_w / (1 - tf.pow(beta1, int(global_step))) m_b_correction = m_b / (1 - tf.pow(beta1, int(global_step))) v_w_correction = v_w / (1 - tf.pow(beta2, int(global_step))) v_b_correction = v_b / (1 - tf.pow(beta2, int(global_step)))
w1.assign_sub(lr * m_w_correction / tf.sqrt(v_w_correction)) b1.assign_sub(lr * m_b_correction / tf.sqrt(v_b_correction))