Skip to content

Commit

Permalink
Add docs versioning
Browse files Browse the repository at this point in the history
Former-commit-id: d65efc5
  • Loading branch information
MaxHalford committed Sep 5, 2020
1 parent eeb00df commit f6febb2
Show file tree
Hide file tree
Showing 14 changed files with 143 additions and 74 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ docs/user-guide/*.md
docs/user-guide/*_files/
docs/examples/*.md
docs/examples/*_files/
docs/api-reference/
docs/api/
site

# Byte-compiled / optimized / DLL files
Expand Down
25 changes: 16 additions & 9 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,19 @@ jobs:
script: *pypi-script
if: tag IS present

- stage: docs
- stage: dev-docs
install:
- sudo apt-get install graphviz pandoc
- python setup.py install
- pip install -e ".[docs]"
script: make doc
after_success:
- git config --global user.email "[email protected]"
- git config --global user.name "MaxHalford"
- mike deploy dev --push
if: branch = the-merge

- stage: release-docs
install:
- sudo apt-get install graphviz pandoc
- python setup.py install
Expand All @@ -65,14 +77,9 @@ jobs:
after_success:
- git config --global user.email "[email protected]"
- git config --global user.name "MaxHalford"
- git config --global push.default matching
- git clone https://${GH_TOKEN}@github.com/creme-ml/creme-ml.github.io.git creme-ml.github.io
- cd creme-ml.github.io
- rm -r *
- cp -r ../site/* .
- git add -A
- git commit --allow-empty -m "Travis build number $TRAVIS_BUILD_NUMBER"
- git push --set-upstream origin master
- CREME_VERSION=$(python -c "import creme; print(creme.__version__)")
- mike deploy ${CREME_VERSION} latest --push
- mike set-default latest --push
if: tag IS present

env:
Expand Down
19 changes: 12 additions & 7 deletions creme/optim/schedulers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@


class Scheduler(abc.ABC):
"""Can be used to program the learning rate shedule of an `optim.Optimizer`."""

@abc.abstractmethod
def get(self, t: int) -> float:
"""Returns the learning rate at a given iteration.
Parameters:
t: The iteration number.
Parameters
----------
t
The iteration number.
"""

Expand All @@ -34,8 +37,9 @@ def __repr__(self):
class Constant(Scheduler):
"""Always uses the same learning rate.
Parameters:
learning_rate
Parameters
----------
learning_rate
"""

Expand All @@ -55,9 +59,10 @@ class InverseScaling(Scheduler):
where $p$ is a user-defined parameter.
Parameters:
learning_rate
power
Parameters
----------
learning_rate
power
"""

Expand Down
2 changes: 1 addition & 1 deletion docs/.pages
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
arrange:
- index.md
- user-guide
- api-reference
- api
- examples
- releases
5 changes: 5 additions & 0 deletions docs/css/version-select.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@media only screen and (max-width:76.1875em) {
#version-selector {
padding: .6rem .8rem;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"In this tutorial, we are going to explore MF algorithms available in `creme` and test them on a movie recommendation problem with the MovieLens 100K dataset. This latter is a collection of movie ratings (from 1 to 5) that includes various information about both the items and the users. We can access it from the [creme.datasets](https://creme-ml.github.io/api-reference/overview/#datasets) module:"
"In this tutorial, we are going to explore MF algorithms available in `creme` and test them on a movie recommendation problem with the MovieLens 100K dataset. This latter is a collection of movie ratings (from 1 to 5) that includes various information about both the items and the users. We can access it from the [creme.datasets](https://creme-ml.github.io/api/overview/#datasets) module:"
]
},
{
Expand Down Expand Up @@ -200,7 +200,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we can do machine learning and explore available models in [creme.reco](https://creme-ml.github.io/api-reference/overview/#reco) module starting with the baseline model. It extends our naive prediction by adding to the global running mean two bias terms characterizing the user and the item discrepancy from the general tendency. The model equation is defined as:\n",
"Now we can do machine learning and explore available models in [creme.reco](https://creme-ml.github.io/api/overview/#reco) module starting with the baseline model. It extends our naive prediction by adding to the global running mean two bias terms characterizing the user and the item discrepancy from the general tendency. The model equation is defined as:\n",
"\n",
"$$\n",
"\\normalsize\n",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
"\n",
"Higher-order FM will be covered in a following section, just note that factorization models express their power in sparse settings, which is also where higher-order interactions are hard to estimate.\n",
"\n",
"Strong emphasis must be placed on feature engineering as it allows FM to mimic most factorization models and significantly impact its performance. High cardinality categorical variables one hot encoding is the most frequent step before feeding the model with data. For more efficiency, `creme` FM implementation considers string values as categorical variables and automatically one hot encode them. FM models have their own module [creme.facto](https://creme-ml.github.io/api-reference/overview/#facto)."
"Strong emphasis must be placed on feature engineering as it allows FM to mimic most factorization models and significantly impact its performance. High cardinality categorical variables one hot encoding is the most frequent step before feeding the model with data. For more efficiency, `creme` FM implementation considers string values as categorical variables and automatically one hot encode them. FM models have their own module [creme.facto](https://creme-ml.github.io/api/overview/#facto)."
]
},
{
Expand Down Expand Up @@ -153,7 +153,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Both MAE are very close to each other (0.7486 vs 0.7485) showing that we almost reproduced [reco.BiasedMF](https://creme-ml.github.io/api-reference/reco/BiasedMF/) algorithm. The cost is a naturally slower running time as FM implementation offers more flexibility."
"Both MAE are very close to each other (0.7486 vs 0.7485) showing that we almost reproduced [reco.BiasedMF](https://creme-ml.github.io/api/reco/BiasedMF/) algorithm. The cost is a naturally slower running time as FM implementation offers more flexibility."
]
},
{
Expand Down Expand Up @@ -557,4 +557,4 @@
},
"nbformat": 4,
"nbformat_minor": 4
}
}
2 changes: 1 addition & 1 deletion docs/getting-started.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@
"source": [
"As we can see, the model is performing much better now that the data is being scaled. Under the hood, the standard scaler maintains a running average and a running variance for each feature in the dataset. Each feature is thus scaled according to the average and the variance seen up to every given point in time.\n",
"\n",
"This concludes this short guide to getting started with `creme`. There is a lot more to discover and understand. Head towards the [user guide](user-guide) for recipes on how to perform common machine learning tasks. You may also consult the [API reference](api-reference), which is a catalogue of all the modules that `creme` exposes. Finally, the [examples](examples) section contains comprehensive examples for various usecases."
"This concludes this short guide to getting started with `creme`. There is a lot more to discover and understand. Head towards the [user guide](user-guide) for recipes on how to perform common machine learning tasks. You may also consult the [API reference](api), which is a catalogue of all the modules that `creme` exposes. Finally, the [examples](examples) section contains comprehensive examples for various usecases."
]
}
],
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ title: Welcome

`creme` is a Python library for [online machine learning](https://www.wikiwand.com/en/Online_machine_learning). All the tools in the library can be updated with a single observation at a time, and can therefore be used to process streaming data. This is general-purpose library, and therefore caters to different machine learning problems, including regression, classification, and unsupervised learning. It can also be used for adhoc tasks, such as computing online metrics, as well as performing drift detection.

We recommend that new users begin with the [getting started guide](getting-started.md). Afterwards, you can check out the [user guide](user-guide/reading-data), which contains recipes and tutorials on specific topics. The [API reference](api-reference/overview) is a catalogue of all the available modules, classes, and functions that are available. Finally, check out the [examples section](examples/batch-to-online) for comprehensive usage examples.
We recommend that new users begin with the [getting started guide](getting-started.md). Afterwards, you can check out the [user guide](user-guide/reading-data), which contains recipes and tutorials on specific topics. The [API reference](api/overview) is a catalogue of all the available modules, classes, and functions that are available. Finally, check out the [examples section](examples/batch-to-online) for comprehensive usage examples.

Feel free to contribute in any way you like, we're always open to new ideas and approaches. You can also take a look at the [issue tracker](https://github.com/creme-ml/creme/issues) and the [icebox](https://github.com/creme-ml/creme/projects/2) to see if anything takes your fancy. Please check out the [contribution guidelines](https://github.com/creme-ml/creme/blob/master/CONTRIBUTING.md) if you want to bring modifications to the code base. You can view the list of people who have contributed [here](https://github.com/creme-ml/creme/graphs/contributors).
49 changes: 49 additions & 0 deletions docs/js/version-select.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
window.addEventListener("DOMContentLoaded", function() {
// This is a bit hacky. Figure out the base URL from a known CSS file the
// template refers to...
var ex = new RegExp("/?css/version-select.css$");
var sheet = document.querySelector('link[href$="version-select.css"]');

var ABS_BASE_URL = sheet.href.replace(ex, "");
var CURRENT_VERSION = ABS_BASE_URL.split("/").pop();

function makeSelect(options, selected) {
var select = document.createElement("select");
select.classList.add("form-control");

options.forEach(function(i) {
var option = new Option(i.text, i.value, undefined,
i.value === selected);
select.add(option);
});

return select;
}

var xhr = new XMLHttpRequest();
xhr.open("GET", ABS_BASE_URL + "/../versions.json");
xhr.onload = function() {
var versions = JSON.parse(this.responseText);

var realVersion = versions.find(function(i) {
return i.version === CURRENT_VERSION ||
i.aliases.includes(CURRENT_VERSION);
}).version;

var select = makeSelect(versions.map(function(i) {
return {text: i.title, value: i.version};
}), realVersion);
select.addEventListener("change", function(event) {
window.location.href = ABS_BASE_URL + "/../" + this.value;
});

var container = document.createElement("div");
container.id = "version-selector";
container.className = "md-nav__item";
container.appendChild(select);

var sidebar = document.querySelector(".md-nav--primary > .md-nav__list");
sidebar.parentNode.insertBefore(container, sidebar);
};
xhr.send();
});
56 changes: 28 additions & 28 deletions docs/scripts/index.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""This script is responsible for building the API reference. The API reference is located in
docs/api-reference. The script scans through all the modules, classes, and functions. It processes
docs/api. The script scans through all the modules, classes, and functions. It processes
the __doc__ of each object and formats it so that MkDocs can process it in turn.
"""
Expand Down Expand Up @@ -86,30 +86,31 @@ def __init__(self):
}

def index_module(mod_name, mod, path):
path = os.path.join(path, mod_name).replace('/', '.')
path = os.path.join(path, mod_name)
dotted_path = path.replace('/', '.')

for func_name, func in inspect.getmembers(mod, inspect.isfunction):
for e in (
f'{mod_name}.{func_name}',
f'{path}.{func_name}',
f'{dotted_path}.{func_name}',
f'{func.__module__}.{func_name}'
):
path_index[e] = os.path.join(path, snake_to_kebab(func_name))
name_index[e] = f'{path}.{func_name}'
name_index[e] = f'{dotted_path}.{func_name}'

for klass_name, klass in inspect.getmembers(mod, inspect.isclass):
for e in (
f'{mod_name}.{klass_name}',
f'{path}.{klass_name}',
f'{dotted_path}.{klass_name}',
f'{klass.__module__}.{klass_name}'
):
path_index[e] = os.path.join(path, klass_name)
name_index[e] = f'{path}.{klass_name}'
name_index[e] = f'{dotted_path}.{klass_name}'

for submod_name, submod in inspect.getmembers(mod, inspect.ismodule):
if submod_name not in mod.__all__ or submod_name == 'typing':
continue
for e in (f'{mod_name}.{submod_name}', f'{path}.{submod_name}'):
for e in (f'{mod_name}.{submod_name}', f'{dotted_path}.{submod_name}'):
path_index[e] = os.path.join(path, snake_to_kebab(submod_name))

# Recurse
Expand All @@ -118,10 +119,6 @@ def index_module(mod_name, mod, path):
for mod_name, mod in modules.items():
index_module(mod_name, mod, path='')

# Prepend the location of the API reference to each path
for k, v in path_index.items():
path_index[k] = os.path.join('/api-reference', v)

# Prepend creme to each index entry
for k in list(path_index.keys()):
path_index[f'creme.{k}'] = path_index[k]
Expand All @@ -131,29 +128,30 @@ def index_module(mod_name, mod, path):
self.path_index = path_index
self.name_index = name_index

def linkify(self, text, use_fences):
def linkify(self, text, use_fences, depth):
path = self.path_index.get(text)
name = self.name_index.get(text)
if path and name:
backwards = '../' * (depth + 1)
if use_fences:
return f'[`{name}`]({path})'
return f'[{name}]({path})'
return f'[`{name}`]({backwards}{path})'
return f'[{name}]({backwards}{path})'
return None

def linkify_fences(self, text):
def linkify_fences(self, text, depth):
between_fences = re.compile('`[\w\.]+\.\w+`')
return between_fences.sub(lambda x: self.linkify(x.group().strip('`'), True) or x.group(), text)
return between_fences.sub(lambda x: self.linkify(x.group().strip('`'), True, depth) or x.group(), text)

def linkify_dotted(self, text):
def linkify_dotted(self, text, depth):
dotted = re.compile('\w+\.[\.\w]+')
return dotted.sub(lambda x: self.linkify(x.group(), False) or x.group(), text)
return dotted.sub(lambda x: self.linkify(x.group(), False, depth) or x.group(), text)


def concat_lines(lines):
return inspect.cleandoc(' '.join('\n\n' if line == '' else line for line in lines))


def print_docstring(obj, file):
def print_docstring(obj, file, depth):
"""Prints a classes's docstring to a file.
"""
Expand All @@ -163,8 +161,8 @@ def print_docstring(obj, file):
printf = functools.partial(print, file=file)

printf(h1(obj.__name__))
printf(linkifier.linkify_fences(paragraph(concat_lines(doc['Summary']))))
printf(linkifier.linkify_fences(paragraph(concat_lines(doc['Extended Summary']))))
printf(linkifier.linkify_fences(paragraph(concat_lines(doc['Summary'])), depth))
printf(linkifier.linkify_fences(paragraph(concat_lines(doc['Extended Summary'])), depth))

# We infer the type annotations from the signatures, and therefore rely on the signature
# instead of the docstring for documenting parameters
Expand All @@ -179,7 +177,7 @@ def print_docstring(obj, file):
# Type annotation
if param.annotation is not param.empty:
anno = inspect.formatannotation(param.annotation)
anno = linkifier.linkify_dotted(anno)
anno = linkifier.linkify_dotted(anno, depth)
printf(f' (*{anno}*)', end='')
# Default value
if param.default is not param.empty:
Expand Down Expand Up @@ -293,6 +291,7 @@ def print_module(mod, path, overview, is_submodule=False):
# Create a directory for the module
mod_slug = snake_to_kebab(mod_name)
mod_path = path.joinpath(mod_slug)
mod_short_path = str(mod_path).replace('docs/api/', '')
os.makedirs(mod_path, exist_ok=True)
with open(mod_path.joinpath('.pages'), 'w') as f:
f.write(f'title: {mod_name}')
Expand All @@ -317,17 +316,18 @@ def print_module(mod, path, overview, is_submodule=False):

for _, c in classes:

if c.__name__ not in ('LinearRegression', 'Optimizer'):
if c.__name__ not in ('LinearRegression', 'Optimizer', 'Scheduler', 'InverseScaling'):
continue
print(f'{mod_name}.{c.__name__}')

# Add the class to the overview
slug = snake_to_kebab(c.__name__)
print(li(link(c.__name__, f'/api-reference/{mod_slug}/{slug}')), end='', file=overview)
print(mod_short_path, mod_slug)
print(li(link(c.__name__, f'../{mod_short_path}/{slug}')), end='', file=overview)

# Write down the class' docstring
with open(mod_path.joinpath(slug).with_suffix('.md'), 'w') as file:
print_docstring(obj=c, file=file)
print_docstring(obj=c, file=file, depth=mod_short_path.count('/') + 1)

# Functions

Expand All @@ -342,11 +342,11 @@ def print_module(mod, path, overview, is_submodule=False):

# Add the function to the overview
slug = snake_to_kebab(f.__name__)
print(li(link(f.__name__, f'/api-reference/{mod_slug}/{slug}')), end='', file=overview)
print(li(link(f.__name__, f'../{mod_short_path}/{slug}')), end='', file=overview)

# Write down the function' docstring
with open(mod_path.joinpath(slug).with_suffix('.md'), 'w') as file:
print_docstring(obj=f, file=file)
print_docstring(obj=f, file=file, depth=mod_short_path.count('.') + 1)

# Sub-modules
for name, submod in inspect.getmembers(mod, inspect.ismodule):
Expand All @@ -360,7 +360,7 @@ def print_module(mod, path, overview, is_submodule=False):

if __name__ == '__main__':

api_path = pathlib.Path('docs/api-reference')
api_path = pathlib.Path('docs/api')

# Create a directory for the API reference
shutil.rmtree(api_path, ignore_errors=True)
Expand Down
Loading

0 comments on commit f6febb2

Please sign in to comment.