Is sigmoid() defined inside the class? Alignment seems to suggest so, but then it should be called as self.sigmoid() and should take "self" as a first argument.
Not necessarily - adding "self" makes it into a member function (method). Without self it is a so called "static" function and needs to be called as "Classname.sigmoid(...)" where Classname is the name of the class it was defined in. This is useful for scoping (polluting the global/module namespace is bad style) and typically used for various factory functions or utilities that don't require an object instance to work.
There is a third option, a so called class method - then the first argument isn't "self" (instance/object) but the class itself it was called on. That has sometimes uses too.
However, in modern Python one is supposed to annotate both static and class methods using appropriate decorators - @classmethod and @staticmethod to make this explicit. It will work without but it is a bad style - and a class method can't be really declared otherwise. Python's "self" is not a special keyword, only a conventional name for the first argument - so without the decorator Python wouldn't know if a normal method or a class method was intended.
And in recent versions there is optional strong typing, but it kind of defeats the purpose.
One isn't required to use it. The entire point of it is that one can catch many errors that would otherwise only manifest themselves at runtime by throwing exceptions because some unexpected type/object has been passed into a function, or perhaps the object has the right type but someone forgot to update some part of the code after refactoring - and now some attribute is missing/has been renamed, resulting in AttributeError exceptions.
Unless you have a 100% test coverage (and even if you do, it doesn't ensure that one is actually testing meaningful things) this is bound to happen to you sooner or later, especially for various corner cases on code paths that don't get exercised often. This is a major pain with dynamic languages like Python or Javascript and that's why in both cases the optional types are available (Typescript in the case of Javascript). It is one more tool to make production Python code more robust without sacrificing flexibility of the language.
There are also some other applications of it - e.g. the IDEs use the typing information where available to both lint your code checking for such mistakes and also to provide better suggestions/completions. That's what VSCode does, for example - Microsoft's Pylance plugin is all based around static analysis and typing. If the typing information isn't available, one gets only very basic functionality from the IDE. Also JITs and the language virtual machines can use the typing info for optimizations (Python doesn't do that yet).
Adding optional strong typing to a dynamically typed language for these reasons is nothing new - e.g. Common Lisp had optional types for decades. There it helps both with generating diagnostics and also with compiler optimizations.