Skip to content

Conversation

@PrasannaKasar
Copy link

@PrasannaKasar PrasannaKasar commented Aug 20, 2025

This Pull request:

This pull request adds the support for parsing Keras models with SOFIE

Changes or fixes:

Currently, SOFIE's existing Keras parser is written in C++ and is quite old. Although it's written in C++ but its actual parsing logic is written in Python. Additionally, it lacks support for parsing layers such as MaxPool.
This new parser is natively written in Python and uses of pythnoization to access C++ methods from SOFIE. The parser support latest version of Keras (Keras 3) and also has backwards compatibility with earlier versions of Keras 2. It also supports for both types of models i.e. models built using Keras' Functional as well as Sequential API.

Layers supported by the parser:

  • Add
  • AveragePool2D channels first
  • AveragePool2D channels last
  • BatchNorm
  • Concat
  • Conv2D channels first
  • Conv2D channels last
  • Conv2D padding same
  • Conv2D padding valid
  • Conv2D transpose
  • Dense
  • Dropout / Identity
  • Elu
  • Flatten
  • GlobalAveragePool2d channels first
  • GlobalAveragePool2d channels last
  • GRU
  • LayerNorm
  • LeakyReLU
  • LSTM
  • MaxPool2D channels first
  • MaxPool2D channels last
  • Multiply
  • Permute
  • Relu
  • Reshape
  • Selu
  • Sigmoid
  • SimpleRNN
  • Softmax
  • Subtract
  • Swish
  • Tanh

Checklist:

  • tested changes locally

@guitargeek
Copy link
Contributor

guitargeek commented Aug 20, 2025

Thank you very much for the PR! It's indeed better to implement this in Python.

Currently, SOFIE's existing Keras parser is written in C++ and is quite old.

While waiting for the review by @lmoneta and @sanjibansg, I have a general question.

What is the plan with the existing Keras parser in C++? If it is superseded by your new parser, can we remove it maybe in this PR as well? Like this we ensure that the users are not confused by two implementations, and also that the total maintenance cost doesn't increase.

@sanjibansg
Copy link
Contributor

Thank you very much for the PR! It's indeed better to implement this in Python.

Currently, SOFIE's existing Keras parser is written in C++ and is quite old.

While waiting for the review by @lmoneta and @sanjibansg, I have a general question.

What is the plan with the existing Keras parser in C++? If it is superseded by your new parser, can we remove it maybe in this PR as well? Like this we ensure that the users are not confused by two implementations, and also that the total maintenance cost doesn't increase.

I agree with @guitargeek, we will remove the existing C++ parser for Keras within this PR.

Copy link
Contributor

@sanjibansg sanjibansg left a comment

Choose a reason for hiding this comment

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

Did an initial review

ROOT/_pythonization/_tmva/_utils.py
ROOT/_pythonization/_tmva/_gnn.py)
ROOT/_pythonization/_tmva/_gnn.py
ROOT/_pythonization/_tmva/_sofie/_parser/_keras/__init__.py
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe better to have an additional CMake file for the keras parser files within their directory, so as to not add all of them here.

@@ -0,0 +1,202 @@
# functional_models.py
Copy link
Contributor

Choose a reason for hiding this comment

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

do we need this comment here?

Comment on lines 21 to 25
# # 1. Dropout (to test SOFIE's Identity operator)
# inp = layers.Input(shape=(10,))
# out = layers.Dropout(0.5)(inp)
# model = models.Model(inputs=inp, outputs=out)
# train_and_save(model, "Functional_Dropout_test")
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe we remove the commented out code if this is not used anyway?

Comment on lines 15 to 24
# 1. Dropout
# model = models.Sequential([
# layers.Input(shape=(10,)),
# layers.Dropout(0.5) # Dropout
# ])
# train_and_save(model, "Sequential_Dropout_test")

# 2. Binary Ops: Add, Subtract, Multiply are not typical in Sequential — skipping here

# 3. Concat (not applicable in Sequential without multi-input)
Copy link
Contributor

Choose a reason for hiding this comment

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

same comment as earlier for functional model

return op
else:
raise RuntimeError(
"TMVA::SOFIE - Unsupported - Operator Gemm does not yet support input type " + fLayerDType
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
"TMVA::SOFIE - Unsupported - Operator Gemm does not yet support input type " + fLayerDType
"TMVA::SOFIE - Unsupported - Operator Conv does not yet support input type " + fLayerDType

op = gbl_namespace.TMVA.Experimental.SOFIE.ROperator_BasicBinary(float,'TMVA::Experimental::SOFIE::EBasicBinaryOperator::Mul')(fX1, fX2, fY)
else:
raise RuntimeError(
"TMVA::SOFIE - Unsupported - Operator Identity does not yet support input type " + fLayerDType
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
"TMVA::SOFIE - Unsupported - Operator Identity does not yet support input type " + fLayerDType
"TMVA::SOFIE - Unsupported - Operator BasicBinary does not yet support input type " + fLayerDType

@@ -0,0 +1,48 @@
from cppyy import gbl as gbl_namespace
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe good to check if the datatypes for the input tensors are float since we don't support the other cases?

input = [str(i) for i in finput]
output = str(foutput[0])
axis = int(attributes["axis"])
op = gbl_namespace.TMVA.Experimental.SOFIE.ROperator_Concat(input, axis, 0, output)
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe good to check the datatype for the input tensor?

Copy link
Contributor

@guitargeek guitargeek left a comment

Choose a reason for hiding this comment

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

TensoFlow/Keras is too fragile to import unconditionally. You need to make sure it's not imported unconditionally, otherwise its presence will break several ROOT usecases, as we see in the tests. Always importing keras will also slow down importing ROOT, which is not desired

@@ -0,0 +1,479 @@
from ......_pythonization import pythonization
from cppyy import gbl as gbl_namespace
import keras
Copy link
Contributor

Choose a reason for hiding this comment

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

Here for example: instead of importing keras whenever the parsers are imported, you need to import it locally in the functions.

Copy link
Author

Choose a reason for hiding this comment

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

hi @guitargeek, thanks for noticing this
but the reason I'm importing keras in this file is because I need the it to load the saved model using the input model file path

Copy link
Contributor

@guitargeek guitargeek Sep 1, 2025

Choose a reason for hiding this comment

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

You can still import Keras in these files, not problem! Just not globally. You have to do it in the functions that use it.

This is not allowed:

import keras

def func():
    # do something with keras

This is allowed:

def func():
    import keras

    # do something with keras

Same with the from keras import xyz imports.

So the refactoring should be pretty easy, because you have nicely organized everything in functions already 🙂 And these local imports don't have overhead in Python, because of the caching of imported modules. Think of it as "lazily importing" the keras module only when you actually need it.

Copy link
Author

Choose a reason for hiding this comment

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

oh okay, got it. I'll make changes in the PR. Thanks



from ._gnn import RModel_GNN, RModel_GraphIndependent
from ._sofie._parser._keras.parser import RModelParser_Keras
Copy link
Contributor

Choose a reason for hiding this comment

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

But here is the main culprit: this global import is the entry point for all other imports I think.

Copy link
Author

@PrasannaKasar PrasannaKasar Aug 31, 2025

Choose a reason for hiding this comment

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

just a question, if we don't expose RModelParser_Keras.Parse class in the _tmva module, then how can we use it using pythonization?
I tried removing it but while using the parser it gave me this error
image
is there a way to tackle this?
@guitargeek @pcanal

@github-actions
Copy link

github-actions bot commented Aug 21, 2025

Test Results

    22 files      22 suites   3d 21h 11m 51s ⏱️
 3 742 tests  3 741 ✅ 0 💤  1 ❌
80 372 runs  80 359 ✅ 0 💤 13 ❌

For more details on these failures, see this check.

Results for commit 2e87835.

♻️ This comment has been updated with latest results.



from ._gnn import RModel_GNN, RModel_GraphIndependent
from ._sofie._parser._keras.parser import RModelParser_Keras
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
from ._sofie._parser._keras.parser import RModelParser_Keras

@vepadulano vepadulano removed their request for review August 25, 2025 15:43
…ers and added tests for them. Imported Keras within the required functions. Created new CMakeLists.txt file for the keras parser. Made changes in the pythonization CMake file to build the keras parser files
@PrasannaKasar
Copy link
Author

PrasannaKasar commented Sep 6, 2025

Hi @lmoneta @sanjibansg, I have made a few changes in the PR
Brief about those changes:

  • Fixed the issue with parsing the multi-dimensional batchnorm layer
  • Added support for parsing layers such as ELU activation, LayerNorm, Average Pooling 2D and Global Average Pooling 2D and created unit tests for them
  • Created a separate CMakeLists.txt file for the SOFIE Keras parser
  • Removed the legacy C++ parser
  • Made changes in the ROperator_LayerNormalization.hxx as suggested by @lmoneta
  • Fixed the issue in ROperator_Reshape.hxx while creating squeeze operator
  • keras is imported within the functions to prevent slowing down ROOT imports

can you please take a look at it and let me know if you require any changes?

Next steps:

  • Implement Conv2D transpose and dropout operator
  • Fuse batchnorm with conv2d to avoid repetitive use of transpose operator
  • Fix the issue with recurrent layers

@PrasannaKasar
Copy link
Author

PrasannaKasar commented Sep 6, 2025

Also, @guitargeek, @pcanal can you please take a look at the import statements of keras and whether they are suitable with ROOT now?
I’d appreciate your comments on the recent changes as well.

@guitargeek
Copy link
Contributor

Also, @guitargeek, @pcanal can you please take a look at the import statements of keras and whether they are suitable with ROOT now?

I started the CI. If there are no seemingly unrelated test failures because of unconditionally importing keras anymore, then we know it's okay!


return keras.__version__

keras_version = get_keras_version() No newline at end of file
Copy link
Contributor

@guitargeek guitargeek Sep 11, 2025

Choose a reason for hiding this comment

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

Looks much better already!

But there is still an implicit import of Keras in this place. You do this import in _tmva/__init__.py:

from ._sofie._parser._keras.parser import RModelParser_Keras

Importing a submodule implies importing all parent modules too. You you're also importing ._sofie._parser._keras. This import means running _keras/__init__.py, where you execute get_keras_version(), thus implicitly importing Keras.

You can fix this by removing the line with keras_version = get_keras_version(), and replace keras_version with get_keras_version() in the remaining code.

Copy link
Author

Choose a reason for hiding this comment

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

oh i overlooked this @guitargeek
is this the correct way to do it then?

image image

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes

Copy link
Author

Choose a reason for hiding this comment

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

hi @guitargeek i have made the changes
Can you please verify it once and start the tests?

Copy link
Author

Choose a reason for hiding this comment

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

@guitargeek I found the error
image
here, the CI uses python 3.9 and a Keras version older than 2.13
And that's why it might be saying that tensorflow.python.framework.errors_impl.InvalidArgumentError: Graph execution error in case of AveragePool2D

Copy link
Author

Choose a reason for hiding this comment

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

I added this sofie-keras-parser test to validate the parser's functionality

Copy link
Author

Choose a reason for hiding this comment

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

Apologies I didn't catch this before
Can I know which keras version does the CI use?
I'll run the tests locally then

Copy link
Contributor

Choose a reason for hiding this comment

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

The oldest Keras version in use by the CI is keras-2.13.1, on the alma8 platform. That's because this platform still uses Python 3.8.

But let's try to first try to fix the problems on the other platforms that use keras>=3.0, and then you think about if you need to support the older Keras versions later with @sanjibansg. Personally, I think it's totally valid to disable the functionality for Keras 2, as Keras 3 is out for almost 2 years.

If you need to know, you can check the version of Python packages that each platform is using by looking at the logs of the Docker container creation:
https://github.com/root-project/root-ci-images/actions

Copy link
Author

Choose a reason for hiding this comment

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

Thanks
I'll look into it

…aced import keras_version with get_keras_version and called it in necessary files
@guitargeek
Copy link
Contributor

We're making progress! But it seems we're still having unwanted implicit imports, as we see from the test failures:

2025-09-12T08:32:10.8961923Z  870/3621 Test   #36: pyunittests-bindings-pyroot-pythonizations-pyroot-import-load-libs ................................***Failed    1.93 sec
2025-09-12T08:32:10.8962555Z test_import (import_load_libs.ImportLoadLibs.test_import)
2025-09-12T08:32:10.8962838Z Test libraries loaded after importing ROOT ... ERROR
2025-09-12T08:32:10.8963000Z 
2025-09-12T08:32:10.8963078Z ======================================================================
2025-09-12T08:32:10.8963347Z ERROR: test_import (import_load_libs.ImportLoadLibs.test_import)
2025-09-12T08:32:10.8963701Z Test libraries loaded after importing ROOT
2025-09-12T08:32:10.8964392Z ----------------------------------------------------------------------
2025-09-12T08:32:10.8964636Z Traceback (most recent call last):
2025-09-12T08:32:10.8965031Z   File "/github/home/ROOT-CI/src/bindings/pyroot/pythonizations/test/import_load_libs.py", line 135, in test_import
2025-09-12T08:32:10.8965419Z     raise Exception(
2025-09-12T08:32:10.8965636Z Exception: Found not whitelisted libraries after importing ROOT:
2025-09-12T08:32:10.8965995Z  - libscipy_openblas64_-56d6093b
2025-09-12T08:32:10.8966173Z  - libgfortran-040039e1-0352e75f
2025-09-12T08:32:10.8966343Z  - libquadmath-96973f99-934c22de
2025-09-12T08:32:10.8966615Z If the test fails with a library that is loaded on purpose, please add it to the whitelist.
2025-09-12T08:32:10.8966846Z 
2025-09-12T08:32:10.8966948Z ----------------------------------------------------------------------
2025-09-12T08:32:10.8967160Z Ran 1 test in 1.777s
2025-09-12T08:32:10.8967253Z 
2025-09-12T08:32:10.8967314Z FAILED (errors=1)
2025-09-12T08:32:10.8967580Z CMake Error at /github/home/ROOT-CI/src/cmake/modules/RootTestDriver.cmake:232 (message):
2025-09-12T08:32:10.8967859Z   error code: 1
2025-09-12T08:32:10.8967945Z 
2025-09-12T08:32:10.8967949Z 
2025-09-12T08:32:10.8967952Z 

It looks like you're importing scipy somewhere in the global scope.

@PrasannaKasar
Copy link
Author

Oh,
But I have not used scipy

@@ -0,0 +1,544 @@
from ......_pythonization import pythonization
from cppyy import gbl as gbl_namespace
import numpy as np
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
import numpy as np

Just like with Keras, numpy should also not be imported globally, because it loads the BLAS libraries. That's also not great, because it increases the time to import ROOT and also the risk of symbol collisions. Also, it makes it harder to test ROOT with Python development builds where NumPy is not yet available.

The only libraries that can be unconditionally imported are the builtin Python libraries, and ROOT libraries themselves like cppyy.

@guitargeek
Copy link
Contributor

guitargeek commented Sep 12, 2025

Oh, But I have not used scipy

My bad, the problem was actually NumPy 🙂 See the comment above

@PrasannaKasar
Copy link
Author

Hi @guitargeek
Apologies I couldn't update you on this issue
I had exams at my university this week
I'll try to fix this and will let you know

@guitargeek
Copy link
Contributor

No problem! And thank you very much for your work. I'm glad to see some C++ code that calls into Python getting replaced, because this is some of the most fragile code in ROOT that is difficult to maintain 👍 Your implementation in Python makes more sense.

@guitargeek
Copy link
Contributor

Hi! I really liked this PR, because it replaced some fragile C++ code that called into Python with real Python code 🙂 @PrasannaKasar, do you have a plan to move forward here?

@PrasannaKasar
Copy link
Author

PrasannaKasar commented Oct 20, 2025

Yes @guitargeek , I am working on a few last issues left to cover, which were causing the tests to fail. Also, I am adding support to parse a couple of new Keras layers
I'll commit those changes asap
I apologise for the delay

@PrasannaKasar
Copy link
Author

@guitargeek i know i promised to push the changes and fix the issues on this pr but i still can't figure out the exact reasons behind the failing tests
I had a meet with Sanjiban a few days ago and we have narrowed down 2 reasons behind this:

  1. I am missing some Keras version which the CI is using and parser code isn't working probably for this
  2. I made changes to the CMake file of the Pythonization folder which might be incompatible to root and hence the CI isn't able to build the parser properly
    I'll keep you updated on this

… directory. Used import numpy statements within the parser functions to avoid slowing down the import of ROOT.
@guitargeek guitargeek mentioned this pull request Nov 8, 2025
@PrasannaKasar
Copy link
Author

@guitargeek can you please run the CI once?
I have reverted the CMakeLists.txt file of the Pythonization folder back to its previous version
Also imported numpy locally in the parser functions
And if anything still fails, I have added print statements in the test function to print the versions of Tensorflow/Keras used by the CI

@guitargeek
Copy link
Contributor

Hi! I have started the CI.

Please also take my draft PR for some inspiration on what needs to be fixed to make things work:
#20350

Especially, see the last 2 commits in my PR. Your PR is still missing the complete removal of the old C++ class and tests (your new Python implementation is not a "pythonization" of a C++ class, but just a new regular Python class).

Also, as you can see in my PR, things only work for some Linux platforms. Can you please take a look, and also integrate my suggestions from my draft PR into your PR? Thanks.

@PrasannaKasar
Copy link
Author

Hi @guitargeek I have few questions regarding the updates you made in the draft pr
image

  1. if we don't expose the RModel Keras parser class in C++, how can we run it?
    Since the parser is using C++ methods to parse the Keras layers into the rmodel object
  2. I really didn't understand the difference between Pythonization of a C++ class and a regular Python class. Could you please help me better understand this difference?

@guitargeek
Copy link
Contributor

guitargeek commented Nov 9, 2025

Hi, thanks for the questions! Good that you are asking.

  1. if we don't expose the RModel Keras parser class in C++, how can we run it?

You run it in Python like rmodel = ROOT.TMVA.Experimental.SOFIE.RModelParser_Keras.Parse(model_file_path, batch_size), just as in your test. You don't need a corresponding C++ class to use the RModelParser_Keras Python class that you introduce. Obviously you can't use your new Python class in C++, but I suppose you were aware of this when starting the rewrite project (or at least you should not. See the end of this comment for our stance on using the Python API in the ROOT C++ libraries).

Since the parser is using C++ methods to parse the Keras layers into the rmodel object

I don't understand your concern there, can yo clarify? Your parser is a Python class that calls into C++ methods via the ROOT Python interface. The parsers doesn't need to be a C++ class itself for that.

  1. I really didn't understand the difference between Pythonization of a C++ class and a regular Python class. Could you please help me better understand this difference?

Pythonizing a C++ class in ROOT means you change the bindings for an existing C++ class, e.g. with this @pythonization hook that you used:

@pythonization("RModelParser_Keras", ns="TMVA::Experimental::SOFIE")
def pythonize_rmodelparser_keras(klass):
    # Parameters:
    # klass: class to be pythonized 
    setattr(klass, "Parse", RModelParser_Keras.Parse)

The original C++ class is still there, but you replace or inject methods with more Pythonic version. In the case of the RModelParser_Keras, that doesn't make any sense becuase you have reduced it to a dummy class on the C++ side that provides no functionality anymore. So there is nothing to pythonize. Therefore, you can just put your class into the SOFIE namespace directly, as I suggest in my PR to do in _facade.py:

setattr(ns.Experimental.SOFIE, "RModelParser_Keras", _tmva.RModelParser_Keras)

The reason why the Pythonization doesn't work in the CI is that by default, we exclude all TMVA C++ classes that call back into Python from the build (tmva-pymva=OFF in the CMake configuration). These callbacks from C++ into Python are very fragile, as they regularly break with different versions of Python, or the libraries TensorFlow and Python. We want to get rid of using the Python C++ API in the ROOT libraries. If you really need to call from C++ into Python, you should use our TPython abstraction. Now, the RModelParser_Keras class that you attempt to pythonize resides in tmva/pymva, so it's no built in the CI and for the ROOT releases. Pythonizing a non-existing class with the @pythonization hook does nothing, and this is why also your pythonized class is not present in the CI runs of your PR.

@sanjibansg, FYI.

@PrasannaKasar
Copy link
Author

PrasannaKasar commented Nov 9, 2025

I don't understand your concern there, can yo clarify? Your parser is a Python class that calls into C++ methods via the ROOT Python interface. The parsers doesn't need to be a C++ class itself for that.

Hi @guitargeek I got it. It was a misunderstanding from my end, I thought we need the parser to be used in C++ as well. Hence, I was insisting on declaring a dummy class in C++. But I'll use the Python class itself now

@PrasannaKasar
Copy link
Author

PrasannaKasar commented Nov 9, 2025

The original C++ class is still there, but you replace or inject methods with more Pythonic version. In the case of the RModelParser_Keras, that doesn't make any sense becuase you have reduced it to a dummy class on the C++ side that provides no functionality anymore. So there is nothing to pythonize.

Ahh, I understood the difference now. We need Pythonization when we have to use the same C++ class in Python. But since we are removing the old C++ parser, we won't actually need this. Have I understood this correctly now?@guitargeek

@guitargeek
Copy link
Contributor

Yes, correct!

@guitargeek
Copy link
Contributor

But we should check with @sanjibansg if you want to continue supporting model parsing in C++ via the RSofieReader. If yes, this should be implemented using the existing abstraction to call Python code from C++, called TPython. Then you have to modify the RSofieReader to use your new parser class via TPython.

In other words, calling from C++ into Python should only happen via TPython, and not by including <Pyhon.h> and using the Python C API directly, as it is happening right now in the old implementation of the Keras parser:
https://github.com/root-project/root/blob/master/tmva/pymva/src/RModelParser_Keras.cxx#L22

@PrasannaKasar
Copy link
Author

PrasannaKasar commented Nov 9, 2025

@guitargeek I have been getting this issue since the first CI

  Default AvgPoolingOp only supports NHWC on device type 
  [[{{node model_9/average_pooling2d/AvgPool}}]] [Op:__inference_train_function_2459]

I looked into the Keras documentation but I could find only few mentions of it in their issues section and found out that the issue is not limited to any Keras version, but it appears to be fixed in the later versions of Keras 2
I also tried looking into the logs while creating the jobs, but I could not find any specific file for Mac14, Mac15, mac 26 (These are some of the docker images where the tests are failing)
I could find the log file for the Fedora though where a similar issue is occurring and found out that the Keras 3.20.0 was being used. I tried training the same AveragePool model on my machine (using CPU), but I didn't encounter it.
Can you please help me with this?

@guitargeek
Copy link
Contributor

I know nothing about Keras, sorry. @sanjibansg needs to help you here. My expertise is the ROOT Python interface in general.

About the Keras versions on the macOS runners: I just checked which versions are installed:

  • macOS 14: tensorflow 2.12.0, keras 2.12.0
  • macOS 15: tensorflow 2.15.1, keras 2.15.0
  • macOS 26: tensorflow 2.15.1, keras 2.15.0
  • macOS beta: tensorflow 2.15.1, keras 2.15.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants