《昇思25天学习打卡营第7天|函数式自动微分》


今日所学:

今天我学习了神经网络训练的核心原理,主要是反向传播算法。这个过程包括将模型预测值(logits)和正确标签(label)输入到损失函数(loss function)中计算loss,然后通过反向传播算法计算梯度(gradients),最终更新模型参数(parameters)。自动微分技术能够在某点计算可导函数的导数值,是反向传播算法的一个广义实现。它的主要作用是将复杂的数学运算分解为一系列简单的基本运算,从而屏蔽了大量求导的细节和过程,显著降低了使用深度学习框架的门槛。

MindSpore采用函数式自动微分的设计理念,提供了更接近数学语义的自动微分接口,例如grad和value_and_grad。为了更好地理解这些概念,我还学习了如何使用一个简单的单层线性变换模型进行实践。


一、函数与计算图

MindSpore之前的还不熟悉的相关内容可以见:《昇思25天学习打卡营第1天|基本介绍》

计算图是一种借助图论来描绘数学函数的一种方法,同时也是深度学习框架用以表达神经网络模型的通用方式。以下,我们将以此计算图为基础,来构建计算函数和神经网络:
在这里插入图片描述
在本节所学的这个模型中,𝑥为输入,𝑦为正确值,𝑤和𝑏是我们需要优化的参数,根据计算图描述的计算过程,构造计算函数,执行计算函数,可以获得计算的loss值,代码与结果如下所示:

x = ops.ones(5, mindspore.float32)  # input tensor
y = ops.zeros(3, mindspore.float32)  # expected output
w = Parameter(Tensor(np.random.randn(5, 3), mindspore.float32), name='w') # weight
b = Parameter(Tensor(np.random.randn(3,), mindspore.float32), name='b') # bias

def function(x, y, w, b):
    z = ops.matmul(x, w) + b
    loss = ops.binary_cross_entropy_with_logits(z, y, ops.ones_like(z), ops.ones_like(z))
    return loss

loss = function(x, y, w, b)
print(loss)

结果如下:

Tensor(shape=[], dtype=Float32, value= 0.914285)

二、微分函数与梯度计算

在之后学习内容中为了优化模型参数,需要求参数对loss的导数:

∂loss∂𝑤

∂loss∂𝑏

此时我们调用mindspore.grad函数,来获得function的微分函数。其中grad函数的两个入参,分别为fn(待求导的函数)与grad_position(指定求导输入位置的索引),代码如下:

grad_fn = mindspore.grad(function, (2, 3))
grads = grad_fn(x, y, w, b)
print(grads)

结果如下:

在这里插入图片描述

使用grad获得微分函数是一种函数变换,即输入为函数,输出也为函数。

三、Stop Gradient

在常规的情况下,求导操作主要是计算loss相对于参数的导数,由此,函数的输出仅有loss一项。然而,当我们期望函数有多项输出时,微分函数将会计算所有输出项相对于参数的导数。在这种情况下,如果我们希望实现特定输出项的梯度截断,或者需要消除某个Tensor对梯度的影响,那么我们将需要使用Stop Gradient操作。在这里,我们会将function改造成同时输出loss和z的function_with_logits,并获取微分函数以供执行。

如果想要屏蔽掉z对梯度的影响,即仍只求参数对loss的导数,可以使用ops.stop_gradient接口,将梯度在此处截断。

代码如下:

def function_with_logits(x, y, w, b):
    z = ops.matmul(x, w) + b
    loss = ops.binary_cross_entropy_with_logits(z, y, ops.ones_like(z), ops.ones_like(z))
    return loss, z

grad_fn = mindspore.grad(function_with_logits, (2, 3))
grads = grad_fn(x, y, w, b)
print(grads)

def function_stop_gradient(x, y, w, b):
    z = ops.matmul(x, w) + b
    loss = ops.binary_cross_entropy_with_logits(z, y, ops.ones_like(z), ops.ones_like(z))
    return loss, ops.stop_gradient(z)

grad_fn = mindspore.grad(function_stop_gradient, (2, 3))
grads = grad_fn(x, y, w, b)
print(grads)

截断前结果:
在这里插入图片描述

截断后结果:

在这里插入图片描述

四、Auxiliary data

我深入理解了Auxiliary data(辅助数据)的概念和应用。我明白了辅助数据其实就是函数的非主要输出项。在实际应用中,我们常将函数的主要输出设为loss,而其它的所有输出则被视为辅助数据。对于grad和value_and_grad函数,我享受到了has_aux参数带来的便利。当将其设为True,它就能自动实现之前需要手动添加的stop_gradient操作。这种设计巧妙地使我在返回辅助数据的同时,不受梯度计算的任何影响。

在后续的实践中,我继续使用了function_with_logits,并设置了has_aux为True进行操作。整个过程顺畅无比,加深了我对这一主题的理解。我会持续探索,并将这些知识应用到更广泛的场景中去:

grad_fn = mindspore.grad(function_with_logits, (2, 3), has_aux=True)

grads, (z,) = grad_fn(x, y, w, b)
print(grads, z)

结果如下:

在这里插入图片描述

五、神经网络梯度计算

前面章节已经讲述了网络构建,还不了解的可见这篇文章:《昇思25天学习打卡营第6天|网络构建》

接下来,我深入了解了如何通过Cell去构造神经网络,以及利用函数式自动微分来实现反向传播的过程。我首先继承了nn.Cell来构建单层线性变换神经网络。有意思的是,这个过程中我直接使用了之前的 𝑤 和 𝑏 来作为模型参数。这种做法完全打破了我早前的理解,让我认识到原来我们可以直接使用现有的参数以节约时间和计算资源。我将这些参数用mindspore.Parameter包装起来作为内部属性,并在construct内实现了与之前一样的Tensor操作:

# Define model
class Network(nn.Cell):
    def __init__(self):
        super().__init__()
        self.w = w
        self.b = b

    def construct(self, x):
        z = ops.matmul(x, self.w) + self.b
        return z
        
# Instantiate model
model = Network()
# Instantiate loss function
loss_fn = nn.BCEWithLogitsLoss()

# Define forward function
def forward_fn(x, y):
    z = model(x)
    loss = loss_fn(z, y)
    return loss
    
grad_fn = mindspore.value_and_grad(forward_fn, None, weights=model.trainable_params())

loss, grads = grad_fn(x, y)
print(grads)

结果如下:
在这里插入图片描述

可以看出,执行微分函数后的梯度值和前文function求得的梯度值一致。

在这里插入图片描述

总结

在今天的学习中,我深入理解了神经网络训练的核心原理,包括反向传播算法和如何利用自动微分技术来计算梯度并更新模型参数。我也学习了如何使用MindSpore框架的函数式自动微分接口来进行实践,并利用计算图进行模型参数优化。此外,我理解了Stop Gradient操作和辅助数据对梯度计算的影响,以及如何在神经网络的梯度计算中有效利用它们。通过理论学习和实践操作,我对这些概念有了更深入的理解,期待在明天的学习中继续进步。

  • 14
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 以下是一个简单的微信小程序制作学习计划打卡记录页面的代码。 在 wxml 文件中,我们可以设置页面的布局和样,以及与用户进行交互的组件,例如按钮、输入框等。 ```html <view class="container"> <view class="header"> <text class="title">学习计划打卡记录</text> </view> <view class="card"> <view class="card-heading"> <text class="card-title">Day 1</text> <text class="card-date">2021-01-01</text> </view> <view class="card-body"> <text class="card-label">学习时间:</text> <input class="card-input" type="number" placeholder="请输入学习时间(单位:小时)" /> </view> <view class="card-footer"> <button class="card-button">提交</button> </view> </view> <!-- Day 2、Day 3 等的卡片同上 --> </view> ``` 在 wxss 文件中,我们可以设置页面的样,例如字体、颜色、边距等。 ```css .container { padding: 20rpx; } .header { text-align: center; margin-bottom: 20rpx; } .title { font-size: 36rpx; font-weight: bold; color: #333333; } .card { background-color: #ffffff; border-radius: 10rpx; box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1); margin-bottom: 20rpx; } .card-heading { display: flex; justify-content: space-between; align-items: center; padding: 20rpx; border-bottom: 1rpx solid #f2f2f2; } .card-title { font-size: 28rpx; font-weight: bold; color: #333333; } .card-date { font-size: 24rpx; color: #999999; } .card-body { display: flex; align-items: center; padding: 20rpx; } .card-label { font-size: 28rpx; color: #333333; } .card-input { flex: 1; margin-left: 20rpx; font-size: 28rpx; color: #999999; border: none; } .card-footer { text-align: center; padding: 20rpx; } .card-button { font-size: 28rpx; color: #ffffff; background-color: #007aff; border-radius: 5rpx; padding: 10rpx 20rpx; } ``` 在 js 文件中,我们可以编写页面的逻辑代码。 ```javascript Page({ data: { days: [ { title: 'Day 1', date: '2021-01-01', time: null }, { title: 'Day 2', date: '2021-01-02', time: null }, { title: 'Day 3', date: '2021-01-03', time: null } // Day 4、Day 5 等的数据同上 ] }, handleInput: function(event) { // 获取输入框的值 let index = event.currentTarget.dataset.index; let value = event.detail.value; // 更新数据 let days = this.data.days; days[index].time = value; this.setData({ days: days }); }, handleSubmit: function(event) { // 获取提交按钮的索引 let index = event.currentTarget.dataset.index; // 获取对应的数据 let days = this.data.days; let day = days[index]; // 判断是否填写了学习时间 if (day.time === null || day.time === '') { wx.showToast({ title: '请填写学习时间', icon: 'none' }); return; } // 提交数据 wx.cloud.callFunction({ name: 'submit', data: { date: day.date, time: day.time }, success: res => { wx.showToast({ title: '提交成功' }); }, fail: err => { wx.showToast({ title: '提交失败', icon: 'none' }); } }); } }) ``` 以上代码是一个简单的微信小程序制作学习计划打卡记录页面的示例,仅供参考。具体实现方可能因个人需求而异。 ### 回答2: 微信小程序制作学习计划打卡记录页面包含前几学习时间的全部代码如下: 首先,在小程序中创建一个page,命名为"studyRecord",在studyRecord.json文件中进行配置,设置"navigationBarTitleText"为"学习打卡",并设置"usingComponents"引入相关组件: ``` { "navigationBarTitleText": "学习打卡", "usingComponents": {} } ``` 接下来,在studyRecord.wxml文件中编写页面结构,包括一个日期选择器和一个列表用于展示打卡记录: ``` <view class="container"> <view class="header"> <picker mode="date" bindchange="dateChange"> <view class="date-picker">{{ currentDate }}</view> </picker> </view> <view class="record-list"> <block wx:for="{{ studyRecords }}" wx:key="index"> <view class="record-item"> <view class="item-date">{{ item.date }}</view> <view class="item-duration">{{ item.duration }}</view> </view> </block> </view> </view> ``` 我们在studyRecord.js文件中定义相关的事件处理函数和数据: ``` Page({ data: { currentDate: '', // 当前选择的日期 studyRecords: [] // 学习打卡记录 }, onLoad: function () { // 获取最近几学习打卡记录 this.getStudyRecords(); }, dateChange: function (event) { this.setData({ currentDate: event.detail.value }); // 根据选择日期的变化更新学习打卡记录 this.getStudyRecords(); }, getStudyRecords: function () { // 根据当前日期获取学习打卡记录,假设获取到的数据格为[{ date: '2022/01/01', duration: '2小时' }, ...] // 可以通过调用接口或其他方获取数据 const currentDate = this.data.currentDate; const studyRecords = this.getStudyRecordsByDate(currentDate); this.setData({ studyRecords: studyRecords }); }, getStudyRecordsByDate: function (date) { // 根据日期获取学习打卡记录的逻辑实现 // ... return studyRecords; // 返回按日期查询到的学习打卡记录 } }) ``` 在studyRecord.wxss文件中定义样: ``` .container { padding: 10px; } .header { margin-bottom: 10px; } .date-picker { font-size: 18px; color: #333; padding: 10px; background-color: #f5f5f5; border-radius: 4px; text-align: center; } .record-list { background-color: #fff; border-radius: 4px; } .record-item { padding: 10px; border-bottom: solid 1px #eee; } .item-date { font-size: 14px; color: #666; } .item-duration { font-size: 16px; color: #333; } ``` 这样,一个包含前几学习时间的微信小程序制作学习计划打卡记录页面的代码就完成了。 ### 回答3: 要制作微信小程序的学习计划打卡记录页面,可以按照以下步骤进行: 1. 首先,需要在微信开发者工具中创建一个新的小程序项目,并在app.json文件中配置页面路由信息。 2. 在项目的根目录下创建一个新的文件夹,用于存放页面相关的文件,比如study-record文件夹。 3. 在study-record文件夹中创建一个study-record.wxml文件用于编写页面的结构。 4. 在study-record文件夹中创建一个study-record.wxss文件用于编写页面的样。 5. 在study-record文件夹中创建一个study-record.js文件用于编写页面的逻辑代码。 6. 在study-record.js中定义一个数据对象,用于存储前几学习时间。可以使用数组来存储每一学习时间,比如每个元素都是一个包含日期和学习时间的对象。 7. 在study-record.js中编写一个函数来获取前几学习时间。可以使用Date对象和相关的方法来计算前几的日期,然后根据日期从数据对象中获取对应的学习时间。 8. 在study-record.js中编写一个函数来更新学习时间。可以通过用户输入的方来更新某一学习时间,并将更新后的数据保存到数据对象中。 9. 在study-record.wxml中使用wx:for循环来遍历数据对象中的学习时间,并将日期和学习时间显示在页面上。 10. 在study-record.wxml中添加一个按钮,用于触发更新学习时间的函数。 11. 在study-record.js中监听按钮的点击事件,并在点击时触发更新学习时间的函数。 12. 在study-record.wxss中设置页面的样,比如学习时间的字体大小、颜色等。 通过以上步骤,就可以完成微信小程序的学习计划打卡记录页面的制作。在页面中包含了前几学习时间,并提供了更新学习时间的功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

邓永豪

打赏一下,好运来敲门!

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

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

打赏作者

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

抵扣说明:

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

余额充值