-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathlipsnet.py
More file actions
110 lines (98 loc) · 3.97 KB
/
Copy pathlipsnet.py
File metadata and controls
110 lines (98 loc) · 3.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import torch
import torch.nn as nn
import torch.nn.functional as F
from functorch import jacrev, vmap
def mlp(sizes, hid_nonliear, out_nonliear):
# declare layers
layers = []
for j in range(len(sizes) - 1):
nonliear = hid_nonliear if j < len(sizes) - 2 else out_nonliear
layers += [nn.Linear(sizes[j], sizes[j + 1]), nonliear()]
# init weight
for i in range(len(layers) - 1):
if isinstance(layers[i], nn.Linear):
if isinstance(layers[i+1], nn.ReLU):
nn.init.kaiming_normal_(layers[i].weight, nonlinearity='relu')
elif isinstance(layers[i+1], nn.LeakyReLU):
nn.init.kaiming_normal_(layers[i].weight, nonlinearity='leaky_relu')
else:
nn.init.xavier_normal_(layers[i].weight)
return nn.Sequential(*layers)
class K_net(nn.Module):
def __init__(self, global_lips, k_init, sizes, hid_nonliear, out_nonliear) -> None:
super().__init__()
self.global_lips = global_lips
if global_lips:
# declare global Lipschitz constant
self.k = torch.nn.Parameter(torch.tensor(k_init, dtype=torch.float), requires_grad=True)
else:
# declare network
self.k = mlp(sizes, hid_nonliear, out_nonliear)
# set K_init
self.k[-2].bias.data += torch.tensor(k_init, dtype=torch.float).data
def forward(self, x):
if self.global_lips:
return F.softplus(self.k).repeat(x.shape[0]).unsqueeze(1)
else:
return self.k(x)
class LipsNet(nn.Module):
def __init__(self, f_sizes, f_hid_nonliear=nn.ReLU, f_out_nonliear=nn.Identity,
global_lips=True, k_init=100, k_sizes=None, k_hid_act=nn.Tanh, k_out_act=nn.Identity,
loss_lambda=0.1, eps=1e-4, squash_action=True) -> None:
super().__init__()
# declare network
self.f_net = mlp(f_sizes, f_hid_nonliear, f_out_nonliear)
self.k_net = K_net(global_lips, k_init, k_sizes, k_hid_act, k_out_act)
# declare hyperparameters
self.loss_lambda = loss_lambda
self.eps = eps
self.squash_action = squash_action
# initialize as eval mode
self.eval()
def forward(self, x):
# K(x) forward
k_out = self.k_net(x)
# L2 regularization backward
if self.training and k_out.requires_grad:
lips_loss = self.loss_lambda * (k_out ** 2).mean()
lips_loss.backward(retain_graph=True)
# f(x) forward
f_out = self.f_net(x)
# calcute jac matrix
if k_out.requires_grad:
jacobi = vmap(jacrev(self.f_net))(x)
else:
with torch.no_grad():
jacobi = vmap(jacrev(self.f_net))(x)
# jacobi.dim: (x.shape[0], f_out.shape[1], x.shape[1])
# (batch , f output dim , x feature dim)
# calcute jac norm
jac_norm = torch.norm(jacobi, 2, dim=(1,2)).unsqueeze(1)
# multi-dimensional gradient normalization (MGN)
action = k_out * f_out / (jac_norm + self.eps)
# squash action
if self.squash_action:
action = torch.tanh(action)
return action
if __name__ == "__main__":
intput_dim = 4
output_dim = 2
net = LipsNet(f_sizes=[intput_dim,64,64,output_dim], f_hid_nonliear=nn.ReLU, f_out_nonliear=nn.Identity,
global_lips=False, k_init=1, k_sizes=[intput_dim,32,1], k_hid_act=nn.Tanh, k_out_act=nn.Softplus,
loss_lambda=0.1, eps=1e-4, squash_action=True)
optimizer = torch.optim.Adam([
{'params':net.f_net.parameters(), 'lr':3e-5},
{'params':net.k_net.parameters(), 'lr':1e-5}])
input = torch.rand(128, intput_dim)
# training
net.train()
out = net(input)
loss = (out ** 2).mean()
loss.backward()
optimizer.step()
optimizer.zero_grad()
net.eval()
# eval
net.eval()
input = torch.rand(128, intput_dim)
out = net(input)