人工智能实践——激活函数损失函数优化器的代码实现

激活函数:

tf.nn.sigmoid或者tf.sigmoid
tf.math.tanh 或者tf.keras.activations.tanh
tf.nn.relu或者tf.keras.activations.relu
前者适用于无keras包,后者在keras搭建中使用

损失函数:

假设有n个输出节点
MSE:
y_onehot = tf.one_hot(y_train, depth=n)
loss = tf.reduce_mean(tf.square(y_onehot - y))
CE:
y_onehot = tf.one_hot(y_train, depth=n)
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(y_onehot, y))

优化器:

GDO

Tf.train.GradientDescentOptimizer(learning_rate, use_locking=False,
name='GradientDescent)
也就是我们最朴素的方法
v[t] = -ε*▽f(θ[t])
Θ[t+1] = θ[t] +v[t]

SGD

SGD全名 stochastic gradient descent, 即随机梯度下降
即从样本中抽取子样本进行梯度训练

train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(32)
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(10)

batch()即划分为子样本进行训练

缺点:
1:由于是抽取,因此不可避免的,得到的梯度肯定有误差.因此学习速率需要逐渐减小.否则模型无法收敛 2:因为误差,所以每一次迭代的梯度受抽样的影响比较大,也就是说梯度含有比较大的噪声,不能很好的反映真实梯度.
优化方法:采用学习率衰减
learning_rate = learning_rate_base * learning_rate_decay ** (epoch / learning_rate_step)

最常见的标准化方法就是Z标准化,也是SPSS中最为常用的标准化方法,spss默认的标准化方法就是z-score标准化。

也叫标准差标准化,这种方法给予原始数据的均值(mean)和标准差(standard deviation)进行数据的标准化。

经过处理的数据符合标准正态分布,即均值为0,标准差为1,其转化函数为:

x* = (x - μ ) / σ

其中μ为所有样本数据的均值,σ为所有样本数据的标准差。
z-score标准化方法适用于属性A的最大值和最小值未知的情况,或有超出取值范围的离群数据的情况。该种归一化方式要求原始数据的分布可以近似为高斯分布,否则归一化的效果会变得很糟糕。

momentum-SGD

SGD方法的一个缺点是,其更新方向完全依赖于当前的batch,因而其更新十分不稳定,每次迭代计算的梯度含有比较大的噪音。解决这一问题的一个简单的做法便是引入momentum。

momentum即动量,它模拟的是物体运动时的惯性,即更新的时候在一定程度上保留之前更新的方向,同时利用当前batch的梯度微调最终的更新方向。这样一来,可以在一定程度上增加稳定性,从而学习地更快,并且还有一定摆脱局部最优的能力。

特点:

  • 前后梯度方向一致时,能够加速学习
  • 前后梯度方向不一致时,能够抑制震荡

beta = 0.9
m_w = beta * m_w + (1 - beta) * grads[0]
m_b = beta * m_b + (1 - beta) * grads[1]
w1.assign_sub(learning_rate * m_w)
b1.assign_sub(learning_rate * m_b)

AdaGrad

优点: 能够实现学习率的自动更改。如果这次梯度大,那么学习速率衰减的就快一些;如果这次梯度小,那么学习速率衰减的慢一些

缺点: 任然要设置一个变量ϵ ,经验表明,在普通算法中也许效果不错,会使得学习率单调递减至0,可能会使得训练过程提前结束,即便后续还有数据也无法学到必要的知识。

v_w += tf.square(grads[0])
v_b += tf.square(grads[1])
w1.assign_sub(learning_rate * grads[0] / tf.sqrt(v_w))
b1.assign_sub(learning_rate * grads[1] / tf.sqrt(v_b))

从上面的代码可以看到 grads[0] / tf.sqrt(v_w) v_w += tf.square(grads[0])
从这数学式中我们可以看到 相当于 grads[0]/ tf.square(grads[0])
即若**grads[i]**过大,则我们要缩小梯度
grads过小,我们要放大梯度
当然如果把 learning_rate/tf.sqrt(v_w) 整体看作学习率,也说明若如果这次梯度大,那么学习速率衰减的就快一些;如果这次梯度小,那么学习速率衰减的慢一些

AdaDelta

由于AdaGrad单调递减的学习率变化过于激进,我们考虑一个改变二阶动量计算方法的策略:不累积全部历史梯度,而只关注过去一段时间窗口的下降梯度。这也就是AdaDelta名称中Delta的来历。

修改的思路很简单。前面我们讲到,指数移动平均值大约就是过去一段时间的平均值,因此我们用这一方法来计算二阶累积动量:这就避免了二阶动量持续累积、导致训练过程提前结束的问题了。
beta = 0.9
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(learning_rate * grads[0] / tf.sqrt(v_w))
b1.assign_sub(learning_rate * grads[1] / tf.sqrt(v_b))

我们可以看到,对于历史时间的梯度,每次都会*beta,这会导致越远历史占比越小,近似达到动态窗口的目的。

Adam

Adam(Adaptive Moment Estimation)本质上是带有动量项的RMSprop,它利用梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习率。Adam的优点主要在于经过偏置校正后,每一次迭代学习率都有个确定范围,使得参数比较平稳。
通俗来讲:
是前述方法的集大成者。我们看到,SGD-M在SGD基础上增加了一阶动量,AdaGrad和AdaDelta在SGD基础上增加了二阶动量。把一阶动量和二阶动量都用起来,就是Adam了——Adaptive +Momentum。

global_step = global_step.assign_add(1)
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(learning_rate * m_w_correction / tf.sqrt(v_w_correction))
b1.assign_sub(learning_rate * m_b_correction / tf.sqrt(v_b_correction))

测试

将鸢尾花数据集合带入测试,得到结果如下:【准确率都是100%】
SGD:
loss: 0.12647321820259094
time: 12.007081508636475

momentum-SGD:
loss: 0.12340985238552094
time: 12.843583583831787

AdaGrad:
loss: 0.10842594504356384
time: 12.665419578552246

AdaDelta:
loss: 0.04672025144100189
time: 13.282380819320679

Adam:
loss: 0.04928009212017059
time: 15.38596796989441
可以看到AdaDelta和Adam表现最好,但是时间也是最多。
所以在不同时候要选用不同的方法
下面这篇博文也许能告诉你为什么有些时候我们仍要用SGD,类似于老式相机和美颜手机的区别

https://blog.csdn.net/jiachen0212/article/details/80086926

实现代码

import tensorflow as tf
import os
from sklearn import datasets
from matplotlib import pyplot as plt
import numpy as np
import time
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'  # 只显示error,不显示其他信息
def standardize(train):
    # 数据标准化(标准正态分布)
    x_data = train.T
    for i in range(4):
        x_data[i] = (x_data[i] - np.mean(x_data[i])) / np.std(x_data[i])
    return x_data.T

x_data = datasets.load_iris().data
y_data = datasets.load_iris().target

x_data = standardize(x_data)

# 随机打乱数据
np.random.seed(116)
np.random.shuffle(x_data)
np.random.seed(116)
np.random.shuffle(y_data)

x_train = x_data[:-30]
y_train = y_data[:-30]
x_test = x_data[-30:]
y_test = y_data[-30:]

x_train = tf.cast(x_train, tf.float32)
x_test = tf.cast(x_test, tf.float32)


# from_tensor_slices函数切分传入的 Tensor 的第一个维度,生成相应的 dataset
train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(32)
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(10)

# seed: 随机数种子,是一个整数,当设置之后,每次生成的随机数都一样
w1 = tf.Variable(tf.random.truncated_normal([4, 3], stddev=0.1, seed=1))
b1 = tf.Variable(tf.random.truncated_normal([3], stddev=0.1, seed=1))

#SGD的参数
# learning_rate_step = 10
# learning_rate_decay = 0.8
# learning_rate_base = 1
# global_step = epoch = 500
#momentum-SGD的参数
# learning_rate_step = 10
# learning_rate_decay = 0.8
# learning_rate_base = 1
# global_step = epoch = 500
# m_w, m_b = 0, 0
#adagrad的参数
# learning_rate_step = 10
# learning_rate_decay = 0.8
# learning_rate_base = 1
# v_w, v_b = 0, 0
# global_step = epoch = 500
#adadelta的参数
# learning_rate_step = 10
# learning_rate_decay = 0.8
# learning_rate_base = 1
# v_w, v_b = 0, 0
# global_step = epoch = 500
#adam的参数
learning_rate_step = 10
learning_rate_decay = 0.8
learning_rate_base = 1
delta_w, delta_b = 0, 0
beta = 0.9
global_step = tf.Variable(0, trainable=False)
m_w, m_b = 0, 0
v_w, v_b = 0, 0
beta1, beta2 = 0.9, 0.999
epoch = 500

train_loss_results = []
test_acc = []
lr = []
loss_all = 0

now_time = time.time()
for epoch in range(epoch):
    learning_rate = learning_rate_base * learning_rate_decay ** (epoch / learning_rate_step)
    lr.append(learning_rate)
    for step, (x_train, y_train) in enumerate(train_db):

        with tf.GradientTape() as tape:
            y = tf.matmul(x_train, w1) + b1
            # y = tf.math.tanh(y)
            y_onehot = tf.one_hot(y_train, depth=3)
            # loss = tf.reduce_mean(tf.square(y_onehot - y))
            loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(y_onehot, y))
            loss_all += loss.numpy()
        grads = tape.gradient(loss, [w1, b1])

        #SGD
        # w1.assign_sub(learning_rate * grads[0])
        # b1.assign_sub(learning_rate * grads[1])

        #momentum-SGD
        # beta = 0.9
        # m_w = beta * m_w + (1 - beta) * grads[0]
        # m_b = beta * m_b + (1 - beta) * grads[1]
        # w1.assign_sub(learning_rate * m_w)
        # b1.assign_sub(learning_rate * m_b)

        # adagrad
        # v_w += tf.square(grads[0])
        # v_b += tf.square(grads[1])
        # w1.assign_sub(learning_rate * grads[0] / tf.sqrt(v_w))
        # b1.assign_sub(learning_rate * grads[1] / tf.sqrt(v_b))

        # adadelta
        # beta = 0.9
        # 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(learning_rate * grads[0] / tf.sqrt(v_w))
        # b1.assign_sub(learning_rate * grads[1] / tf.sqrt(v_b))

        # adam
        global_step = global_step.assign_add(1)
        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(learning_rate * m_w_correction / tf.sqrt(v_w_correction))
        b1.assign_sub(learning_rate * m_b_correction / tf.sqrt(v_b_correction))

        if step % 10 == 0:
            print("step=", step, 'loss:', float(loss))
            print("lr=", learning_rate)

    train_loss_results.append(loss_all / 3)
    loss_all = 0

    # test(做测试)
    total_correct, total_number = 0, 0
    for step, (x_test, y_test) in enumerate(test_db):
        y = tf.matmul(x_test, w1) + b1
        y = tf.nn.sigmoid(y)

        pred = tf.argmax(y, axis=1)

        # 因为pred的dtype为int64,在计算correct时会出错,所以需要将它转化为int32
        pred = tf.cast(pred, dtype=tf.int32)
        correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32)
        correct = tf.reduce_sum(correct)
        total_correct += int(correct)
        total_number += x_test.shape[0]
    acc = total_correct / total_number
    test_acc.append(acc)
    print("test_acc:", acc)
    print("---------------------")
total_time = time.time() - now_time
print("total_time", total_time)

# 绘制 loss 曲线
plt.title('Loss Function Curve')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.plot(train_loss_results, label="$Loss$")
plt.legend()
plt.show()

# 绘制 Accuracy 曲线
plt.title('Acc Curve')
plt.xlabel('Epoch')
plt.ylabel('Acc')
plt.plot(test_acc, label="$Accuracy$")
plt.legend()
plt.show()

# 绘制 Learning_rate 曲线
plt.title('Learning Rate Curve')
plt.xlabel('Global steps')
plt.ylabel('Learning rate')
plt.plot(range(epoch + 1), lr, label="$lr$")
plt.legend()
plt.show()

你可能感兴趣的:(人工智能实践,人工智能实践)