给出一些相关的前置知识,我们从对象真正是怎么产生的开始。


一、先严格区分:定义类,不等于创建对象

看:

class BCPolicy(nn.Module):    ...

这句话是在:

定义一个新的类 BCPolicy。

它相当于告诉 Python:

以后我要有一种叫做 BCPolicy 的对象,这种对象应该按照这里定义的规则工作。

但此时还没有真正创建一个 BCPolicy 模型实例。

也就是说:

class BCPolicy(nn.Module):

不是:

创建一个模型。

而只是:

定义“模型这一类东西应该长什么样”。

真正创建对象是在:

model = BCPolicy()

这里才产生了一个真正的:

BCPolicy 对象

并把这个对象的引用保存到:

model

里面。

所以一定区分:

class BCPolicy(nn.Module):

是:

类定义

而:

model = BCPolicy()

才是:

创建实例对象


二、(nn.Module) 到底是什么意思?

看:

class BCPolicy(nn.Module):

括号里的:

nn.Module

不是函数参数。

它表示:

BCPolicy 继承 nn.Module。

所以关系是:

nn.Module
    ↑
BCPolicy

可以把:

nn.Module

叫做:

父类 / 基类

把:

BCPolicy

叫:

子类 / 派生类

意思是:

BCPolicy 在自己定义新功能的同时,可以继承 nn.Module 提供的能力。


三、为什么 PyTorch 模型一般都继承 nn.Module?

因为:

nn.Module

给神经网络对象准备好了大量基础设施。

例如后面我们会不断使用:

model.parameters()model.to(device)model.train()model.eval()model.state_dict()model.load_state_dict(...)

包括:

model(obs)

这种调用方式背后的很多机制,也来自 nn.Module。

所以你可以暂时理解:

nn.Module
=
PyTorch 神经网络模块的基础骨架

而我们写:

class BCPolicy(nn.Module):

相当于:

我要在 PyTorch 已经提供好的神经网络骨架上,定义我自己的 BC Policy。


四、现在看第二行:def __init__(self):

def __init__(self):

它只是:

定义这个类的初始化方法。

注意又是“定义”。

这一行本身也没有创建对象。

真正什么时候执行?

当后面:

model = BCPolicy()

创建 BCPolicy 对象时,它会参与对象初始化过程。

先用一个普通 Python 类看:

class Person:    def __init__(self, name):        self.name = name

然后:

p = Person("Tom")

最终:

p.name

就是:

Tom

所以:

__init__()

的主要作用可以先理解成:

当对象创建时,初始化这个对象内部需要保存的状态。


五、但是这里要比“自动调用 init”再严谨一点

很多教程会简单说:

创建对象的时候 Python 自动调用 __init__()。

作为初学理解没问题,但我们现在可以稍微严谨一点。

你写:

model = BCPolicy()

大致经历:

BCPolicy()
↓
创建一个新的 BCPolicy 实例
↓
初始化这个实例
↓
返回实例
↓
model 指向这个实例

其中 Python 对象创建机制还涉及:

__new__()

和:

__init__()

可以粗略看成:

__new__()
负责“创建对象”

__init__()
负责“初始化已经创建出来的对象”

当前阶段不用深入 __new__()。

你只需要避免以后说成:

__init__() 创建了对象。

更准确的是:

BCPolicy() 触发对象创建流程,__init__() 负责初始化这个已经产生的实例。


六、self 到底是谁?

现在:

class BCPolicy(nn.Module):    def __init__(self):        ...

假设后面:

model = BCPolicy()

那么在这次初始化过程中:

self

指向的就是:

这次刚创建出来的那个 BCPolicy 实例。

也就是最终由:

model

引用的那个对象。

所以如果写:

self.fc = nn.Linear(10, 7)

意思不是:

给 BCPolicy 这个类统一创建一个 fc。

而是:

给当前这个具体的 BCPolicy 对象保存一个名为 fc 的成员。

例如:

model1 = BCPolicy()model2 = BCPolicy()

这是两个不同对象。

概念上:

BCPolicy 类
   │
   ├── model1 对象
   │     └── 自己的 fc
   │
   └── model2 对象
         └── 自己的 fc

这点和“类定义”和“真正对象”一定不要混在一起。


七、最关键的一行:super().__init__() 到底在干什么?

现在看:

class BCPolicy(nn.Module):    def __init__(self):        super().__init__()

前面已经知道:

BCPolicy

继承:

nn.Module

那么 BCPolicy 自己定义了:

__init__()

就有一个问题:

父类 nn.Module 自己原本的初始化工作怎么办?

于是使用:

super().__init__()

它可以先理解成:

调用父类这一套初始化逻辑。

对于这里:

super()

会按照 Python 的继承关系,继续寻找父类中的初始化方法。

所以概念上:

super().__init__()

就是:

让 nn.Module 先把 PyTorch Module 应该准备的内部结构初始化好

然后我们再继续定义自己模型的层。

例如:

class BCPolicy(nn.Module):    def __init__(self):        super().__init__()        self.fc1 = nn.Linear(10, 64)        self.relu = nn.ReLU()        self.fc2 = nn.Linear(64, 7)

逻辑就是:

创建 BCPolicy 对象
↓
进入 BCPolicy.__init__()
↓
先调用父类 nn.Module 的初始化逻辑
↓
PyTorch Module 基础设施准备好
↓
再把 fc1 注册进去
↓
再把 relu 注册进去
↓
再把 fc2 注册进去

八、super() 不是“创建父类对象”

这个地方也非常重要。

不要理解成:

super().__init__()

创建了一个独立的:

nn.Module 对象

不是。

当前仍然只有我们这个:

BCPolicy 实例

比如:

model = BCPolicy()

我们最终使用的就是这一个 model 对象。

super().__init__() 是:

在这个同一个对象上,执行继承链中父类需要完成的初始化逻辑。

你可以先画成:

model
│
└── BCPolicy 实例
      │
      ├── 具有 nn.Module 提供的能力
      │
      └── 具有 BCPolicy 自己增加的结构

而不是:

model
├── 一个 BCPolicy
└── 一个单独 nn.Module

后者是错误理解。


九、nn.Module.__init__() 到底帮我们初始化了什么?

不用现在去背 PyTorch 源码字段,但概念上非常重要。

nn.Module 需要管理:

  • Parameters;
  • 子 Module;
  • Buffers;
  • forward/backward hooks;
  • 模型状态;
  • state_dict();
  • .to(device) 等 Module 机制。

例如你后来写:

self.fc1 = nn.Linear(10, 64)

PyTorch 不只是把:

fc1

当成一个普通 Python 属性。

因为:

nn.Linear

自己也是:

nn.Module

所以它能够被当前 BCPolicy 注册成:

子 Module。

这样后面执行:

model.parameters()

PyTorch 才能够递归找到:

model
↓
fc1
↓
fc1.weight
fc1.bias

model
↓
fc2
↓
fc2.weight
fc2.bias

于是:

model.parameters()

才能把整个模型内部需要学习的参数找出来。

这就是为什么:

super().__init__()

不是为了“写法好看”。

它实际上是在建立 PyTorch Module 的基础设施。


十、所以这三行应该怎样完整翻译?

现在重新看:

class BCPolicy(nn.Module):    def __init__(self):        super().__init__()

可以逐行翻译。

第一行:

class BCPolicy(nn.Module):

定义一个名为 BCPolicy 的新类,它继承 PyTorch 的 nn.Module。

第二行:

def __init__(self):

定义 BCPolicy 实例创建后需要执行的初始化逻辑;self 表示当前这个具体实例。

第三行:

super().__init__()

在当前这个实例上先执行父类 nn.Module 的初始化逻辑,使这个对象具备完整的 PyTorch Module 基础设施。

然后通常继续:

self.fc1 = nn.Linear(10, 64)

给当前 BCPolicy 实例注册一个 Linear 子模块。

这才是完整理解。


十一、接下来重点讲 __call__():为什么 model(obs) 能工作?

先完全离开 PyTorch,看普通 Python。

假设:

class Test:    def __call__(self, x):        return x * 2

创建对象:

obj = Test()

注意:

obj

是一个对象,不是普通函数。

但现在可以写:

result = obj(5)

得到:

10

为什么?

因为:

obj(5)

这种:

对象(...)

语法会触发对象对应类的:

__call__()

概念上可以理解:

obj(5)

触发:

obj.__call__(5)

因此:

对象后面加括号
↓
__call__()

这和上一课:

dataset[10]
↓
__getitem__(10)

属于同一套 Python 特殊方法机制。


十二、现在回到 PyTorch

我们的类:

class BCPolicy(nn.Module):

继承:

nn.Module

而 nn.Module 为 Module 对象提供了“可以被调用”的机制。

于是:

model = BCPolicy()

得到一个 BCPolicy 实例以后,可以写:

pred_action = model(obs)

这里:

model(obs)

会进入 nn.Module 提供的调用流程。

最终再执行我们自己定义的:

def forward(self, obs):

十三、那为什么我们不自己定义 __call__()?

这是 PyTorch 初学中很关键的一点。

你可能会想:

既然:

model(obs)

调用 __call__(),

那我为什么不写:

class BCPolicy(nn.Module):    def __call__(self, obs):        ...

而非要写:

def forward(self, obs):

因为 nn.Module 已经替我们实现了 Module 的调用机制。

它不只是简单:

__call__()
↓
forward()

中间还需要为 PyTorch 处理一些 Module 级机制,例如各种 hook 等。

因此我们通常应该做的是:

实现 forward(),而不是自己覆盖 nn.Module.__call__()。

也就是把职责分开:

nn.Module
负责模型怎么被调用

我们
负责 forward 到底怎么计算

这是一种非常重要的框架设计思想。


十四、所以这条链可以写得更严谨

之前我们写:

model(obs)
↓
nn.Module 的 __call__()
↓
PyTorch 相关 Module 调用逻辑
↓
forward(obs)
↓
返回结果

这个理解是对的。

为了避免你以后误解,再加上对象关系:

model = BCPolicy()
        │
        └── model 是 BCPolicy 的一个实例
                    │
                    └── BCPolicy 继承 nn.Module
                                │
                                └── 得到了 Module 的调用机制

model(obs)
↓
触发 Module 对象的调用流程
↓
进入 PyTorch Module 的 __call__ 相关逻辑
↓
最终调用 model.forward(obs)
↓
得到输出

十五、为什么推荐 model(obs),而不是直接 model.forward(obs)?

因为:

model.forward(obs)

是直接调用你自己实现的 forward()。

而:

model(obs)

走的是:

nn.Module 调用机制
↓
forward

这样 PyTorch 在 forward() 前后需要处理的 Module 机制才能正常参与。

因此平时应该:

output = model(x)

而不是把:

model.forward(x)

当成标准调用方式。


十六、把 __getitem__() 和 __call__() 放在一起,你就容易记住了

现在你已经遇到两个非常典型的 Python 特殊方法。

Dataset

你写:

sample = dataset[10]

背后:

[]
↓
__getitem__()

所以:

dataset[10]

触发取数据逻辑。


Model

你写:

pred = model(obs)

背后:

()
↓
__call__()
↓
forward()

所以:

model(obs)

最终进行网络 Forward。

因此:

dataset[index]
↓
__getitem__()
↓
得到数据

model(input)
↓
__call__()
↓
forward()
↓
得到预测

这两条链以后会每天见到。


十七、把类创建、Dataset、Model 整体串起来

现在看代码:

class BCPolicy(nn.Module):    def __init__(self):        super().__init__()        self.net = nn.Sequential(            nn.Linear(10, 64),            nn.ReLU(),            nn.Linear(64, 7)        )    def forward(self, obs):        return self.net(obs)

第一阶段只是:

定义 BCPolicy 类

还没有真正的模型对象。

然后:

model = BCPolicy()

才:

创建 BCPolicy 实例
↓
执行初始化过程
↓
BCPolicy.__init__()
↓
super().__init__()
↓
nn.Module 基础结构初始化
↓
创建并注册 self.net
↓
model 对象准备完成

之后训练:

pred_action = model(obs)

发生:

model(obs)
↓
Module 调用机制
↓
forward(obs)
↓
self.net(obs)
↓
Linear
↓
ReLU
↓
Linear
↓
pred_action

现在整个对象链就连起来了。


十八、第五课标准答案

1. nn.Linear(10,64) 的 Weight 和 Bias Shape

PyTorch 中:

\[ weight.shape=[64,10] \]\[ bias.shape=[64] \]

记住:

\[ nn.Linear(in,out) \]

其 Weight:

\[ [out,in] \]


2. 为什么 [B,10] → [B,64],但 \(B\) 不变?

因为这一层对 Batch 中的每个样本使用同一套 Linear 参数。

每个:

\[ [10] \]

变成:

\[ [64] \]

所以:

\[ [B,10]\rightarrow[B,64] \]

Batch 中仍然是原来的 \(B\) 个样本。


3. nn.Linear(10,64) 有多少参数?

Weight:

\[ 64\times10=640 \]

Bias:

\[ 64 \]

总数:

\[ 640+64=\boxed{704} \]


4. nn.Linear(64,7) 有多少参数?

Weight:

\[ 7\times64=448 \]

Bias:

\[ 7 \]

总数:

\[ 448+7=\boxed{455} \]


5. 如果 \(W:[64,10]\),那么 W.grad 的 Shape?

也是:

\[ \boxed{[64,10]} \]

因为每一个 Weight:

\[ w_{ij} \]

都需要对应一个梯度:

\[ \frac{\partial L}{\partial w_{ij}} \]

因此 Weight Matrix 和它的 Gradient Matrix Shape 相同。


6. 为什么要把 model.parameters() 给 Optimizer?

因为 Optimizer 必须知道:

哪些东西是允许它更新的模型参数。

例如:

fc1.weight
fc1.bias
fc2.weight
fc2.bias

于是:

optimizer = Adam(model.parameters())

相当于告诉 Optimizer:

训练的时候,请更新这些参数。


7. model(obs) 为什么会最终调用 forward()?

因为:

BCPolicy

继承:

nn.Module

获得了 Module 的调用机制。

所以:

model(obs)
↓
Module 的 __call__ 相关流程
↓
forward(obs)
↓
返回预测

我们负责定义:

forward()

PyTorch 负责外面的调用框架。


8. \(\theta\) 到底是什么?

\[ \theta \]

表示:

模型所有可训练参数的集合。

例如:

\[ \theta= \{ W_1,b_1,W_2,b_2 \} \]

更复杂的模型中可能有成千上万组参数。

训练本质上就是不断改变:

\[ \theta \]

让:

\[ \pi_\theta(o) \]

产生越来越正确的输出。


VLA 系统学习第 6 课:为什么只有 Linear 不够?——ReLU、非线性和隐藏层究竟做了什么

现在我们已经知道一个 Linear Layer 的本质:

\[ y=Wx+b \]

于是一个非常自然的问题出现了:

那我多堆几层 Linear 不就很强了吗?

比如:

nn.Linear(10, 64)nn.Linear(64, 128)nn.Linear(128, 64)nn.Linear(64, 7)

看起来已经四层了。

是不是就已经拥有非常强的表达能力?

答案非常关键:

如果中间没有非线性激活函数,那么无论堆多少个 Linear,本质上仍然只相当于一个 Linear。

这就是为什么神经网络里必须出现:

ReLU

这样的东西。


一、先从两个 Linear 连起来看

假设第一层:

\[ h=W_1x+b_1 \]

第二层:

\[ y=W_2h+b_2 \]

把第一层代入第二层:

\[ y=W_2(W_1x+b_1)+b_2 \]

展开:

\[ y=W_2W_1x+W_2b_1+b_2 \]

现在定义:

\[ W'=W_2W_1 \]

再定义:

\[ b'=W_2b_1+b_2 \]

那么:

\[ y=W'x+b' \]

你会发现:

两层 Linear 又被合并成了一个新的 Linear。

所以:

\[ Linear\rightarrow Linear \]

本质上仍然是:

\[ Linear \]


二、堆十层也没有解决问题

三层:

\[ x \rightarrow W_1x+b_1 \rightarrow W_2h_1+b_2 \rightarrow W_3h_2+b_3 \]

最后照样可以整理成:

\[ y=W'x+b' \]

因此:

\[ \boxed{ Linear \rightarrow Linear \rightarrow Linear \rightarrow \cdots } \]

如果中间没有非线性操作,再多层也只是一个复杂写法的线性映射。

所以问题不是:

层数还不够。

而是:

模型内部缺少非线性。


三、为什么机器人 Policy 明显需要非线性?

想一个非常简单的动作规则。

假设 Observation 只有一个值:

\[ x \]

表示:

物体在机器人左右方向上的位置。

规定:

  • \(x<0\):物体在左边;
  • \(x>0\):物体在右边。

假设策略希望:

物体在左边
→ 向左运动

物体在右边
→ 向右运动

有些简单关系 Linear 可以表达。

但现实策略往往是:

如果物体在左边
而且夹爪是打开的
而且末端还比较远
→ 靠近物体

如果物体在左边
而且夹爪已经靠近
→ 关闭夹爪

如果已经抓住
→ 向上抬

如果已经到目标位置
→ 松开

也就是说同一个输入维度对 Action 的影响,会随着其他状态变化而变化。

这种关系不是简单:

\[ y=Wx+b \]

就能轻松描述的。

我们需要模型能够形成:

不同区域使用不同的行为规律。

于是引入:

Nonlinearity

非线性。


四、ReLU 是什么?

最常见的激活函数之一:

\[ \operatorname{ReLU}(x) = \max(0,x) \]

意思非常简单:

如果:

\[ x>0 \]

输出:

\[ x \]

如果:

\[ x\leq0 \]

输出:

\[ 0 \]

例如:

\[ ReLU(-3)=0 \]\[ ReLU(-0.5)=0 \]\[ ReLU(0)=0 \]\[ ReLU(2)=2 \]\[ ReLU(8)=8 \]


五、它到底改变了什么?

假设 Linear 输出:

\[ h= [-2,\;3,\;-0.5,\;7] \]

经过 ReLU:

\[ ReLU(h) = [0,\;3,\;0,\;7] \]

可以理解成:

所有负值被截断为 0,正值保留。

所以:

Linear
↓
产生一组特征
↓
ReLU
↓
根据数值所在区域改变这些特征

这一步虽然公式看起来简单,却破坏了“所有层都可以合并成一个 Linear”的性质。


六、为什么加了 ReLU 后不能再简单合并?

没有 ReLU:

\[ y=W_2(W_1x+b_1)+b_2 \]

可以直接展开。

但加入 ReLU:

\[ h=ReLU(W_1x+b_1) \]

然后:

\[ y=W_2h+b_2 \]

也就是:

\[ y= W_2ReLU(W_1x+b_1)+b_2 \]

现在:

\[ ReLU(\cdot) \]

夹在中间。

你无法再简单写成:

\[ y=W'x+b' \]

因为 ReLU 会根据输入不同,决定:

哪些值保留,哪些值直接变成 0。

这就是非线性真正带来的变化。


七、隐藏层为什么叫 Hidden Layer?

看:

self.net = nn.Sequential(    nn.Linear(10, 64),    nn.ReLU(),    nn.Linear(64, 7))

输入:

\[ x:[10] \]

经过第一层:

\[ h:[64] \]

最后:

\[ a:[7] \]

这里:

\[ h \]

既不是原始 Observation,也不是最终 Action。

它是网络内部生成的中间表示。

所以叫:

Hidden Representation

对应这一层叫:

Hidden Layer

数据链:

\[ Observation \rightarrow Hidden\ Feature \rightarrow Action \]


八、Hidden Feature 到底是什么?

假设 Observation 有:

\[ 10 \]

个原始数字。

经过:

nn.Linear(10,64)

以后变成:

\[ 64 \]

个隐藏特征。

这些隐藏特征通常没有简单固定的人类解释。

不能机械地说:

hidden[0] = 距离
hidden[1] = 角度
hidden[2] = 是否应该抓

模型自己通过训练决定这些内部表示应该如何组织。

更准确地说:

Hidden Representation 是模型为了完成最终预测任务而学习出来的内部特征空间。


九、现在看完整 Shape

假设:

\[ obs:[32,10] \]

第一层:

nn.Linear(10,64)

得到:

\[ [32,64] \]

再经过:

nn.ReLU()

Shape 不变:

\[ [32,64] \]

然后:

nn.Linear(64,7)

得到:

\[ [32,7] \]

所以完整数据流:

\[ [32,10] \rightarrow [32,64] \rightarrow [32,64] \rightarrow [32,7] \]

对应:

Observation
↓
Linear
↓
Hidden Feature
↓
ReLU
↓
Nonlinear Hidden Feature
↓
Linear
↓
Predicted Action

十、为什么 ReLU 没有 Parameters?

看:

nn.Linear(10,64)

里面有:

\[ W,b \]

所以有可训练参数。

而:

nn.ReLU()

只是执行固定规则:

\[ x\leq0\rightarrow0 \]\[ x>0\rightarrow x \]

它没有需要训练的:

\[ W \]

或者:

\[ b \]

所以:

\[ \boxed{ ReLU\ Parameters=0 } \]

但是:

没有参数,不代表它不重要。

恰恰相反,它对神经网络表达复杂函数非常关键。


十一、用代码完整看一次

class BCPolicy(nn.Module):    def __init__(self):        super().__init__()        self.fc1 = nn.Linear(10, 64)        self.relu = nn.ReLU()        self.fc2 = nn.Linear(64, 7)    def forward(self, obs):        x = self.fc1(obs)        x = self.relu(x)        action = self.fc2(x)        return action

现在这段代码应该能够连续读。

输入:

obs

Shape:

\[ [B,10] \]

先:

x = self.fc1(obs)

发生:

\[ [B,10] \rightarrow [B,64] \]

然后:

x = self.relu(x)

数值发生非线性变换,但 Shape:

\[ [B,64] \rightarrow[B,64] \]

最后:

action = self.fc2(x)

发生:

\[ [B,64] \rightarrow[B,7] \]

最终:

\[ action:[B,7] \]


十二、它又怎么训练?

仍然没有改变我们前面的主链。

pred_action = model(obs)loss = loss_fn(pred_action, action)optimizer.zero_grad()loss.backward()optimizer.step()

只是现在:

model(obs)

内部不再是一个 Linear。

而是:

\[ Observation \rightarrow Linear \rightarrow ReLU \rightarrow Linear \rightarrow Action \]

Backward 会沿整条计算链往回计算:

Loss
↓
fc2
↓
ReLU
↓
fc1

最终得到:

\[ \frac{\partial L}{\partial W_2}, \frac{\partial L}{\partial b_2}, \frac{\partial L}{\partial W_1}, \frac{\partial L}{\partial b_1} \]

然后 Optimizer 更新这些参数。


十三、为什么这和后面的 VLA 有关系?

因为后面模型会变得非常复杂:

Image
↓
Vision Encoder

Language
↓
Language Encoder

Robot State
↓
State Encoder

       ↓
     Fusion
       ↓
 Transformer
       ↓
 Action Head
       ↓
    Action

但是无论名字多复杂,里面仍然充满:

  • Linear;
  • Matrix Multiplication;
  • Nonlinear Activation;
  • Parameters;
  • Forward;
  • Gradient;
  • Backward。

所以现在真正理解:

\[ Wx+b \]

以及:

\[ Linear\rightarrow ReLU \]

不是在偏离 VLA。

而是在学习以后所有复杂网络共同使用的底层语言。


第六课链路回看

这一课最重要的逻辑只有一条:

\[ Linear \rightarrow Linear \rightarrow Linear \]

如果没有非线性,本质仍然可以压缩成:

\[ Linear \]

因此加入:

\[ ReLU \]

以后:

\[ Linear \rightarrow ReLU \rightarrow Linear \]

模型才拥有表达复杂非线性映射的基础能力。

于是 BC Policy:

\[ Observation \rightarrow Hidden\ Representation \rightarrow Action \]

不再只是一个简单的线性变换。


第六课自测

  1. 为什么两层 Linear 中间如果没有激活函数,最终仍然等价于一个 Linear?

  2. ReLU 的数学定义是什么?

  3. 输入:

\[ [-2,3,-0.5,7] \]

经过 ReLU 后是什么?

  1. 为什么 ReLU 不改变:

\[ [B,64] \]

的 Shape,却仍然非常重要?

  1. nn.ReLU() 有多少个可训练参数?

  2. 为什么 Hidden Feature 通常不能直接解释成“某个明确物理量”?

  3. 对于:

nn.Linear(10,64)nn.ReLU()nn.Linear(64,7)

如果输入:

\[ [32,10] \]

请完整写出每一步 Shape。

  1. 最后一个核心问题:为什么 Linear → ReLU → Linear 比单纯 Linear → Linear 更有能力表达复杂的 Observation → Action 关系?
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐