diff --git a/include/aidge/data/Data.hpp b/include/aidge/data/Data.hpp
index d8412dbd4ddb4ec371649d180bce10a80dd624f3..a6ff03d36b662f4420424f930401844de25036d2 100644
--- a/include/aidge/data/Data.hpp
+++ b/include/aidge/data/Data.hpp
@@ -52,6 +52,7 @@ public:
         return mType;
     }
     virtual ~Data() = default;
+    virtual std::string toString() const = 0;
 
 private:
     const std::string mType;
@@ -84,4 +85,4 @@ namespace Aidge {
 inline auto format_as(DataType dt) { return EnumStrings<Aidge::DataType>::data[static_cast<int>(dt)]; }
 }
 
-#endif /* AIDGE_DATA_H_ */
\ No newline at end of file
+#endif /* AIDGE_DATA_H_ */
diff --git a/include/aidge/data/Tensor.hpp b/include/aidge/data/Tensor.hpp
index b82ec89d0096d47644e1bb4bd3819536ce7ccd66..1f9c5a5ec14cca4469b0329f2f968cf9dbc7b0de 100644
--- a/include/aidge/data/Tensor.hpp
+++ b/include/aidge/data/Tensor.hpp
@@ -445,7 +445,7 @@ public:
         set<expectedType>(getStorageIdx(coordIdx), value);
     }
 
-    std::string toString() const;
+    std::string toString() const override;
 
     inline void print() const { fmt::print("{}\n", toString()); }
 
diff --git a/include/aidge/graph/GraphView.hpp b/include/aidge/graph/GraphView.hpp
index 46fa56ef0e7d63ce10bb3c96a8d7e1c42b191322..0c6b7f03326491711fd57ed939642d1eec80b0d8 100644
--- a/include/aidge/graph/GraphView.hpp
+++ b/include/aidge/graph/GraphView.hpp
@@ -98,6 +98,8 @@ public:
      */
     void save(const std::string& path, bool verbose = false, bool showProducers = true) const;
 
+    void logOutputs(const std::string& dirName) const;
+
     /**
      * Check that a node is in the current GraphView.
      * @param nodePtr Node to check
@@ -283,7 +285,7 @@ public:
      *   added to the list, and so on.
      * - Any remaining nodes have no path to the root node and are added in
      *   arbitrary order. In this case, the ranking is not garanteed to be unique.
-     * 
+     *
      * If the ranking cannot be garanteed to be unique, the second item indicates
      * the rank from which unicity cannot be garanteed.
      * @return std::pair<std::vector<NodePtr>, size_t> Pair with the list of ranked
diff --git a/include/aidge/operator/Conv.hpp b/include/aidge/operator/Conv.hpp
index 82cd5df8e24457bd9f5e07c89826904c7d2283ad..517af5b050daa200e7d608aa71660c86b17701b0 100644
--- a/include/aidge/operator/Conv.hpp
+++ b/include/aidge/operator/Conv.hpp
@@ -27,21 +27,31 @@
 #include "aidge/utils/Types.h"
 
 namespace Aidge {
-enum class ConvAttr { StrideDims, DilationDims, InChannels, OutChannels, KernelDims };
+enum class ConvAttr { StrideDims, DilationDims, InChannels, OutChannels, KernelDims, NoBias };
 
 template <DimIdx_t DIM>
 class Conv_Op : public OperatorTensor,
                 public Registrable<Conv_Op<DIM>, std::string, std::shared_ptr<OperatorImpl>(const Conv_Op<DIM> &)>,
-                public StaticAttributes<ConvAttr, std::array<DimSize_t, DIM>, std::array<DimSize_t, DIM>, DimSize_t,
-                                       DimSize_t, std::array<DimSize_t, DIM>> {
+                public StaticAttributes<ConvAttr,
+                                        std::array<DimSize_t, DIM>,
+                                        std::array<DimSize_t, DIM>,
+                                        DimSize_t,
+                                        DimSize_t,
+                                        std::array<DimSize_t, DIM>,
+                                        bool> {
 
 public:
     static const std::string Type;
 
     Conv_Op() = delete;
 
-    using Attributes_ = StaticAttributes<ConvAttr, std::array<DimSize_t, DIM>, std::array<DimSize_t, DIM>,
-                                             DimSize_t, DimSize_t, std::array<DimSize_t, DIM>>;
+    using Attributes_ = StaticAttributes<ConvAttr,
+                                        std::array<DimSize_t, DIM>,
+                                        std::array<DimSize_t, DIM>,
+                                        DimSize_t,
+                                        DimSize_t,
+                                        std::array<DimSize_t, DIM>,
+                                        bool>;
     template <ConvAttr e>
     using attr = typename Attributes_::template attr<e>;
 
@@ -49,13 +59,15 @@ public:
                       DimSize_t outChannels,
                       const std::array<DimSize_t, DIM> &kernelDims,
                       const std::array<DimSize_t, DIM> &strideDims = create_array<DimSize_t,DIM>(1),
-                      const std::array<DimSize_t, DIM> &dilationDims = create_array<DimSize_t,DIM>(1))
+                      const std::array<DimSize_t, DIM> &dilationDims = create_array<DimSize_t,DIM>(1),
+                      bool noBias = false)
         : OperatorTensor(Type, 1, 2, 1),
           Attributes_(attr<ConvAttr::StrideDims>(strideDims),
                       attr<ConvAttr::DilationDims>(dilationDims),
                       attr<ConvAttr::InChannels>(inChannels),
                       attr<ConvAttr::OutChannels>(outChannels),
-                      attr<ConvAttr::KernelDims>(kernelDims)) {}
+                      attr<ConvAttr::KernelDims>(kernelDims),
+                      attr<ConvAttr::NoBias>(noBias)) {}
 
     /**
      * @brief Copy-constructor. Copy the operator attributes and its output tensor(s), but not its input tensors (the new operator has no input associated).
@@ -163,15 +175,17 @@ std::vector<std::pair<std::vector<Aidge::DimSize_t>, std::vector<DimSize_t>>> co
             std::vector<DimSize_t> weightIdxDims = std::vector<DimSize_t>(DIM+2, 0);
             weightIdxDims[0] = firstEltDims[1];
 
-            // Bias
-            const std::vector<DimSize_t> biasDims{outputDims[1]}; // the number of output channel
-            const std::vector<DimSize_t> biasIdxDims{firstEltDims[1]};
-
             // Result
             std::vector<std::pair<std::vector<Aidge::DimSize_t>, std::vector<DimSize_t>>> res;
             res.push_back(std::pair<std::vector<Aidge::DimSize_t>, std::vector<DimSize_t>>(inputIdxDims, inputDims));
             res.push_back(std::pair<std::vector<Aidge::DimSize_t>, std::vector<DimSize_t>>(weightIdxDims, weightDims));
-            res.push_back(std::pair<std::vector<Aidge::DimSize_t>, std::vector<DimSize_t>>(biasIdxDims, biasDims));
+
+            // Bias
+            if (! this->template getAttr<ConvAttr::NoBias>()){
+                const std::vector<DimSize_t> biasDims{outputDims[1]}; // the number of output channel
+                const std::vector<DimSize_t> biasIdxDims{firstEltDims[1]};
+                res.push_back(std::pair<std::vector<Aidge::DimSize_t>, std::vector<DimSize_t>>(biasIdxDims, biasDims));
+            }
             return res;
         }
         AIDGE_THROW_OR_ABORT(std::runtime_error, "Given outputDim out of range or output dim not forwarded yet.");
@@ -215,12 +229,14 @@ inline std::shared_ptr<Node> Conv(DimSize_t inChannels,
                                   const std::array<DimSize_t, DIM> &kernelDims,
                                   const std::string& name = "",
                                   const std::array<DimSize_t, DIM> &strideDims = create_array<DimSize_t,DIM>(1),
-                                  const std::array<DimSize_t, DIM> &dilationDims = create_array<DimSize_t,DIM>(1)) {
+                                  const std::array<DimSize_t, DIM> &dilationDims = create_array<DimSize_t,DIM>(1),
+                                  bool noBias = false) {
     // FIXME: properly handle default w&b initialization in every cases
     static_assert(DIM<=MaxDim,"Too many kernel dimensions required by Conv, not supported");
-    auto conv = std::make_shared<Node>(std::make_shared<Conv_Op<static_cast<DimIdx_t>(DIM)>>(inChannels, outChannels, kernelDims, strideDims, dilationDims), name);
+    auto conv = std::make_shared<Node>(std::make_shared<Conv_Op<static_cast<DimIdx_t>(DIM)>>(inChannels, outChannels, kernelDims, strideDims, dilationDims, noBias), name);
     addProducer(conv, 1, append(outChannels, append(inChannels, kernelDims)), "w");
-    addProducer(conv, 2, {outChannels}, "b");
+    addProducer(conv, 2, {(noBias ? 0 : outChannels)}, "b"); // already sets bias dims
+
     return conv;
 }
 
@@ -232,9 +248,10 @@ inline std::shared_ptr<Node> Conv(
     DimSize_t const (&kernelDims)[DIM],
     const std::string& name = "",
     const std::array<DimSize_t, DIM> &strideDims = create_array<DimSize_t,DIM>(1),
-    const std::array<DimSize_t, DIM> &dilationDims = create_array<DimSize_t,DIM>(1)) {
+    const std::array<DimSize_t, DIM> &dilationDims = create_array<DimSize_t,DIM>(1),
+    bool noBias = false) {
     static_assert(DIM<=MaxDim,"Too many kernel dimensions required by Conv, not supported");
-    return Conv(inChannels, outChannels, to_array(kernelDims), name, strideDims, dilationDims);
+    return Conv(inChannels, outChannels, to_array(kernelDims), name, strideDims, dilationDims, noBias);
 }
 }  // namespace Aidge
 
@@ -245,7 +262,8 @@ const char *const EnumStrings<Aidge::ConvAttr>::data[] = {
     "DilationDims",
     "InChannels",
     "OutChannels",
-    "KernelDims"
+    "KernelDims",
+    "NoBias"
 };
 }
 
diff --git a/include/aidge/operator/ConvDepthWise.hpp b/include/aidge/operator/ConvDepthWise.hpp
index 7fa9124d4c750cee53d9c4a402a2fa6196ac8158..035bd84b647bc7b4c57daa14d20ebe60e59e83c2 100644
--- a/include/aidge/operator/ConvDepthWise.hpp
+++ b/include/aidge/operator/ConvDepthWise.hpp
@@ -26,7 +26,7 @@
 #include "aidge/utils/Types.h"
 
 namespace Aidge {
-enum class ConvDepthWiseAttr { StrideDims, DilationDims, Channels, KernelDims };
+enum class ConvDepthWiseAttr { StrideDims, DilationDims, Channels, KernelDims, NoBias };
 
 template <DimIdx_t DIM>
 class ConvDepthWise_Op : public OperatorTensor,
@@ -35,7 +35,8 @@ class ConvDepthWise_Op : public OperatorTensor,
                                        std::array<DimSize_t, DIM>,
                                        std::array<DimSize_t, DIM>,
                                        DimSize_t,
-                                       std::array<DimSize_t, DIM>> {
+                                       std::array<DimSize_t, DIM>,
+                                       bool> {
 public:
     static const std::string Type;
 
@@ -45,19 +46,22 @@ public:
                                              std::array<DimSize_t, DIM>,
                                              std::array<DimSize_t, DIM>,
                                              DimSize_t,
-                                             std::array<DimSize_t, DIM>>;
+                                             std::array<DimSize_t, DIM>,
+                                             bool>;
     template <ConvDepthWiseAttr e>
     using attr = typename Attributes_::template attr<e>;
 
     constexpr ConvDepthWise_Op(const DimSize_t nbChannels,
                                const std::array<DimSize_t, DIM> &kernel_dims,
                                const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1),
-                               const std::array<DimSize_t, DIM> &dilation_dims = create_array<DimSize_t,DIM>(1))
+                               const std::array<DimSize_t, DIM> &dilation_dims = create_array<DimSize_t,DIM>(1),
+                               bool no_bias=false)
         : OperatorTensor(Type, 1, 2, 1),
           Attributes_(attr<ConvDepthWiseAttr::StrideDims>(stride_dims),
                       attr<ConvDepthWiseAttr::DilationDims>(dilation_dims),
                       attr<ConvDepthWiseAttr::Channels>(nbChannels),
-                      attr<ConvDepthWiseAttr::KernelDims>(kernel_dims)) {}
+                      attr<ConvDepthWiseAttr::KernelDims>(kernel_dims),
+                      attr<ConvDepthWiseAttr::NoBias>(no_bias)) {}
 
     /**
      * @brief Copy-constructor. Copy the operator attributes and its output tensor(s), but not its input tensors (the new operator has no input associated).
@@ -157,15 +161,17 @@ public:
             std::vector<DimSize_t> weightIdxDims = std::vector<DimSize_t>(DIM+2, 0);
             weightIdxDims[0] = firstEltDims[1];
 
-            // Bias
-            const std::vector<DimSize_t> biasDims{outputDims[1]}; // the number of output channel
-            const std::vector<DimSize_t> biasIdxDims{firstEltDims[1]};
 
             // Result
             std::vector<std::pair<std::vector<Aidge::DimSize_t>, std::vector<DimSize_t>>> res;
             res.push_back(std::pair<std::vector<Aidge::DimSize_t>, std::vector<DimSize_t>>(inputIdxDims, inputDims));
             res.push_back(std::pair<std::vector<Aidge::DimSize_t>, std::vector<DimSize_t>>(weightIdxDims, weightDims));
-            res.push_back(std::pair<std::vector<Aidge::DimSize_t>, std::vector<DimSize_t>>(biasIdxDims, biasDims));
+            // Bias
+            if (! this->template getAttr<ConvDepthWiseAttr::NoBias>()){
+                const std::vector<DimSize_t> biasDims{outputDims[1]}; // the number of output channel
+                const std::vector<DimSize_t> biasIdxDims{firstEltDims[1]};
+                res.push_back(std::pair<std::vector<Aidge::DimSize_t>, std::vector<DimSize_t>>(biasIdxDims, biasDims));
+            }
             return res;
         }
         AIDGE_THROW_OR_ABORT(std::runtime_error, "Given outputDim out of range or output dim not forwarded yet.");
@@ -196,12 +202,13 @@ inline std::shared_ptr<Node> ConvDepthWise(const DimSize_t nbChannels,
                                            const std::array<DimSize_t, DIM> &kernelDims,
                                            const std::string& name = "",
                                            const std::array<DimSize_t, DIM> &strideDims = create_array<DimSize_t,DIM>(1),
-                                           const std::array<DimSize_t, DIM> &dilationDims = create_array<DimSize_t,DIM>(1)) {
+                                           const std::array<DimSize_t, DIM> &dilationDims = create_array<DimSize_t,DIM>(1),
+                                           bool noBias=false) {
     // FIXME: properly handle default w&b initialization in every cases
     static_assert(DIM<=MaxDim,"Too many kernel dimensions required by ConvDepthWise, not supported");
-    auto convDW = std::make_shared<Node>(std::make_shared<ConvDepthWise_Op<static_cast<DimIdx_t>(DIM)>>(nbChannels, kernelDims, strideDims, dilationDims), name);
+    auto convDW = std::make_shared<Node>(std::make_shared<ConvDepthWise_Op<static_cast<DimIdx_t>(DIM)>>(nbChannels, kernelDims, strideDims, dilationDims, noBias), name);
     addProducer(convDW, 1, append(nbChannels, append(DimSize_t(1), kernelDims)), "w");
-    addProducer(convDW, 2, {nbChannels}, "b");
+    addProducer(convDW, 2, {(noBias ? 0 : nbChannels)}, "b");
     return convDW;
 }
 
@@ -212,16 +219,17 @@ inline std::shared_ptr<Node> ConvDepthWise(
     DimSize_t const (&kernelDims)[DIM],
     const std::string& name = "",
     const std::array<DimSize_t, DIM> &strideDims = create_array<DimSize_t,DIM>(1),
-    const std::array<DimSize_t, DIM> &dilationDims = create_array<DimSize_t,DIM>(1)) {
+    const std::array<DimSize_t, DIM> &dilationDims = create_array<DimSize_t,DIM>(1),
+    bool noBias=false) {
     static_assert(DIM<=MaxDim,"Too many kernel dimensions required by ConvDepthWise, not supported");
-    return ConvDepthWise(nbChannels, to_array(kernelDims), name, strideDims, dilationDims);
+    return ConvDepthWise(nbChannels, to_array(kernelDims), name, strideDims, dilationDims, noBias);
 }
 }  // namespace Aidge
 
 namespace {
 template <>
 const char *const EnumStrings<Aidge::ConvDepthWiseAttr>::data[] = {"StrideDims", "DilationDims", "Channels",
-                                                          "KernelDims"};
+                                                          "KernelDims", "NoBias"};
 }
 
 #endif /* AIDGE_CORE_OPERATOR_CONVDEPTHWISE_H_ */
diff --git a/include/aidge/operator/MetaOperatorDefs.hpp b/include/aidge/operator/MetaOperatorDefs.hpp
index 8f1de7c0e92558a4b47962c3a375764e1bd1c2ee..fb3aa6384fc703d758cb8753dcf54c4694f96bd4 100644
--- a/include/aidge/operator/MetaOperatorDefs.hpp
+++ b/include/aidge/operator/MetaOperatorDefs.hpp
@@ -35,11 +35,12 @@ inline std::shared_ptr<Node> PaddedConv(DimSize_t in_channels,
                                   const std::string& name = "",
                                   const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1),
                                   const std::array<DimSize_t, 2*DIM> &padding_dims = create_array<DimSize_t,2*DIM>(0),
-                                  const std::array<DimSize_t, DIM> &dilation_dims = create_array<DimSize_t,DIM>(1))
+                                  const std::array<DimSize_t, DIM> &dilation_dims = create_array<DimSize_t,DIM>(1),
+                                  bool no_bias = false)
 {
     // Construct micro-graph
     auto pad = Pad<DIM>(padding_dims, (!name.empty()) ? name + "_pad" : "", PadBorderType::Constant, 0.0);
-    auto conv = std::make_shared<Node>(std::make_shared<Conv_Op<static_cast<DimIdx_t>(DIM)>>(in_channels, out_channels, kernel_dims, stride_dims, dilation_dims), (!name.empty()) ? name + "_conv" : "");
+    auto conv = std::make_shared<Node>(std::make_shared<Conv_Op<static_cast<DimIdx_t>(DIM)>>(in_channels, out_channels, kernel_dims, stride_dims, dilation_dims, no_bias), (!name.empty()) ? name + "_conv" : "");
 
     auto metaOp = MetaOperator("PaddedConv", Sequential({pad, conv}), name);
     addProducer(metaOp, 1, append(out_channels, append(in_channels, kernel_dims)), "w");
@@ -56,9 +57,10 @@ inline std::shared_ptr<Node> PaddedConv(
     const std::string& name = "",
     const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1),
     const std::array<DimSize_t, 2*DIM> &padding_dims = create_array<DimSize_t,2*DIM>(0),
-    const std::array<DimSize_t, DIM> &dilation_dims = create_array<DimSize_t,DIM>(1))
+    const std::array<DimSize_t, DIM> &dilation_dims = create_array<DimSize_t,DIM>(1),
+    bool no_bias = false)
 {
-    return PaddedConv(in_channels, out_channels, to_array(kernel_dims), name, stride_dims, padding_dims, dilation_dims);
+    return PaddedConv(in_channels, out_channels, to_array(kernel_dims), name, stride_dims, padding_dims, dilation_dims, no_bias);
 }
 
 template <std::array<DimSize_t, 1>::size_type DIM>
@@ -67,11 +69,12 @@ inline std::shared_ptr<Node> PaddedConvDepthWise(const DimSize_t nb_channels,
                                   const std::string& name = "",
                                   const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1),
                                   const std::array<DimSize_t, 2*DIM> &padding_dims = create_array<DimSize_t,2*DIM>(0),
-                                  const std::array<DimSize_t, DIM> &dilation_dims = create_array<DimSize_t,DIM>(1))
+                                  const std::array<DimSize_t, DIM> &dilation_dims = create_array<DimSize_t,DIM>(1),
+                                  bool no_bias = false)
 {
     // Construct micro-graph
     auto pad = Pad<DIM>(padding_dims, (!name.empty()) ? name + "_pad" : "", PadBorderType::Constant, 0.0);
-    auto conv = std::make_shared<Node>(std::make_shared<ConvDepthWise_Op<static_cast<DimIdx_t>(DIM)>>(nb_channels, kernel_dims, stride_dims, dilation_dims), (!name.empty()) ? name + "_conv" : "");
+    auto conv = std::make_shared<Node>(std::make_shared<ConvDepthWise_Op<static_cast<DimIdx_t>(DIM)>>(nb_channels, kernel_dims, stride_dims, dilation_dims, no_bias), (!name.empty()) ? name + "_conv" : "");
 
     auto metaOp = MetaOperator("PaddedConvDepthWise", Sequential({pad, conv}), name);
     addProducer(metaOp, 1, append(nb_channels, append(DimSize_t(1), kernel_dims)), "w");
@@ -87,9 +90,10 @@ inline std::shared_ptr<Node> PaddedConvDepthWise(
     const std::string& name = "",
     const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1),
     const std::array<DimSize_t, 2*DIM> &padding_dims = create_array<DimSize_t,2*DIM>(0),
-    const std::array<DimSize_t, DIM> &dilation_dims = create_array<DimSize_t,DIM>(1))
+    const std::array<DimSize_t, DIM> &dilation_dims = create_array<DimSize_t,DIM>(1),
+    bool no_bias = false)
 {
-    return PaddedConvDepthWise(nb_channels, to_array(kernel_dims), name, stride_dims, padding_dims, dilation_dims);
+    return PaddedConvDepthWise(nb_channels, to_array(kernel_dims), name, stride_dims, padding_dims, dilation_dims, no_bias);
 }
 
 template <std::array<DimSize_t, 1>::size_type DIM>
diff --git a/include/aidge/utils/Directories.hpp b/include/aidge/utils/Directories.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..3bc07b9dd58e472096102c1b0c66971164d632a3
--- /dev/null
+++ b/include/aidge/utils/Directories.hpp
@@ -0,0 +1,83 @@
+/********************************************************************************
+ * Copyright (c) 2023 CEA-List
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+
+#ifndef AIDGE_DIRECTORIES_H_
+#define AIDGE_DIRECTORIES_H_
+
+
+#include <string>  // std::string
+#include <sstream> // std::stringstream
+#include <iostream>
+#include <sys/stat.h>
+#include <errno.h>
+
+#ifdef WIN32
+#include <direct.h>
+#else
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+namespace Aidge {
+
+    bool isNotValidFilePath(int c) {
+        return (iscntrl(c)
+            || c == '<'
+            || c == '>'
+            || c == ':'
+            || c == '"'
+            || c == '|'
+            || c == '?'
+            || c == '*');
+    }
+
+    std::string filePath(const std::string& str) {
+        std::string filePath(str);
+        std::replace_if(filePath.begin(), filePath.end(),
+                        isNotValidFilePath, '_');
+        return filePath;
+    }
+
+
+    bool createDirectories(const std::string& dirName)
+    {
+        std::stringstream path(dirName);
+        std::string dir;
+        std::string pathToDir("");
+        int status = 0;
+
+        while (std::getline(path, dir, '/') && status == 0) {
+            pathToDir += dir + '/';
+            struct stat fileStat;
+            if (stat(pathToDir.c_str(), &fileStat) != 0) {
+                // Directory does not exist
+    #ifdef WIN32
+                status = _mkdir(pathToDir.c_str());
+    #else
+    #if defined(S_IRWXU)
+                status = mkdir(pathToDir.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
+    #else
+                status = mkdir(pathToDir.c_str());
+    #endif
+    #endif
+            } else if (!S_ISDIR(fileStat.st_mode)) {
+                status = -1;
+            }
+        }
+        return (status == 0 || errno == EEXIST);
+    }
+
+
+}
+
+#endif //AIDGE_DIRECTORIES_H_
+
diff --git a/python_binding/data/pybind_Data.cpp b/python_binding/data/pybind_Data.cpp
index df3792fd784a2ef2b9418628959629ac59c04094..bca246c94434b280a12d070526ad4ffb2c7fbe7b 100644
--- a/python_binding/data/pybind_Data.cpp
+++ b/python_binding/data/pybind_Data.cpp
@@ -26,12 +26,11 @@ void init_Data(py::module& m){
     .value("Int64", DataType::Int64)
     .value("UInt8", DataType::UInt8)
     .value("UInt32", DataType::UInt32)
-    .value("UInt64", DataType::UInt64)   
+    .value("UInt64", DataType::UInt64)
     ;
 
-    py::class_<Data, std::shared_ptr<Data>>(m,"Data")
-    .def(py::init<const std::string&>());
+    py::class_<Data, std::shared_ptr<Data>>(m,"Data");
+
 
-    
 }
 }
diff --git a/python_binding/graph/pybind_GraphView.cpp b/python_binding/graph/pybind_GraphView.cpp
index a41d0d92835be2b5ef07d30c4a5233da1e3906b7..eae05d8e2c04a877e5942600d7120024f20c4788 100644
--- a/python_binding/graph/pybind_GraphView.cpp
+++ b/python_binding/graph/pybind_GraphView.cpp
@@ -30,7 +30,7 @@ void init_GraphView(py::module& m) {
           :param path: save location
           :type path: str
           )mydelimiter")
-
+          .def("log_outputs", &GraphView::logOutputs, py::arg("path"))
           .def("get_output_nodes", &GraphView::outputNodes,
           R"mydelimiter(
           Get set of output Nodes.
diff --git a/python_binding/operator/pybind_BatchNorm.cpp b/python_binding/operator/pybind_BatchNorm.cpp
index 7020c35f63880e77ecd3c2011a1b3c74bed847ed..087c232dc6a2977169e19ce4bdf0807adfc13d93 100644
--- a/python_binding/operator/pybind_BatchNorm.cpp
+++ b/python_binding/operator/pybind_BatchNorm.cpp
@@ -23,6 +23,9 @@ template <DimSize_t DIM>
 void declare_BatchNormOp(py::module& m) {
     const std::string pyClassName("BatchNormOp" + std::to_string(DIM) + "D");
     py::class_<BatchNorm_Op<DIM>, std::shared_ptr<BatchNorm_Op<DIM>>, Attributes, OperatorTensor>(m, pyClassName.c_str(), py::multiple_inheritance())
+    .def(py::init<float, float>(),
+        py::arg("epsilon"),
+        py::arg("momentum"))
     .def("get_inputs_name", &BatchNorm_Op<DIM>::getInputsName)
     .def("get_outputs_name", &BatchNorm_Op<DIM>::getOutputsName)
     .def("attributes_name", &BatchNorm_Op<DIM>::staticGetAttrsName);
diff --git a/python_binding/operator/pybind_Conv.cpp b/python_binding/operator/pybind_Conv.cpp
index aea402017622655a577ac4f9e207141bff01d70d..d1016869c3fec9cbc10f2d2c86f685f8787b1d3b 100644
--- a/python_binding/operator/pybind_Conv.cpp
+++ b/python_binding/operator/pybind_Conv.cpp
@@ -33,12 +33,14 @@ template <DimIdx_t DIM> void declare_ConvOp(py::module &m) {
                 DimSize_t,
                 const std::array<DimSize_t, DIM> &,
                 const std::array<DimSize_t, DIM> &,
-                const std::array<DimSize_t, DIM> &>(),
+                const std::array<DimSize_t, DIM> &,
+                bool>(),
         py::arg("in_channels"),
         py::arg("out_channels"),
         py::arg("kernel_dims"),
         py::arg("stride_dims"),
-        py::arg("dilation_dims"))
+        py::arg("dilation_dims"),
+        py::arg("no_bias"))
     .def("get_inputs_name", &Conv_Op<DIM>::getInputsName)
     .def("get_outputs_name", &Conv_Op<DIM>::getOutputsName)
     .def("attributes_name", &Conv_Op<DIM>::staticGetAttrsName)
@@ -51,18 +53,20 @@ template <DimIdx_t DIM> void declare_ConvOp(py::module &m) {
                                                          const std::vector<DimSize_t>& kernel_dims,
                                                          const std::string& name,
                                                          const std::vector<DimSize_t> &stride_dims,
-                                                         const std::vector<DimSize_t> &dilation_dims) {
+                                                         const std::vector<DimSize_t> &dilation_dims,
+                                                         bool noBias) {
         AIDGE_ASSERT(kernel_dims.size() == DIM, "kernel_dims size [{}] does not match DIM [{}]", kernel_dims.size(), DIM);
         AIDGE_ASSERT(stride_dims.size() == DIM, "stride_dims size [{}] does not match DIM [{}]", stride_dims.size(), DIM);
         AIDGE_ASSERT(dilation_dims.size() == DIM, "dilation_dims size [{}] does not match DIM [{}]", dilation_dims.size(), DIM);
 
-        return Conv<DIM>(in_channels, out_channels, to_array<DIM>(kernel_dims.begin()), name, to_array<DIM>(stride_dims.begin()), to_array<DIM>(dilation_dims.begin()));
+        return Conv<DIM>(in_channels, out_channels, to_array<DIM>(kernel_dims.begin()), name, to_array<DIM>(stride_dims.begin()), to_array<DIM>(dilation_dims.begin()), noBias);
     }, py::arg("in_channels"),
        py::arg("out_channels"),
        py::arg("kernel_dims"),
        py::arg("name") = "",
        py::arg("stride_dims") = std::vector<DimSize_t>(DIM,1),
-       py::arg("dilation_dims") = std::vector<DimSize_t>(DIM,1));
+       py::arg("dilation_dims") = std::vector<DimSize_t>(DIM,1),
+       py::arg("no_bias") = false);
 }
 
 
diff --git a/python_binding/operator/pybind_ConvDepthWise.cpp b/python_binding/operator/pybind_ConvDepthWise.cpp
index 83eac8742628bf2e0921e6a17dd46226c46fbea1..bbb94c3773e825cd5ee852243fa8db7a5bd763da 100644
--- a/python_binding/operator/pybind_ConvDepthWise.cpp
+++ b/python_binding/operator/pybind_ConvDepthWise.cpp
@@ -33,11 +33,13 @@ template <DimIdx_t DIM> void declare_ConvDepthWiseOp(py::module &m) {
   .def(py::init<const DimSize_t,
                 const std::array<DimSize_t, DIM> &,
                 const std::array<DimSize_t, DIM> &,
-                const std::array<DimSize_t, DIM> &>(),
+                const std::array<DimSize_t, DIM> &,
+                bool>(),
         py::arg("nb_channels"),
         py::arg("kernel_dims"),
         py::arg("stride_dims"),
-        py::arg("dilation_dims"))
+        py::arg("dilation_dims"),
+        py::arg("no_bias"))
   .def("get_inputs_name", &ConvDepthWise_Op<DIM>::getInputsName)
   .def("get_outputs_name", &ConvDepthWise_Op<DIM>::getOutputsName)
   .def("attributes_name", &ConvDepthWise_Op<DIM>::staticGetAttrsName);
@@ -46,17 +48,19 @@ template <DimIdx_t DIM> void declare_ConvDepthWiseOp(py::module &m) {
                                                                   const std::vector<DimSize_t>& kernel_dims,
                                                                   const std::string& name,
                                                                   const std::vector<DimSize_t> &stride_dims,
-                                                                  const std::vector<DimSize_t> &dilation_dims) {
+                                                                  const std::vector<DimSize_t> &dilation_dims,
+                                                                  bool no_bias) {
         AIDGE_ASSERT(kernel_dims.size() == DIM, "kernel_dims size [{}] does not match DIM [{}]", kernel_dims.size(), DIM);
         AIDGE_ASSERT(stride_dims.size() == DIM, "stride_dims size [{}] does not match DIM [{}]", stride_dims.size(), DIM);
         AIDGE_ASSERT(dilation_dims.size() == DIM, "dilation_dims size [{}] does not match DIM [{}]", dilation_dims.size(), DIM);
 
-        return ConvDepthWise<DIM>(nb_channels, to_array<DIM>(kernel_dims.begin()), name, to_array<DIM>(stride_dims.begin()), to_array<DIM>(dilation_dims.begin()));
+        return ConvDepthWise<DIM>(nb_channels, to_array<DIM>(kernel_dims.begin()), name, to_array<DIM>(stride_dims.begin()), to_array<DIM>(dilation_dims.begin()), no_bias);
     }, py::arg("nb_channenls"),
        py::arg("kernel_dims"),
        py::arg("name") = "",
        py::arg("stride_dims") = std::vector<DimSize_t>(DIM,1),
-       py::arg("dilation_dims") = std::vector<DimSize_t>(DIM,1));
+       py::arg("dilation_dims") = std::vector<DimSize_t>(DIM,1),
+       py::arg("no_bias")= false);
 
 }
 
diff --git a/python_binding/operator/pybind_MetaOperatorDefs.cpp b/python_binding/operator/pybind_MetaOperatorDefs.cpp
index 20a620cee737db5380ee7641b161cf6296ef7e5b..20cd3f156996c98bb64502a90ab98535f87cc2a3 100644
--- a/python_binding/operator/pybind_MetaOperatorDefs.cpp
+++ b/python_binding/operator/pybind_MetaOperatorDefs.cpp
@@ -30,21 +30,23 @@ template <DimIdx_t DIM> void declare_PaddedConvOp(py::module &m) {
                                                          const std::string& name,
                                                          const std::vector<DimSize_t> &stride_dims,
                                                          const std::vector<DimSize_t> &padding_dims,
-                                                         const std::vector<DimSize_t> &dilation_dims)
+                                                         const std::vector<DimSize_t> &dilation_dims,
+                                                         bool no_bias)
     {
         AIDGE_ASSERT(kernel_dims.size() == DIM, "kernel_dims size [{}] does not match DIM [{}]", kernel_dims.size(), DIM);
         AIDGE_ASSERT(stride_dims.size() == DIM, "stride_dims size [{}] does not match DIM [{}]", stride_dims.size(), DIM);
         AIDGE_ASSERT(padding_dims.size() == 2*DIM, "padding_dims size [{}] does not match DIM [{}]", padding_dims.size(), 2*DIM);
         AIDGE_ASSERT(dilation_dims.size() == DIM, "dilation_dims size [{}] does not match DIM [{}]", dilation_dims.size(), DIM);
 
-        return PaddedConv<DIM>(in_channels, out_channels, to_array<DIM>(kernel_dims.begin()), name, to_array<DIM>(stride_dims.begin()), to_array<2*DIM>(padding_dims.begin()), to_array<DIM>(dilation_dims.begin()));
+        return PaddedConv<DIM>(in_channels, out_channels, to_array<DIM>(kernel_dims.begin()), name, to_array<DIM>(stride_dims.begin()), to_array<2*DIM>(padding_dims.begin()), to_array<DIM>(dilation_dims.begin()), no_bias);
     }, py::arg("in_channels"),
        py::arg("out_channels"),
        py::arg("kernel_dims"),
        py::arg("name") = "",
        py::arg("stride_dims") = std::vector<DimSize_t>(DIM,1),
        py::arg("padding_dims") = std::vector<DimSize_t>(2*DIM,0),
-       py::arg("dilation_dims") = std::vector<DimSize_t>(DIM,1));
+       py::arg("dilation_dims") = std::vector<DimSize_t>(DIM,1),
+       py::arg("no_bias")= false);
 }
 
 template <DimIdx_t DIM> void declare_PaddedConvDepthWiseOp(py::module &m) {
@@ -53,20 +55,22 @@ template <DimIdx_t DIM> void declare_PaddedConvDepthWiseOp(py::module &m) {
                                                          const std::string& name,
                                                          const std::vector<DimSize_t> &stride_dims,
                                                          const std::vector<DimSize_t> &padding_dims,
-                                                         const std::vector<DimSize_t> &dilation_dims)
+                                                         const std::vector<DimSize_t> &dilation_dims,
+                                                         bool no_bias)
     {
         AIDGE_ASSERT(kernel_dims.size() == DIM, "kernel_dims size [{}] does not match DIM [{}]", kernel_dims.size(), DIM);
         AIDGE_ASSERT(stride_dims.size() == DIM, "stride_dims size [{}] does not match DIM [{}]", stride_dims.size(), DIM);
         AIDGE_ASSERT(padding_dims.size() == 2*DIM, "padding_dims size [{}] does not match DIM [{}]", padding_dims.size(), 2*DIM);
         AIDGE_ASSERT(dilation_dims.size() == DIM, "dilation_dims size [{}] does not match DIM [{}]", dilation_dims.size(), DIM);
 
-        return PaddedConvDepthWise<DIM>(nb_channels, to_array<DIM>(kernel_dims.begin()), name, to_array<DIM>(stride_dims.begin()), to_array<2*DIM>(padding_dims.begin()), to_array<DIM>(dilation_dims.begin()));
+        return PaddedConvDepthWise<DIM>(nb_channels, to_array<DIM>(kernel_dims.begin()), name, to_array<DIM>(stride_dims.begin()), to_array<2*DIM>(padding_dims.begin()), to_array<DIM>(dilation_dims.begin()), no_bias);
     }, py::arg("nb_channels"),
        py::arg("kernel_dims"),
        py::arg("name") = "",
        py::arg("stride_dims") = std::vector<DimSize_t>(DIM,1),
        py::arg("padding_dims") = std::vector<DimSize_t>(2*DIM,0),
-       py::arg("dilation_dims") = std::vector<DimSize_t>(DIM,1));
+       py::arg("dilation_dims") = std::vector<DimSize_t>(DIM,1),
+       py::arg("no_bias") = false);
 
 }
 
diff --git a/src/graph/GraphView.cpp b/src/graph/GraphView.cpp
index 005a7e679da5941d0995204b6c2a28a01ce376b4..edcea9544037634aede8102dadffdf1c75dd2427 100644
--- a/src/graph/GraphView.cpp
+++ b/src/graph/GraphView.cpp
@@ -26,6 +26,7 @@
 #include "aidge/operator/GenericOperator.hpp"
 #include "aidge/operator/MetaOperator.hpp"
 #include "aidge/utils/ErrorHandling.hpp"
+#include "aidge/utils/Directories.hpp"
 
 ///////////////////////////////////////////////////////
 //        FUNCTIONAL DESCRIPTION
@@ -193,6 +194,29 @@ void Aidge::GraphView::save(const std::string& path, bool verbose, bool showProd
     fmt::print(fp.get(), "\n");
 }
 
+void Aidge::GraphView::logOutputs(const std::string& dirName) const {
+  if (!Aidge::createDirectories(dirName)){
+    AIDGE_THROW_OR_ABORT(std::runtime_error, "Failed to create directory: {}.", dirName);
+  }
+  for (std::shared_ptr<Node> nodePtr : getNodes()) {
+
+    const std::string& nodePath = dirName + "/" + Aidge::filePath(nodePtr->name()) +"/";
+    if (!Aidge::createDirectories(nodePath)){
+      AIDGE_THROW_OR_ABORT(std::runtime_error, "Failed to create directory: {}.", nodePath);
+    }
+
+    for (IOIndex_t outIdx = 0; outIdx < nodePtr->nbOutputs(); ++outIdx) {
+      const std::string& inputPath = nodePath +"output_" + std::to_string(outIdx) + ".log";
+      auto fp = std::unique_ptr<FILE, decltype(&std::fclose)>(std::fopen(inputPath.c_str(), "w"), &std::fclose);
+      if (!fp) {
+        AIDGE_THROW_OR_ABORT(std::runtime_error,
+            "Could not create graph view log file: {}", inputPath);
+      }
+      fmt::print(fp.get(), "{}\n", nodePtr->getOperator()->getRawOutput(outIdx)->toString().c_str());
+    }
+  }
+}
+
 void Aidge::GraphView::setRootNode(NodePtr node) {
   AIDGE_ASSERT(mNodes.find(node) != mNodes.end(), "Root node is not in the GraphView!");
   mRootNode = node;
@@ -356,7 +380,7 @@ void Aidge::GraphView::forwardDims(const std::vector<std::vector<Aidge::DimSize_
                 }
             } else {
                 AIDGE_ASSERT(nodePtr->getOperator()->getRawInput(i)
-                    && !std::static_pointer_cast<Tensor>(nodePtr->getOperator()->getRawInput(i))->empty(), 
+                    && !std::static_pointer_cast<Tensor>(nodePtr->getOperator()->getRawInput(i))->empty(),
                   "Missing input#{} for node {} ({})", i, nodePtr->name(), nodePtr->type());
             }