Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RNO and dissipative loss (MNO) #248

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open

Conversation

mliuschi
Copy link
Member

No description provided.

neuralop/layers/recurrent_layers.py Show resolved Hide resolved
neuralop/layers/recurrent_layers.py Show resolved Hide resolved
neuralop/models/rno.py Show resolved Hide resolved
if self.domain_padding:
h = h.unsqueeze(1) # add dim for padding compatibility
h = self.domain_padding.unpad(h)
h = h[:,0] # remove extraneous dim
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just squeeze?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you referring to squeeze for the last line? In general, if I were to squeeze here, I would be wary of accidentally deleting the out_channels dimension if it is 1.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, just squeeze that particular dimension, ie. h = h.squeeze(1)

neuralop/models/rno.py Show resolved Hide resolved
pred_x = self.layers[i](x, init_hidden_states[i])
if i < self.n_layers - 1:
if self.residual:
x = x + pred_x
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need to apply self.layers[i].transform to x in case of output_scaling factor passed by user

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I have implemented a transform() function for the recurrent layers. Do I need to do this? I thought any output_scale_factor differences were handled by the Fourier blocks under the hood.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but what happens if self.layers[i] downscales (or upscales) the input? Or does this never happen?

Copy link
Member Author

@mliuschi mliuschi Mar 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, we need transform after each spectral convolution in case we upscale/downscale the input? In recurrent_layers.py, I operate directly on FNO blocks, so I think any changes in resolution are dealt with inside the blocks. What do you think?

neuralop/training/losses.py Outdated Show resolved Hide resolved
Copy link
Member

@JeanKossaifi JeanKossaifi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @mliuschi! Looks good overall!

Copy link
Member

@JeanKossaifi JeanKossaifi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @mliuschi, it looks much better! I left some comments, mostly just style and clarity.

else:
self.domain_shape = None

def sample_uniform_spherical_shell(self, shape: tuple): # shape is (batch_size, dim1, ..., dimk, out_dim)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a docstring here to explain what the shape correspond to, what the function does and how it should be used?

ndim = torch.prod(torch.tensor(shape))
inner_radius, outer_radius = self.radii

samp_radii = torch.zeros(npoints, 1).uniform_(inner_radius, outer_radius)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sample_radii? I like to spell out the variables.

domain_shape : int tuple
Shape of PDE domain (resolution)
"""
def __init__(self, data_loss, diss_y_rule, loss_weight: float, diss_radii: tuple, out_dim: int, domain_shape: tuple):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not explicitly pass the inner and outer radius for clarity instead of a tuple?
Should we call out_dim out_channels? And domain shape, is it just the spatial domain? If so we can say in the docstring, excludes channels.

Copy link
Member Author

@mliuschi mliuschi Mar 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I have it set up, the inner and outer radii are saved as fields in the DissipativeLoss object. What do you think?

"""Dissipative loss is used to avoid model blow-up and to encourage the model
to learn dissipative dynamics (particularly in chaotic systems). The dissipative
loss samples points uniformly and randomly in a spherical shell around the
attractor of a chaotic system (at user-specified radii) and encourages
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Csn you expand on the radii -let's also describe each radius individually?

loss samples points uniformly and randomly in a spherical shell around the
attractor of a chaotic system (at user-specified radii) and encourages
that a learned model's predictions on this shell scale inwards by some
user-specified dissipative rule.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you have some higher, less technical level intuition here? Readers can refer to the paper for details.

samp_radii = torch.zeros(npoints, 1).uniform_(inner_radius, outer_radius)
vec = torch.randn(npoints, ndim) # ref: https://mathworld.wolfram.com/SpherePointPicking.html
vec = torch.nn.functional.normalize(vec, p=2.0, dim=1)
samp_radii = torch.broadcast_to(samp_radii, vec.shape)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, try not to change the semantic meaning of variables half way through as it makes the code quite hard to read.


return (samp_radii * vec).reshape((npoints, *shape))

def __call__(self, x, y, x_diss, pred_diss, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a short docstring as the arguments are non-standard.
Usually our losses take as input y_pred, y.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! I have also added a "Usage example" in the main docstring for the class as well

pred_x = self.layers[i](x, init_hidden_states[i])
if i < self.n_layers - 1:
if self.residual:
x = x + pred_x
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but what happens if self.layers[i] downscales (or upscales) the input? Or does this never happen?

if self.domain_padding:
h = h.unsqueeze(1) # add dim for padding compatibility
h = self.domain_padding.unpad(h)
h = h[:,0] # remove extraneous dim
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, just squeeze that particular dimension, ie. h = h.squeeze(1)

neuralop/models/rno.py Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants