Keras中MinMaxNorm约束的具体计算逻辑和方法解密

在这里插入图片描述

一、概述

在机器学习中,在训练阶段对权重参数可以设置约束。Keras中的Constraints 模块提供了不同的功能来设置层上的约束,在提供的约束类型中,MinMaxNorm约束是初看容易理解,但仔细研究却比较难理解。为此笔者花了好几天时间进行研究,在多次准备放弃又重新捡起,最后终于明白了相关逻辑。

二、MinMaxNorm官方文档的介绍

MinMaxNorm:将权重约束为指定的最小值和最大值之间的范数,映射到每个隐藏单元的权值的约束,使其范数在上下界之间。
语法:keras.constraints.MinMaxNorm(min_value=0.0, max_value=1.0, rate=1.0, axis=0),
其中rate为强制执行约束的比例,表示约束条件的强制程度。权重将被重新调整为 (1 - rate) * norm + rate * norm.clip(min_value, max_value)。 实际上,这意味着 rate = 1.0 代表严格执行约束,而 rate <1.0 意味着权重将在每一步重新调整缓慢移动到所需区间内的值。

官方文档关于约束的介绍请参考:https://keras-zh.readthedocs.io/constraints/

三、研究思路

为了研究上述变换公式,老猿写了个测试程序,其思路是:

  1. 构建一个4行4列的权重矩阵,初始化每个元素值为6;
  2. 用带MinMaxNorm约束和不带MinMaxNorm分别创建一个Dense层,其他层定义的参数完全相同,并使用相同的优化器和损失函数;
  3. 使用相同的全1数据来执行一次训练;
  4. 输出2种方式训练后的权重数据,带约束的输出权重记为W1,不带约束的记为W2;
  5. 使用EXCEL来研究怎么将W2变为W1。

四、程序代码及逻辑介绍

def testMinMaxNorm(rate,min,max):
#rate为约束比例,min和max为输出权重范数的上、下界
    import numpy as np
    import tensorflow as tf
    from keras import models,initializers
    from keras.layers import Dense
    from keras.constraints import MinMaxNorm

    # 获取 TensorFlow 的日志器
    logger = tf.get_logger()

    # 设置日志级别为 ERROR,这样就不会显示 INFO 级别的训练输出信息
    logger.setLevel('ERROR')

    #设置模型的输入数据个数为4
    shape = 4
       
    # 设置随初始权重为6
    my_init = initializers.Constant(value =6)
    #设置约束为MinMaxNorm
    my_constrain = MinMaxNorm(min_value = min, max_value = max, rate = rate, axis = 0) 
    
    #创建2个模型,一个带约束一个不带
    model1 = models.Sequential()
    model1.add(Dense(shape, activation='relu', kernel_initializer=my_init,kernel_constraint=my_constrain,input_shape=(shape,)))
    model2 = models.Sequential()
    model2.add(Dense(shape, activation='relu', kernel_initializer=my_init,input_shape=(shape,)))


    # 使用相同的优化器和损失函数编译模型
    model1.compile(optimizer='adam', loss='mean_squared_error')
    model2.compile(optimizer='adam', loss='mean_squared_error')

    
    # 创建一些全1的数据来训练
    x_train = np.ones((shape, shape))
    y_train = np.ones((shape, 1))

    # 使用相同训练数据对2个模型训练一个周期
    model1.fit(x_train, y_train, epochs=1, batch_size=4,verbose=0)
    model2.fit(x_train, y_train, epochs=1, batch_size=4,verbose=0)

    # 获取2种模式训练后的权重以及不带约束训练后的权重范数
    trained_weights1 = model1.layers[0].get_weights()[0]
    trained_weights2 = model2.layers[0].get_weights()[0]
    trained_norms2 = np.linalg.norm(trained_weights2, axis=1)  # 计算不带约束训练后的权重范数

    #输出相关数据
    print(f"rate={rate},min={min},max={max}: w1={trained_weights1[0][0]:.4f},w2={trained_weights2[0][0]:.4f},norms2={trained_norms2[0]:.4f}")
    

min=0
max = 5.0
testMinMaxNorm(0,min,max)
testMinMaxNorm(0.1,min,max)
testMinMaxNorm(0.2,min,max)
testMinMaxNorm(0.3,min,max)
testMinMaxNorm(1,min,max)

这个测试代码可以改变min、max以及rate参数值,实现输出不同rate和不同max、min值情况下带约束和不带约束的训练后权重。上面只列出了min=0、max=5以及不同rate取值的例子。执行后输出如下:

rate=0,min=0,max=5.0: w1=5.9990,w2=5.9990,norms2=11.9980
rate=0.1,min=0,max=5.0: w1=5.6491,w2=5.9990,norms2=11.9980
rate=0.2,min=0,max=5.0: w1=5.2992,w2=5.9990,norms2=11.9980
rate=0.3,min=0,max=5.0: w1=4.9493,w2=5.9990,norms2=11.9980
rate=1,min=0,max=5.0: w1=2.5000,w2=5.9990,norms2=11.9980

其中输出w1为直接使用keras带约束进行训练后输出权重矩阵的元素值,w2为不带约束训练后输出权重矩阵的元素值,norms2为不带约束训练后输出权重矩阵的L2范数元素值。

五、通过EXCEL来模拟MinMaxNorm的计算过程

下面表格用于模拟MinMaxNorm的计算过程:
在这里插入图片描述

上述表格:

  1. 前3列是函数的调用参数min、max和rate,分别对应MinMaxNorm的前3个参数;
  2. 第4列为上面程序输出的w1的值,第5列为程序输出的w2的值,第6列为程序输出的norms2(表格内记为N2)的值;
  3. 第7列为官方文档介绍的权重调整算法: (1 - rate) * norm + rate * norm.clip(min_value, max_value)计算出的权重L2范数调整值,记为NC,其中的norm为表格N2的值,clip函数就是当norm的值小于min_value时强制设置为min_value,大于max_value时强制设置为max_value, 在表格中,由于初始权重为6大于0,因此使用min函数取N2和max中的最小值;
  4. 最后一列为W2权重变为W1的权重调整公式,这个公式就是老猿研究多天才总结出来的计算公式,其计算方法为:W2*NC/N2。

从上述表格来看,MinMaxNorm对权重约束后的权重计算方式为:
无约束训练后输出权重W2*((1 - rate) * N2 + rate * N2.clip(min_value, max_value))/N2,其中N2为W2对应的L2范数。

也就是官方文档介绍的调整方法是对权重L2范数的调整,得到的值记为NC,然后对权重的调整为:
调整前的权重×NC/权重L2范数。

下面是按此方法计算的不同rate、max计算的相关值:
在这里插入图片描述
可以看到最后一列的值和第4列W1的值完全相等。

六、其他补充说明

用EXCEL模拟计算笔者之所以折腾了这么长时间,主要原因如下:

  1. 没有找到正确的计算公式,这个是主因,占了90%的时间,原因主要是官方文档的说明不正确;
  2. 在这个测试案例中fit(x_train, y_train, epochs=1, batch_size=4,verbose=0)的batch_size为4才会与excel计算的公式相同,而如果为小于4的值如1,训练后的数据与excel的公式计算值会存在差距,这是因为模型训练将使用全部的4个样本来计算损失函数并更新模型的权重,损失函数和权重更新都只执行一次,而如果batch_size为1,则会计算4次损失函数和4次更新,执行结果肯定有差异,而EXCEL就是按一次更新权重的方式来模拟的(取的是权重中的具体元素值)。

七、完善的样例代码

得到上述结论后,我们可以对上述样例代码进行调整,用程序实现2种不同方式的训练和处理,以判断2种方式结果是否一致。
下面是完整的代码:

def testMinMaxNorm(rate,min,max):
#rate为约束比例,min和max为输出权重范数的上、下界
    import numpy as np
    import tensorflow as tf
    from keras import models,initializers
    from keras.layers import Dense
    from keras.constraints import MinMaxNorm

    # 获取 TensorFlow 的日志器
    logger = tf.get_logger()

    # 设置日志级别为 ERROR,这样就不会显示 INFO 级别的训练输出信息
    logger.setLevel('ERROR')

    #设置模型的输入数据个数为4
    shape = 4
       
    # 设置随初始权重为6
    my_init = initializers.Constant(value =6)
    #设置约束为MinMaxNorm
    my_constrain = MinMaxNorm(min_value = min, max_value = max, rate = rate, axis = 0) 
    
    #创建2个模型,一个带约束一个不带
    model1 = models.Sequential()
    model1.add(Dense(shape, activation='relu', kernel_initializer=my_init,kernel_constraint=my_constrain,input_shape=(shape,)))
    model2 = models.Sequential()
    model2.add(Dense(shape, activation='relu', kernel_initializer=my_init,input_shape=(shape,)))


    # 使用相同的优化器和损失函数编译模型
    model1.compile(optimizer='adam', loss='mean_squared_error')
    model2.compile(optimizer='adam', loss='mean_squared_error')
    w0 = model1.layers[0].get_weights()[0]
        
    # 创建一些全1的数据来训练
    x_train = np.ones((shape, shape))
    y_train = np.ones((shape, 1))

    # 使用相同训练数据对2个模型训练一个周期
    model1.fit(x_train, y_train, epochs=1, batch_size=4,verbose=0)
    model2.fit(x_train, y_train, epochs=1, batch_size=4,verbose=0)

    # 获取2种模式训练后的权重以及不带约束训练后的权重范数
    trained_weights1 = model1.layers[0].get_weights()[0]
    trained_weights2 = model2.layers[0].get_weights()[0]
    trained_norms2 = np.linalg.norm(trained_weights2, axis=1)  # 计算不带约束训练后的权重范数

    #用单独的矩阵运算模拟带MinMaxNorm的Keras训练方式
    norm2_constraint =  trained_norms2*(1-rate)+trained_norms2.clip(min,max)*rate
    weights_constraint = trained_weights2*(norm2_constraint[0]/trained_norms2[0])

    #比较两种方式结果是否相同
    if(weights_constraint.all()==trained_weights1.all()):
        print("两种计算方法的权重相等")
    else:
        print("两种计算方法的权重不同")
    #输出相关数据
    print(f"rate={rate},min={min},max={max}: w1={trained_weights1[0][0]:.4f},w2={trained_weights2[0][0]:.4f},norms2={trained_norms2[0]:.4f}")
    


min=0
max = 5.0
testMinMaxNorm(0,min,max)
testMinMaxNorm(0.1,min,max)
testMinMaxNorm(0.8,min,max)
testMinMaxNorm(1,min,max)

上述代码相比前面代码就是在函数中增加了“用单独的矩阵运算模拟带MinMaxNorm的Keras训练方式”注释后面的几行。
下面是运行输出数据:

两种计算方法的权重相等
rate=0,min=0,max=5.0: w1=5.9990,w2=5.9990,norms2=11.9980
两种计算方法的权重相等
rate=0.1,min=0,max=5.0: w1=5.6491,w2=5.9990,norms2=11.9980
两种计算方法的权重相等
rate=0.8,min=0,max=5.0: w1=3.1998,w2=5.9990,norms2=11.9980
两种计算方法的权重相等
rate=1,min=0,max=5.0: w1=2.5000,w2=5.9990,norms2=11.9980

八、小结

本文介绍了Keras中MinMaxNorm约束的具体计算逻辑,并通过示例代码和表格来说明了逻辑的具体实现,从相关计算逻辑来看,官方文档介绍的计算方法是不完整的。

更多人工智能知识学习请关注专栏《零基础机器学习入门》后续的文章。

更多人工智能知识学习过程中可能遇到的疑难问题及解决办法请关注专栏《机器学习疑难问题集》后续的文章。

写博不易,敬请支持:

如果阅读本文于您有所获,敬请点赞、评论、收藏,谢谢大家的支持!

关于老猿的付费专栏

  1. 付费专栏《https://blog.csdn.net/laoyuanpython/category_9607725.html 使用PyQt开发图形界面Python应用》专门介绍基于Python的PyQt图形界面开发基础教程,对应文章目录为《 https://blog.csdn.net/LaoYuanPython/article/details/107580932 使用PyQt开发图形界面Python应用专栏目录》;
  2. 付费专栏《https://blog.csdn.net/laoyuanpython/category_10232926.html moviepy音视频开发专栏 )详细介绍moviepy音视频剪辑合成处理的类相关方法及使用相关方法进行相关剪辑合成场景的处理,对应文章目录为《https://blog.csdn.net/LaoYuanPython/article/details/107574583 moviepy音视频开发专栏文章目录》;
  3. 付费专栏《https://blog.csdn.net/laoyuanpython/category_10581071.html OpenCV-Python初学者疑难问题集》为《https://blog.csdn.net/laoyuanpython/category_9979286.html OpenCV-Python图形图像处理 》的伴生专栏,是笔者对OpenCV-Python图形图像处理学习中遇到的一些问题个人感悟的整合,相关资料基本上都是老猿反复研究的成果,有助于OpenCV-Python初学者比较深入地理解OpenCV,对应文章目录为《https://blog.csdn.net/LaoYuanPython/article/details/109713407 OpenCV-Python初学者疑难问题集专栏目录
  4. 付费专栏《https://blog.csdn.net/laoyuanpython/category_10762553.html Python爬虫入门 》站在一个互联网前端开发小白的角度介绍爬虫开发应知应会内容,包括爬虫入门的基础知识,以及爬取CSDN文章信息、博主信息、给文章点赞、评论等实战内容。

前两个专栏都适合有一定Python基础但无相关知识的小白读者学习,第三个专栏请大家结合《https://blog.csdn.net/laoyuanpython/category_9979286.html OpenCV-Python图形图像处理 》的学习使用。

对于缺乏Python基础的同仁,可以通过老猿的免费专栏《https://blog.csdn.net/laoyuanpython/category_9831699.html 专栏:Python基础教程目录)从零开始学习Python。

如果有兴趣也愿意支持老猿的读者,欢迎购买付费专栏。

老猿Python,跟老猿学Python!

☞ ░ 前往老猿Python博文目录 https://blog.csdn.net/LaoYuanPython
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LaoYuanPython

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值