#About OOP
About OOP
I tried to exploit OOP as much as I could, instead of using a procedural approach to write the algorithm.
Although I believe that your approach was fine, using OOP for the sake of OOP is something I would rather warn against. There is a talk about this here.
Comments
def __init__(self, dataset, learning_rate, num_iterations):
"""
Class constructor.
"""
The comment Class constructor is redundant and unecessary, I would instead explain the parameters of __init__ in the doc string.
self.M = len(self.dataset) # 100.
Is the # 100 saying that the len(self.dataset) is always going to be 100? It might be 100 in this case, but I highly doubt you can ensure that.
Default values
Have you considered putting default values for learning_rate and num_iterations.? If we want a default of 100 and 0.001 for num_iterations and learning_rate respectively, you could rewrite __init__ like:
def __init__(self, dataset, learning_rate=0.001, num_iterations=100):
Private methods
Do you really want do_gradient_step(self) to be considered public? Yes, there are no "true" private methods, but the convention is to put one underscore before the name to indicate it is private. Honestly, I would just call it _step(self).
Indentation
if __name__ == "__main__": main()
should really be:
if __name__ == "__main__":
main()
To comply with PEP 8.