mlp文件夹继续阅读
纵览结构,mlp文件夹就是定义了mlp类,所有的函数都在mlp这个class下。
关于plot:画神经网络结构图
def plot(self, beta=3, scale=1., metric='w'):# metric = 'w', 'act' or 'fa'if metric == 'fa':self.attribute()depth = self.depthy0 = 0.5fig, ax = plt.subplots(figsize=(3*scale,3*y0*depth*scale))shp = self.widthmin_spacing = 1/max(self.width)for j in range(len(shp)):N = shp[j]for i in range(N):plt.scatter(1 / (2 * N) + i / N, j * y0, s=min_spacing ** 2 * 5000 * scale ** 2, color='black')plt.ylim(-0.1*y0,y0*depth+0.1*y0)plt.xlim(-0.02,1.02)linears = self.linearsfor ii in range(len(linears)):linear = linears[ii]p = linear.weightp_shp = p.shapeif metric == 'w':passelif metric == 'act':p = self.wa_forward[ii]elif metric == 'fa':p = self.wa_backward[ii]else:raise Exception('metric = \'{}\' not recognized. Choices are \'w\', \'act\', \'fa\'.'.format(metric))for i in range(p_shp[0]):for j in range(p_shp[1]):plt.plot([1/(2*p_shp[0])+i/p_shp[0], 1/(2*p_shp[1])+j/p_shp[1]], [y0*(ii+1),y0*ii], lw=0.5*scale, alpha=np.tanh(beta*np.abs(p[i,j].cpu().detach().numpy())), color="blue" if p[i,j]>0 else "red")ax.axis('off')
这段代码是一个Python函数,用于绘制一个神经网络的结构图。以下是代码的主要功能和它所绘制的内容的详细解释:
-
参数说明:
beta
:用于控制连接线的透明度,基于权重的大小。scale
:用于调整图形的大小。metric
:用于指定绘制时使用的度量标准,可以是权重(‘w’)、激活(‘act’)或反馈调整(‘fa’)。
-
绘图步骤:
- 首先,根据网络深度和给定的比例尺设置图形的大小。
y0
是一个垂直间距因子,用于控制层与层之间的距离。- 绘制神经网络中的每个神经元,它们被表示为黑色的小点。点的位置取决于它们在各自层中的位置,点的尺寸与层的宽度成反比。
-
神经元布局:
- 通过两层循环,在每个层的指定位置上放置神经元。
min_spacing
用于确保即使在非常宽的层中,神经元也不会太小。
-
绘制连接线:
linears
是一个包含神经网络层权重的列表。- 对于每一层,根据
metric
参数选择要使用的权重(原始权重、激活后的权重或反馈调整后的权重)。 - 连接线表示层与层之间的连接,线的颜色取决于权重的符号(正为蓝色,负为红色),线宽和透明度则取决于权重的大小和
beta
参数。
-
坐标轴设置:
- 设置y轴的范围以适应所有层。
- 设置x轴的范围,确保所有神经元都在视图内。
- 最后,关闭坐标轴的显示,以便只显示神经网络的结构图。
总结:这个函数绘制了一个神经网络的结构,其中包括层的神经元和它们之间的连接。通过调整metric
参数,可以查看不同视角下的网络结构,例如原始权重、激活后的权重或反馈调整后的权重。通过这种方式,可以直观地了解神经网络的结构和权重分布。
第一步:画散点scatter,使用x 与y坐标
第二部:用线连接,权重大小决定透明度,越重要透明度低。 plt.plot([
1/(2*p_shp[0])+i/p_shp[0],1/(2*p_shp[1])+j/p_shp[1]],【x0,x1】
[y0*(ii+1),y0*ii],【y0,y1】,这里x01,y01都是单个数字
lw=0.5*scale,
lw
是线宽的缩写,表示连接线的宽度。- 这里的线宽是0.5乘以缩放因子
scale
,以便在调整图形大小时保持线宽的比例。
alpha=np.tanh(beta*np.abs(p[i,j].cpu().detach().numpy())),【透明度】
lpha
表示连接线的透明度。- 透明度由权重
p[i,j]
的绝对值决定,经过beta
缩放后,再通过双曲正切函数np.tanh
进行变换。这样,权重较大的连接线会更明显,而权重较小的连接线则更透明。
color="blue" if p[i,j]>0 else "red")权重正,蓝色;否则红色。
init__里的linear
linears = []self.width = widthself.depth = depth = len(width) - 1for i in range(depth):linears.append(nn.Linear(width[i], width[i+1]))self.linears = nn.ModuleList(linears)
linears是一层又一层的(depth层)nn.linear 组成的列表,将linears
列表转换为nn.ModuleList
-
linears = []
:- 这行代码初始化了一个名为
linears
的空列表,用于存储即将创建的线性层。
- 这行代码初始化了一个名为
-
self.width = width
:- 这里,
width
是一个列表,表示每一层的神经元数量。self.width
将这个列表存储为模型的属性。
- 这里,
-
self.depth = depth = len(width) - 1
:depth
变量计算网络的总层数,它是width
列表长度的减一。这样做是因为列表中的元素数量代表层数,而层数比元素数量少一个(因为每一层都连接到下一层)。self.depth
将层数存储为模型的属性。
-
for i in range(depth)
:- 这是一个循环,它遍历每一层,从0开始到
depth - 1
结束。
- 这是一个循环,它遍历每一层,从0开始到
-
linears.append(nn.Linear(width[i], width[i+1]))
:- 在循环内部,这行代码为每一层创建一个线性层(全连接层),并将其添加到
linears
列表中。 nn.Linear(width[i], width[i+1])
是PyTorch框架中用于创建线性层的函数,其中width[i]
是当前层的输入特征数(即当前层的神经元数量),width[i+1]
是下一层的输出特征数(即下一层的神经元数量)。
- 在循环内部,这行代码为每一层创建一个线性层(全连接层),并将其添加到
-
self.linears = nn.ModuleList(linears)
:- 最后,
self.linears
将linears
列表转换为nn.ModuleList
,这是PyTorch中的一个类,它能够将模块列表作为模型的一部分进行管理。 - 通过这种方式,所有创建的线性层都会被PyTorch识别,并在模型的参数优化和移动到GPU等操作中一起处理。
- 最后,
总结:这段代码创建了一个由多个线性层组成的神经网络模型,每一层的神经元数量由width
列表指定。通过这种方式,可以定义一个具有特定结构和深度的全连接神经网络。
方便之后前向传播像函数一样作用在输入上——
x = self.linears[i](x)
-
self.linears
是一个nn.ModuleList
,它是一个可以包含多个模块(例如,神经网络层)的列表。当你通过索引i
访问self.linears[i]
时,你实际上是在获取列表中的第i
个模块。 -
在PyTorch中,每个模块(例如
nn.Linear
)都是一个可调用的对象。这意味着你可以像调用函数一样调用这个模块。当你调用self.linears[i](x)
时,你实际上是在调用第i
个线性层,并将输入数据x
传递给它。 -
当一个线性层被调用时,它会执行矩阵乘法(加上偏置项,如果有的话),这就是全连接层的典型操作。输入
x
会通过这个线性层,并产生输出,这个输出可以被用作下一层的输入。
所以,整个表达式x = self.linears[i](x)
的含义是:
- 获取索引为
i
的线性层。 - 将当前的数据
x
传递给这个线性层。 - 执行线性层的操作(即全连接层的计算)。
- 将计算结果赋值给变量
x
,这样x
就成为了下一层的输入。
这样的链式调用在神经网络的前向传播过程中非常常见,允许你按顺序通过网络的每一层
装饰器,w属性
@property是把函数转成属性
@自定义函数 才是自定义装饰函数、
@propertydef w(self):return [self.linears[l].weight for l in range(self.depth)]
在Python中,@property
装饰器用于将一个方法转换为属性访问。这里,@property
被用来定义一个属性的getter方法,允许用户以访问属性的方式访问方法返回的值,而不是显式地调用一个方法。
@property
装饰器用于定义一个名为w
的属性,它允许用户以self.w
的形式访问网络中每一层线性变换的权重。
以下是为什么使用@property
装饰器的原因:
-
简化访问:使用
@property
,你可以像访问一个普通属性一样访问self.w
,而不是调用一个方法(如self.get_weights()
)。这使得代码更加简洁,易于理解。 -
封装:通过使用
@property
,你可以隐藏内部的实现细节。例如,如果你想在未来改变权重存储或计算的方式,你可以只修改w
属性的getter方法,而不需要修改使用这些权重的代码。 -
控制访问:如果你需要对属性的访问进行更多的控制(例如,添加验证逻辑或者延迟计算),你可以通过
@property
来实现。虽然在这个例子中,w
属性只是简单地返回一个列表,但你可以添加额外的逻辑来控制权重值的访问。 -
只读属性:在这个例子中,
@property
没有对应的setter方法(如@w.setter
),这意味着self.w
是一个只读属性。这可以防止权重被外部代码意外修改。
reg正则化
def reg(self, reg_metric, lamb_l1, lamb_entropy):if reg_metric == 'w':acts_scale = self.wif reg_metric == 'act':acts_scale = self.wa_forwardif reg_metric == 'fa':acts_scale = self.wa_backwardif reg_metric == 'a':acts_scale = self.acts_scaleif len(acts_scale[0].shape) == 2:reg_ = 0.for i in range(len(acts_scale)):vec = acts_scale[i]vec = torch.abs(vec)l1 = torch.sum(vec)p_row = vec / (torch.sum(vec, dim=1, keepdim=True) + 1)p_col = vec / (torch.sum(vec, dim=0, keepdim=True) + 1)entropy_row = - torch.mean(torch.sum(p_row * torch.log2(p_row + 1e-4), dim=1))entropy_col = - torch.mean(torch.sum(p_col * torch.log2(p_col + 1e-4), dim=0))reg_ += lamb_l1 * l1 + lamb_entropy * (entropy_row + entropy_col)elif len(acts_scale[0].shape) == 1:reg_ = 0.for i in range(len(acts_scale)):vec = acts_scale[i]vec = torch.abs(vec)l1 = torch.sum(vec)p = vec / (torch.sum(vec) + 1)entropy = - torch.sum(p * torch.log2(p + 1e-4))reg_ += lamb_l1 * l1 + lamb_entropy * entropyreturn reg_
def reg(self, reg_metric, lamb_l1, lamb_entropy):
- 定义一个名为
reg
的方法,它接受三个参数:reg_metric
(用于选择正则化类型),lamb_l1
(L1正则化的系数),和lamb_entropy
(熵正则化的系数)。
python
复制
if reg_metric == 'w':acts_scale = self.w
- 如果
reg_metric
是 ‘w’,则将self.w
赋值给acts_scale
,这意味着正则化将基于权重。
python
复制
if reg_metric == 'act':acts_scale = self.wa_forward
- 如果
reg_metric
是 ‘act’,则将self.wa_forward
赋值给acts_scale
,这意味着正则化将基于前向激活。
python
复制
if reg_metric == 'fa':acts_scale = self.wa_backward
- 如果
reg_metric
是 ‘fa’,则将self.wa_backward
赋值给acts_scale
,这意味着正则化将基于反向激活。
python
复制
if reg_metric == 'a':acts_scale = self.acts_scale
- 如果
reg_metric
是 ‘a’,则将self.acts_scale
赋值给acts_scale
,这意味着正则化将基于激活尺度。
python
复制
if len(acts_scale[0].shape) == 2:reg_ = 0.
- 检查
acts_scale
的第一个元素是否是二维的(即矩阵)。如果是,初始化正则化损失reg_
为 0。
python
复制
for i in range(len(acts_scale)):vec = acts_scale[i]vec = torch.abs(vec)
- 遍历
acts_scale
中的每个元素,并计算其绝对值。
python
复制
l1 = torch.sum(vec)
- 计算当前元素的L1范数(所有元素的绝对值之和)。
python
复制
p_row = vec / (torch.sum(vec, dim=1, keepdim=True) + 1)p_col = vec / (torch.sum(vec, dim=0, keepdim=True) + 1)
- 计算每行的归一化概率
p_row
和每列的归一化概率p_col
,这里加1是为了避免除以0。
python
复制
entropy_row = - torch.mean(torch.sum(p_row * torch.log2(p_row + 1e-4), dim=1))entropy_col = - torch.mean(torch.sum(p_col * torch.log2(p_col + 1e-4), dim=0))
- 计算每行的熵
entropy_row
和每列的熵entropy_col
,这里加1e-4
是为了数值稳定性,避免对0取对数。
python
复制
reg_ += lamb_l1 * l1 + lamb_entropy * (entropy_row + entropy_col)
- 将当前层的L1正则化和熵正则化加到总正则化损失
reg_
上。
python
复制
elif len(acts_scale[0].shape) == 1:
- 如果
acts_scale
的第一个元素是一维的(即向量),则进行以下操作。
python
复制
reg_ = 0.
- 初始化正则化损失
reg_
为 0。
python
复制
for i in range(len(acts_scale)):vec = acts_scale[i]vec = torch.abs(vec)
- 遍历
acts_scale
中的每个元素,并计算其绝对值。
python
复制
l1 = torch.sum(vec)
- 计算当前元素的L1范数。
python
复制
p = vec / (torch.sum(vec) + 1)
- 计算归一化概率
p
。
python
复制
entropy = - torch.sum(p * torch.log2(p + 1e-4))
- 计算熵
entropy
。
python
复制
reg_ += lamb_l1 * l1 + lamb_entropy * entropy
- 将当前层的L1正则化和熵正则化加到总正则化损失
reg_
上。
python
复制
return reg_
- 返回计算得到的总正则化损失
reg_
。
closure闭包
def closure():global train_loss, reg_optimizer.zero_grad()pred = self.forward(dataset['train_input'][train_id].to(self.device))train_loss = loss_fn(pred, dataset['train_label'][train_id].to(self.device))if self.save_act:if reg_metric == 'fa':self.attribute()reg_ = self.get_reg(reg_metric, lamb_l1, lamb_entropy)else:reg_ = torch.tensor(0.)objective = train_loss + lamb * reg_objective.backward()return objective
def closure():global train_loss, reg_
- 定义一个名为
closure
的函数。这个函数将被用作优化器中的闭包,用于计算损失并执行反向传播。 - 使用
global
关键字声明train_loss
和reg_
是全局变量,这样在函数内部对这些变量的修改会影响到外部作用域。
python
复制
optimizer.zero_grad()
- 清除优化器中所有参数的梯度。这是在每次迭代开始时必须执行的步骤,以确保不会累加之前的梯度。
python
复制
pred = self.forward(dataset['train_input'][train_id].to(self.device))
- 调用
self.forward
方法来获取模型的预测结果。这里的dataset['train_input'][train_id]
是当前批次的数据,.to(self.device)
确保数据在正确的设备上(CPU或GPU)。
python
复制
train_loss = loss_fn(pred, dataset['train_label'][train_id].to(self.device))
- 使用提供的损失函数
loss_fn
计算预测结果pred
和真实标签dataset['train_label'][train_id]
之间的损失,并将结果赋值给全局变量train_loss
。
python
复制
if self.save_act:
- 检查
self.save_act
是否为真。如果为真,则执行以下代码块。
python
复制
if reg_metric == 'fa':self.attribute()
- 如果
reg_metric
等于'fa'
,则调用self.attribute()
方法。这个方法可能是用来计算或者保存某些属性,但具体实现未在代码中给出。
python
复制
reg_ = self.get_reg(reg_metric, lamb_l1, lamb_entropy)
- 调用
self.get_reg
方法来计算正则化项reg_
。这个方法接受正则化类型reg_metric
、L1正则化系数lamb_l1
和熵正则化系数lamb_entropy
作为参数。
python
复制
else:reg_ = torch.tensor(0.)
- 如果
self.save_act
为假,则将正则化项reg_
设置为 0,即不应用正则化。
python
复制
objective = train_loss + lamb * reg_
- 计算最终的目标函数,它是训练损失
train_loss
和正则化项lamb * reg_
的和。
python
复制
objective.backward()
- 对目标函数执行反向传播,计算模型参数的梯度。
python
复制
return objective
- 返回计算得到的最终目标函数值。这个值可能用于调试或其他目的,但通常不是必须的,因为闭包的主要目的是执行反向传播。
fit()
def fit(self, dataset, opt="LBFGS", steps=100, log=1, lamb=0., lamb_l1=1., lamb_entropy=2., loss_fn=None, lr=1., batch=-1,metrics=None, in_vars=None, out_vars=None, beta=3, device='cpu', reg_metric='w', display_metrics=None):if lamb > 0. and not self.save_act:print('setting lamb=0. If you want to set lamb > 0, set =True')old_save_act = self.save_actif lamb == 0.:self.save_act = Falsepbar = tqdm(range(steps), desc='description', ncols=100)if loss_fn == None:loss_fn = loss_fn_eval = lambda x, y: torch.mean((x - y) ** 2)else:loss_fn = loss_fn_eval = loss_fnif opt == "Adam":optimizer = torch.optim.Adam(self.parameters(), lr=lr)elif opt == "LBFGS":optimizer = LBFGS(self.parameters(), lr=lr, history_size=10, line_search_fn="strong_wolfe", tolerance_grad=1e-32, tolerance_change=1e-32, tolerance_ys=1e-32)results = {}results['train_loss'] = []results['test_loss'] = []results['reg'] = []if metrics != None:for i in range(len(metrics)):results[metrics[i].__name__] = []if batch == -1 or batch > dataset['train_input'].shape[0]:batch_size = dataset['train_input'].shape[0]batch_size_test = dataset['test_input'].shape[0]else:batch_size = batchbatch_size_test = batchglobal train_loss, reg_def closure():global train_loss, reg_optimizer.zero_grad()pred = self.forward(dataset['train_input'][train_id].to(self.device))train_loss = loss_fn(pred, dataset['train_label'][train_id].to(self.device))if self.save_act:if reg_metric == 'fa':self.attribute()reg_ = self.get_reg(reg_metric, lamb_l1, lamb_entropy)else:reg_ = torch.tensor(0.)objective = train_loss + lamb * reg_objective.backward()return objectivefor _ in pbar:if _ == steps-1 and old_save_act:self.save_act = Truetrain_id = np.random.choice(dataset['train_input'].shape[0], batch_size, replace=False)test_id = np.random.choice(dataset['test_input'].shape[0], batch_size_test, replace=False)if opt == "LBFGS":optimizer.step(closure)if opt == "Adam":pred = self.forward(dataset['train_input'][train_id].to(self.device))train_loss = loss_fn(pred, dataset['train_label'][train_id].to(self.device))if self.save_act:reg_ = self.get_reg(reg_metric, lamb_l1, lamb_entropy)else:reg_ = torch.tensor(0.)loss = train_loss + lamb * reg_optimizer.zero_grad()loss.backward()optimizer.step()test_loss = loss_fn_eval(self.forward(dataset['test_input'][test_id].to(self.device)), dataset['test_label'][test_id].to(self.device))if metrics != None:for i in range(len(metrics)):results[metrics[i].__name__].append(metrics[i]().item())results['train_loss'].append(torch.sqrt(train_loss).cpu().detach().numpy())results['test_loss'].append(torch.sqrt(test_loss).cpu().detach().numpy())results['reg'].append(reg_.cpu().detach().numpy())if _ % log == 0:if display_metrics == None:pbar.set_description("| train_loss: %.2e | test_loss: %.2e | reg: %.2e | " % (torch.sqrt(train_loss).cpu().detach().numpy(), torch.sqrt(test_loss).cpu().detach().numpy(), reg_.cpu().detach().numpy()))else:string = ''data = ()for metric in display_metrics:string += f' {metric}: %.2e |'try:results[metric]except:raise Exception(f'{metric} not recognized')data += (results[metric][-1],)pbar.set_description(string % data)return results@propertydef connection_cost(self):with torch.no_grad():cc = 0.for linear in self.linears:t = torch.abs(linear.weight)def get_coordinate(n):return torch.linspace(0,1,steps=n+1, device=self.device)[:n] + 1/(2*n)in_dim = t.shape[0]x_in = get_coordinate(in_dim)out_dim = t.shape[1]x_out = get_coordinate(out_dim)dist = torch.abs(x_in[:,None] - x_out[None,:])cc += torch.sum(dist * t)return ccdef swap(self, l, i1, i2):def swap_row(data, i1, i2):data[i1], data[i2] = data[i2].clone(), data[i1].clone()def swap_col(data, i1, i2):data[:,i1], data[:,i2] = data[:,i2].clone(), data[:,i1].clone()swap_row(self.linears[l-1].weight.data, i1, i2)swap_row(self.linears[l-1].bias.data, i1, i2)swap_col(self.linears[l].weight.data, i1, i2)def auto_swap_l(self, l):num = self.width[l]for i in range(num):ccs = []for j in range(num):self.swap(l,i,j)self.get_act()self.attribute()cc = self.connection_cost.detach().clone()ccs.append(cc)self.swap(l,i,j)j = torch.argmin(torch.tensor(ccs))self.swap(l,i,j)def auto_swap(self):depth = self.depthfor l in range(1, depth):self.auto_swap_l(l)def tree(self, x=None, in_var=None, style='tree', sym_th=1e-3, sep_th=1e-1, skip_sep_test=False, verbose=False):if x == None:x = self.cache_dataplot_tree(self, x, in_var=in_var, style=style, sym_th=sym_th, sep_th=sep_th, skip_sep_test=skip_sep_test, verbose=verbose)
def fit(self, dataset, opt="LBFGS", steps=100, log=1, lamb=0., lamb_l1=1., lamb_entropy=2., loss_fn=None, lr=1., batch=-1,metrics=None, in_vars=None, out_vars=None, beta=3, device='cpu', reg_metric='w', display_metrics=None):
- 定义了一个名为
fit
的方法,它是用来训练神经网络的。它接受多个参数,包括数据集、优化器选项、训练步骤数、日志频率、正则化参数、损失函数、学习率等。
python
复制
if lamb > 0. and not self.save_act:print('setting lamb=0. If you want to set lamb > 0, set =True')
- 检查正则化参数
lamb
是否大于 0 且self.save_act
是否为 False。如果是,则打印一条消息,并建议设置self.save_act
为 True。
python
复制
old_save_act = self.save_act
if lamb == 0.:self.save_act = False
- 保存原始的
self.save_act
值,并根据lamb
的值设置self.save_act
。
python
复制
pbar = tqdm(range(steps), desc='description', ncols=100)
- 创建一个进度条,用于显示训练进度。
python
复制
if loss_fn == None:loss_fn = loss_fn_eval = lambda x, y: torch.mean((x - y) ** 2)
else:loss_fn = loss_fn_eval = loss_fn
- 如果没有提供损失函数,则使用均方误差作为默认损失函数。否则,使用提供的损失函数。
python
复制
if opt == "Adam":optimizer = torch.optim.Adam(self.parameters(), lr=lr)
elif opt == "LBFGS":optimizer = LBFGS(self.parameters(), lr=lr, history_size=10, line_search_fn="strong_wolfe", tolerance_grad=1e-32, tolerance_change=1e-32, tolerance_ys=1e-32)
- 根据优化器选项
opt
创建一个优化器对象。
python
复制
results = {}
results['train_loss'] = []
results['test_loss'] = []
results['reg'] = []
if metrics != None:for i in range(len(metrics)):results[metrics[i].__name__] = []
- 初始化一个字典
results
用于存储训练过程中的结果,包括训练损失、测试损失和正则化项。如果提供了metrics
,则为每个度量初始化一个空列表。
python
复制
if batch == -1 or batch > dataset['train_input'].shape[0]:batch_size = dataset['train_input'].shape[0]batch_size_test = dataset['test_input'].shape[0]
else:batch_size = batchbatch_size_test = batch
- 确定批处理大小,如果
batch
参数小于等于 -1 或大于训练集大小,则使用整个数据集。
python
复制
global train_loss, reg_
- 声明
train_loss
和reg_
为全局变量。
python
复制
def closure():# ...
- 定义一个闭包函数
closure
,用于优化器的内部使用。
python
复制
for _ in pbar:# ...
- 使用进度条进行迭代训练过程。
python
复制
if _ == steps-1 and old_save_act:self.save_act = True
- 在最后一次迭代时,如果原始的
self.save_act
为 True,则将其重新设置为 True。
python
复制
train_id = np.random.choice(dataset['train_input'].shape[0], batch_size, replace=False)
test_id = np.random.choice(dataset['test_input'].shape[0], batch_size_test, replace=False)
- 随机选择训练和测试数据的批次。
python
复制
if opt == "LBFGS":optimizer.step(closure)
- 如果使用的是 LBFGS 优化器,则调用闭包函数进行优化步骤。
python
复制
# ... (省略了 Adam 优化器的代码)
python
复制
test_loss = loss_fn_eval(self.forward(dataset['test_input'][test_id].to(self.device)), dataset['test_label'][test_id].to(self.device))
- 计算测试集上的损失。
python
复制
if metrics != None:for i in range(len(metrics)):results[metrics[i].__name__].append(metrics[i]().item())
- 如果提供了度量函数,则计算并存储这些度量的结果。
- ……、
【】
@propertydef connection_cost(self):with torch.no_grad():cc = 0.for linear in self.linears:t = torch.abs(linear.weight)def get_coordinate(n):return torch.linspace(0,1,steps=n+1, device=self.device)[:n] + 1/(2*n)in_dim = t.shape[0]x_in = get_coordinate(in_dim)out_dim = t.shape[1]x_out = get_coordinate(out_dim)dist = torch.abs(x_in[:,None] - x_out[None,:])cc += torch.sum(dist * t)return ccdef swap(self, l, i1, i2):def swap_row(data, i1, i2):data[i1], data[i2] = data[i2].clone(), data[i1].clone()def swap_col(data, i1, i2):data[:,i1], data[:,i2] = data[:,i2].clone(), data[:,i1].clone()swap_row(self.linears[l-1].weight.data, i1, i2)swap_row(self.linears[l-1].bias.data, i1, i2)swap_col(self.linears[l].weight.data, i1, i2)def auto_swap_l(self, l):num = self.width[l]for i in range(num):ccs = []for j in range(num):self.swap(l,i,j)self.get_act()self.attribute()cc = self.connection_cost.detach().clone()ccs.append(cc)self.swap(l,i,j)j = torch.argmin(torch.tensor(ccs))self.swap(l,i,j)def auto_swap(self):depth = self.depthfor l in range(1, depth):self.auto_swap_l(l)def tree(self, x=None, in_var=None, style='tree', sym_th=1e-3, sep_th=1e-1, skip_sep_test=False, verbose=False):if x == None:x = self.cache_dataplot_tree(self, x, in_var=in_var, style=style, sym_th=sym_th, sep_th=sep_th, skip_sep_test=skip_sep_test, verbose=verbose)逐行解释
@property
def connection_cost(self):
- 定义一个名为
connection_cost
的属性方法,它将计算并返回网络中所有线性层权重的“连接成本”。
python
复制
with torch.no_grad():cc = 0.for linear in self.linears:t = torch.abs(linear.weight)def get_coordinate(n):return torch.linspace(0,1,steps=n+1, device=self.device)[:n] + 1/(2*n)in_dim = t.shape[0]x_in = get_coordinate(in_dim)out_dim = t.shape[1]x_out = get_coordinate(out_dim)dist = torch.abs(x_in[:,None] - x_out[None,:])cc += torch.sum(dist * t)return cc
- 在不计算梯度的情况下,初始化
cc
为 0,遍历网络中的每个线性层。 - 对于每个线性层,计算其权重的绝对值
t
。 - 定义一个内部函数
get_coordinate
来获取每个维度上的坐标。 - 计算
in_dim
和out_dim
的坐标x_in
和x_out
。 - 计算输入和输出坐标之间的距离
dist
。 - 将距离与权重相乘并累加到
cc
中。 - 返回累加的连接成本
cc
。
python
复制
def swap(self, l, i1, i2):
- 定义一个名为
swap
的方法,它将交换第l
层线性层的第i1
和第i2
行(权重)以及对应的偏置。
python
复制
def swap_row(data, i1, i2):data[i1], data[i2] = data[i2].clone(), data[i1].clone()def swap_col(data, i1, i2):data[:,i1], data[:,i2] = data[:,i2].clone(), data[:,i1].clone()swap_row(self.linears[l-1].weight.data, i1, i2)
swap_row(self.linears[l-1].bias.data, i1, i2)
swap_col(self.linears[l].weight.data, i1, i2)
- 定义两个内部函数
swap_row
和swap_col
用于交换行和列。 - 使用这些函数交换指定层线性层权重和偏置的行和列。
python
复制
def auto_swap_l(self, l):
- 定义一个名为
auto_swap_l
的方法,它将自动交换第l
层的行,以最小化连接成本。
python
复制
num = self.width[l]
for i in range(num):ccs = []for j in range(num):self.swap(l,i,j)self.get_act()self.attribute()cc = self.connection_cost.detach().clone()ccs.append(cc)self.swap(l,i,j)j = torch.argmin(torch.tensor(ccs))self.swap(l,i,j)
- 获取第
l
层的宽度num
。 - 遍历所有行
i
,对于每一行,尝试与所有其他行j
交换。 - 在每次交换后,计算连接成本
cc
并存储在ccs
列表中。 - 恢复交换前的状态。
- 选择连接成本最小的交换,并执行最终的交换。
python
复制
def auto_swap(self):
- 定义一个名为
auto_swap
的方法,它将遍历所有层并调用auto_swap_l
来最小化整个网络的连接成本。
python
复制
depth = self.depth
for l in range(1, depth):self.auto_swap_l(l)
- 获取网络的深度
depth
。 - 遍历所有层(除了输入层),并对每一层调用
auto_swap_l
。
python
复制
def tree(self, x=None, in_var=None, style='tree', sym_th=1e-3, sep_th=1e-1, skip_sep_test=False, verbose=False):
- 定义一个名为
tree
的方法,它可能用于绘制或分析网络的树状结构。
python
复制
if x == None:x = self.cache_data
plot_tree(self, x, in_var=in_var, style=style, sym_th=sym_th, sep_th=sep_th, skip_sep_test=skip_sep_test, verbose=verbose)
- 如果没有提供输入数据
x
,则使用缓存的self.cache_data
。 - 调用
plot_tree
函数来绘制或分析网络,这个函数可能是在其他地方定义的。